1

我想提高我已經寫了一個概念證明業績和我有沒有運氣。我認爲這種做法可能有缺陷,但我正在努力尋找另一種解決方案。我已經涵蓋了我可以找到的所有Ask Tom文章和論壇帖子。的Oracle SQL層次查詢:層次扁平化和執行聚集

我們正在運行Oracle 10g R2。

我們安排在一個層次結構的項目。數量是根據關係定義的。在層次結構中有兩種類型的對象:作爲邏輯分組的組件和表示實際項目的項目。所以如果我們代表一個完整的工具集,我們將有一個代表整個工具集的根,以及一個代表實際工具的葉。所以:

工具集 - >螺絲刀 - >平頭螺絲刀 - >小扁頭螺絲刀

可以在層次結構中重複使用的組件,如項目。

我需要扁平化層級,所以一個項目的每個實例都有一個行和數量。任何關係可以有一個數量> = 1。爲了得到一個項目的數量,我們需要從根到葉子的所有關係中得到數量的乘積。

我的解決方案的工作,但它不能很好地擴展。針對實際數據運行需要大約8分鐘時間來生成6000多行,並且我們擁有可產生50k +行的層次結構。理想的情況是在10秒以內完成,但我知道這是......樂觀;)

我的解決方案和簡化的數據集如下。任何反饋將非常感謝!

CREATE TABLE ITEMHIER 
(
    PARENT   VARCHAR2(30 BYTE), 
    CHILD   VARCHAR2(30 BYTE), 
    QUANTITY  NUMBER(15,2), 
    ISLEAF   NUMBER 
); 

INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY005','ITEM001',2,1); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY005','ITEM002',1,1); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY005','ITEM003',5,1); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY006','ITEM002',10,1); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY006','ITEM004',3,1); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY007','ITEM005',12,1); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY007','ITEM006',1,1); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY008','ITEM006',2,1); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY008','ITEM005',5,1); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY002','ASSY005',2,0); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY002','ASSY007',1,0); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY003','ASSY006',3,0); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY003','ASSY008',2,0); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY004','ASSY007',1,0); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY004','ASSY005',3,0); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY004','ASSY006',2,0); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY001','ASSY002',1,0); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY001','ASSY003',2,0); 
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY001','ASSY004',1,0); 

COMMIT; 
/

CREATE OR REPLACE FUNCTION GETQTY(P_NAVPATH IN VARCHAR2, 
            P_STARTWITH IN VARCHAR2) RETURN INTEGER AS 

R_QTY INTEGER; 

BEGIN 

    SELECT EXP(SUM(LN(QUANTITY))) 
    INTO R_QTY 
    FROM (
      SELECT QUANTITY, SYS_CONNECT_BY_PATH(CHILD,'/') NAV_PATH 
      FROM ITEMHIER 
      START WITH PARENT = P_STARTWITH 
      CONNECT BY PRIOR CHILD = PARENT 
     ) 
    WHERE INSTR(P_NAVPATH, NAV_PATH) = 1; 

    RETURN R_QTY; 
END; 
/

SELECT 'ASSY001' || SYS_CONNECT_BY_PATH(CHILD,'/') NAV_PATH, 
     GETQTY(SYS_CONNECT_BY_PATH(CHILD,'/'), 'ASSY001') QTY, 
     CHILD 
FROM ITEMHIER 
WHERE ISLEAF = 1 
START WITH PARENT = 'ASSY001' 
CONNECT BY PRIOR CHILD = PARENT; 

----編輯

使用WITH條款我是能夠削減處理時間約1/2,這是一個很大的收穫!任何其他想法?

with 
h as (
    select sys_connect_by_path(child,'/') navpath, 
      child, 
      quantity qty, 
      isleaf 
    from itemhier 
    start with parent = 'ASSY001' 
    connect by prior child = parent 
) 
select h1.navpath, 
     h1.child, 
     (SELECT exp(sum(ln(h2.qty))) 
     FROM h h2 
     WHERE instr(h1.navpath, h2.navpath) = 1) qty 
from h h1 
where isleaf = 1 

EDIT 2

jonearles建議使用SYS_CONNECT_BY_PATH構建算術表達式,然後使用PL/SQL來評估它似乎是要走的路。針對我最大的數據集運行,我能夠在55秒內產生77k行輸出。

我也試圖使用並行性,但正如他指出的那樣,幾乎沒有性能提升。

+0

索引'PARENT'可能會有很大的幫助。您的示例數據非常有用,並且比大多數人發佈的數據好得多,但爲了診斷性能問題,它將有助於獲得更多數據。例如,我用這樣的腳本來創建一個更大的數據集:'開始我在1 .. 100000循環插入ITEMHIER(父母,兒童,數量,ISLEAF)值('ASSY005'|| lpad( ',6,'0'),'ITEM001'|| lpad(i,6,'0'),2,1); ...'。 但我不確定這是否真正代表您的數據。 –

+0

謝謝......我試圖徹底,答案只有問題:)表已索引得很好,父母是其中的一個指標。我非常有信心,索引部門沒有太多的收穫。 – tac0

回答

1

您可以使用SYS_CONNECT_BY_PATH產生的一種表現,一個分支的所有數量的產品。然後使用一個函數來動態執行該表達式以獲得最終數量。

這不是一個理想的解決方案。 SQL和PL/SQL之間的上下文切換需要一些時間。你需要擔心SQL注入。但至少你可以避免查詢同一個表兩次。按照我的經驗,即使兩種語法做同樣的事情並使用相似的訪問路徑,遞歸CTE也可能是最好的解決方案。比connect by顯著快。但是,你需要等到升級到11gR2的。)

CREATE OR REPLACE FUNCTION EVALUATE_EXPRESSION(P_EXPRESSION IN VARCHAR2) RETURN NUMBER AS 
    R_QTY INTEGER; 
BEGIN 
    EXECUTE IMMEDIATE 'SELECT '||P_EXPRESSION||' FROM DUAL' INTO R_QTY; 
    RETURN R_QTY; 
END; 
/


SELECT 'ASSY001' || SYS_CONNECT_BY_PATH(CHILD,'/') NAV_PATH, 
     GETQTY(SYS_CONNECT_BY_PATH(CHILD,'/'), 'ASSY001') QTY, 
     SUBSTR(SYS_CONNECT_BY_PATH(QUANTITY,'*'), 2) QTY_EXPRESSION, 
     EVALUATE_EXPRESSION(SUBSTR(SYS_CONNECT_BY_PATH(QUANTITY,'*'), 2)) QTY2, 
     CHILD 
FROM ITEMHIER 
WHERE ISLEAF = 1 
START WITH PARENT = 'ASSY001' 
CONNECT BY PRIOR CHILD = PARENT; 

另外,你提到的表有索引。但是使用索引的查詢?你可以發佈解釋計劃嗎?

最後,如果查詢速度很慢,您可能需要查看並行度。不幸的是,我從來沒有運用過並行和connect by

+0

我認爲這是答案,它更快。我們正在計劃升級11g,但仍在工作中。升級完成後我會重新訪問,但這是我當然可以使用的東西,直到那時。 – tac0

1

你應該看看WITH語句,公共表表達式/子查詢分解,這將允許您遍歷層次結構在一個單獨的SQL語句。它可能也會更快。

如:

找到 'assy002'

with cte as 
(
    select * from #ITEMHIER 
    union all 
    select i.PARENT, cte.CHILD, cte.QUANTITY, cte.ISLEAF 
    from #ITEMHIER i 
     inner join cte on i.CHILD = cte.PARENT 
) 
    select CHILD,QUANTITY, isleaf from cte 
    where PARENT='assy002' 
    and isleaf=1; 
+1

使用START WITH不會得到相同的結果嗎? '選擇數量,ISLEAF = 1的兒童從ITEMHIER = 1開始PARENT ='ASSY002'按照以前的兒童=家長'連接 我的挑戰是獲得單個路徑數量的乘積。 – tac0

+0

不確定。我的oracle語法充其量是最好的:) – podiluska

+0

感謝您使用with子句的建議,但我會玩,看看我是否可以只做一個分層查詢。這應該會節省一些時間...... – tac0

2

Podiluska的建議是很好的所有葉子。如果你有Oracle 11g R2,通用表表達式是最好的選擇。新語法的遞歸性質將允許您將sys_connect_by_pathinstr結合在一起,這會嚴重傷害您的性能。

試試這個:

select 
    child, 
    sum(total_quantity) total_quantity 
from (
    with h (parent, child, isleaf, quantity, total_quantity) as (
    select 
     parent, 
     child, 
     isleaf, 
     quantity, 
     quantity total_quantity 
    from 
     itemhier 
    where 
     parent = 'ASSY001' 
    union all 
    select 
     ih.parent, 
     ih.child, 
     ih.isleaf, 
     ih.quantity, 
     ih.quantity * h.total_quantity total_quantity 
    from 
     itemhier ih 
    join 
     h on h.child = ih.parent 
) 
    select * from h 
    where isleaf = 1 
) 
group by child; 

這裏的sqlfiddle:http://sqlfiddle.com/#!4/9840f/6

+0

我錯過了關於在Oracle 10g R2上運行的部分。但是,這裏有一些很棒的建議:http://stackoverflow.com/a/4786672/537166 –

+0

這樣做效率更高,但我需要層次結構分支的數量,而不是層次結構中的總數量。這就是爲什麼我使用instr函數,我知道這是昂貴的,但我想不出另一種方法來將聚合縮小到單個分支。 – tac0

+0

不知道我跟着你。此解決方案遞歸遍歷層次結構的分支並計算分支的產品。如果葉節點項只與層次結構的一個分支相關聯,則只能獲得該分支的產品。使用您提供的樣本數據,項目位於多個分支,因此它彙總了項目落在其中的任何潛在分支的總數。但正如我上面提到的,它只適用於Oracle 11g R2或更高版本。 –