2010-03-26 57 views
2

我正在使用Oracle對象數據類型來表示時間跨度或時間段。而且我必須完成一系列涉及處理期間集合的操作。在SQL中迭代集合比在PL/SQL中快得多。迭代Oracle對象的集合並將其展開

CREATE TYPE PERIOD AS OBJECT (
    beginning DATE, 
    ending DATE, 
    ... some member functions...); 

CREATE TYPE PERIOD_TABLE AS TABLE OF PERIOD; 

-- what I would like to do: where t.column_value is still a period type 
SELECT (t.column_value).range_intersect(period2) 
FROM TABLE(period_table1) t 
WHERE pa_contains(period_table1, (t.column_value).prev()) = 0 
    AND pa_contains(period_table1, (t.column_value).next()) = 1 

的問題是,該表()函數爆炸的對象爲標量值,我真的很需要的對象,而不是。我可以使用標量值重新創建對象,但這會導致重新實例化對象的開銷。這段時間被設計爲小類,所以在試圖找出將其初始化時會有額外的困難。

有沒有另外一種方法可以在SQL中做到這一點,不會破壞我的對象?

+0

請給我們一個你的代碼的工作示例。 'CONTAINS()'是一個成員函數嗎? MAP函數()?您提出問題越完整,您就越有可能獲得有用的答案。 – APC 2010-03-27 07:33:59

+0

由於與問題正交,我沒有詳細討論週期或週期表。在規範和正文之間有很多代碼和發佈,這將是確保人們的眼睛黯然失色的最可靠的方法。 – 2010-03-28 04:55:47

回答

0

對不起,這是一個非常棘手的問題。但我終於找到了一種使用我之前創建的一些工具來實現這一點的方法。這個技巧最終是迭代一個嵌套的數字表格來獲取每個元素。

所以第一塊是我從Postgres無恥借用的一系列發生器。還有其他方法可以生成數字,但這非常有效。

CREATE OR REPLACE FUNCTION generate_series(
    p_start   NUMBER, 
    p_end   NUMBER 
) RETURN NUMBER_TABLE PIPELINED IS 
BEGIN 
    FOR i IN p_start .. p_end LOOP 
    PIPE ROW(i); 
    END LOOP; 
    RETURN; 
END; 

而第二部分是能夠在SQL中下標集合項目。

CREATE OR REPLACE FUNCTION get_item(p1 PERIOD_TABLE, idx NUMBER) 
RETURN PERIOD IS 
BEGIN 
    RETURN p1(idx); 
END; 

最後把他們放在一起:

FUNCTION range_intersect(
    p1 PERIOD_TABLE, 
    p2 PERIOD_TABLE 
) RETURN PERIOD_TABLE IS 
    v_return PERIOD_TABLE; 
    v_len1  NUMBER(8); 
    v_len2  NUMBER(8); 
BEGIN 
    v_len1 := p1.last; 
    v_len2 := p2.last; 

    WITH pa1 AS (
    SELECT get_item(p1, column_value) AS period 
    FROM TABLE(generate_series(1, v_len1)) 
), 
    pa2 AS (
    SELECT get_item(p2, column_value) AS period 
    FROM TABLE(generate_series(1, v_len2)) 
)  
    SELECT period(start_time, MIN(end_time)) 
    BULK COLLECT INTO v_return 
    FROM (
    SELECT (pa1.period).first() AS start_time 
    FROM pa1 
    WHERE contains(p1, (pa1.period).prev()) = 0 
     AND contains(p2, (pa1.period).first()) = 1 

    UNION ALL 

    SELECT (pa2.period).first() AS start_time 
    FROM pa2 
    WHERE contains(p1, (pa2.period).prev()) = 0 
     AND contains(p2, (pa2.period).first()) = 1 
) s_start 
    ... snip... 

並不容易,但可行的。

+0

我不確定我是否應該接受我自己的答案。但我昨天使用它,它工作得很漂亮。 – 2010-03-31 15:46:53

0

如果我正確理解你的問題,問題在於TABLE運算符的行爲不返回對象表(使用屬性和成員函數完成),而是返回一個簡單的「標量」表(只有屬性是無障礙)。

我不確定你可以用TABLE操作符來描述。這裏是一個臨時表,而不是你的嵌套表(甲骨文10.2.0.1)解決方法:

SQL> CREATE OR REPLACE TYPE PERIOD AS OBJECT (
    2 beginning DATE, 
    3 ending DATE, 
    4 MEMBER FUNCTION range_intersect (p_period period) RETURN VARCHAR2 
    5 ); 
    6/ 
Type created 

SQL> CREATE OR REPLACE TYPE BODY period IS 
    2  MEMBER FUNCTION range_intersect(p_period period) RETURN VARCHAR2 IS 
    3  BEGIN 
    4  RETURN CASE 
    5     WHEN p_period.beginning <= ending 
    6      AND p_period.ending >= beginning 
    7     THEN 'Y' 
    8     ELSE 'N' 
    9    END; 
10  END range_intersect; 
11 END; 
12/ 
Type body created 

SQL> CREATE GLOBAL TEMPORARY TABLE period_table_tmp (
    2  per period 
    3 ); 
Table created 

有了這個設置,我可以查詢該表period_table_tmp在SQL就好像它是period對象的集合(即我可以看到成員函數):

SQL> INSERT INTO period_table_tmp 
    2  VALUES (period(DATE '2010-01-01', DATE '2010-01-31')); 
1 row inserted 

SQL> INSERT INTO period_table_tmp 
    2  VALUES (period(DATE '2010-02-01', DATE '2010-02-28'));  
1 row inserted 

SQL> SELECT t.per.beginning, t.per.ending, 
    2   t.per.range_intersect(period(DATE '2010-01-01', 
    3          DATE '2010-01-15')) intersec 
    4 FROM period_table_tmp t; 

PER.BEGINNING PER.ENDING INTERSEC 
------------- ----------- --------- 
01/01/2010 31/01/2010 Y 
01/02/2010 28/02/2010 N 
+0

文森特。是的,這正是我想要做的。我將不得不測試它,但我認爲臨時表不會比PL/SQL引擎更快。 – 2010-03-29 17:22:11