2014-04-10 13 views
3

我的任務是優化Oracle SQL中的一個查詢,其中一個表與其自身連接,使用與其中一列中的varchar數據的分析片段有關的條件。據我瞭解,Oracle不會使用索引,因爲ON子句中的列名僅作爲函數的參數出現。查詢需要幾乎永遠完成。用處理後的REF數據創建一個表格(見下文)可以解決這個問題,但由於其他原因,這是不容置疑的。我已經爲插圖準備了一個簡化版本的問題(我相當肯定這是線索,所以我提取了一個更復雜的查詢的相關部分)。 「交易」表具有以下的列:如何優化Oracle SQL中將列名作爲函數參數出現的查詢?

  • TRAN - 一個10位數字被交易的代碼,
  • STORE - 其中交易被做了商店的代碼,
  • DATE - 交易日期,
  • REF - 參考代碼到不同的交易(在退貨等情況下)。此代碼格式爲:[商店代碼] * [交易年份的最後兩位數字] * [TRAN的最後7位數字不含左側零號],因此它可以如下所示:'142 * 09 * 3234'。基本上,REF指向表中的其他行,但在使用之前必須進行一些處理。

    SELECT * 
    FROM transactions t1 
        JOIN transactions t2 
        ON (t2.store = substr(t1.REF, 1, instr(t1.REF, '*') - 1) 
         AND to_char(t2.DATE, 'yy') = substr(t1.REF, instr(t1.REF, '*', 1, 1) + 1), instr(t1.REF, '*', 1, 2) - 1) 
         AND to_number(substr(to_char(t2.TRAN), -7)) = to_number(substr(t1.REF, instr(t1.REF, '*', 1, 2) + 1)) 
         ) 
    

我沒有經驗處理SQL優化,所以我會很感激的良好方向的任何建議。

+0

也許這個SO問題會幫助你,閱讀評論也:http://stackoverflow.com/questions/2486952/optimizing-oracle-query?rq=1,但是瓶頸可能在這一行'... to_char(t2.DATE,'yy')...' – bodi0

+1

這種連接是可怕的...... –

+0

解釋計劃將幫助我們排除故障。例如,優化器有可能嚴重低估了聯接的基數,導致了NESTED LOOP而不是HASH JOIN。在這種情況下,USE_HASH(t1 t2)'提示或條件的擴展統計信息可能會有所幫助。但這只是一個沒有執行計劃的瘋狂猜測。 –

回答

2

您可以在Oracle中創建「基於函數的索引」。試試這個:

CREATE INDEX ind_1 ON transactions (SUBSTR(REF, 1, INSTR(REF, '*') - 1)); 
CREATE INDEX ind_2 ON transactions (SUBSTR(REF, INSTR(REF, '*', 1, 1) + 1), INSTR(REF, '*', 1, 2) - 1)); 
CREATE INDEX ind_3 ON transactions (TO_NUMBER(SUBSTR(TO_CHAR(TRAN), -7))); 
CREATE INDEX ind_4 ON transactions (TO_NUMBER(SUBSTR(REF, INSTR(REF, '*', 1, 2) + 1))); 
CREATE INDEX ind_5 ON transactions (TO_CHAR(DATE, 'yy')); 

但是,您應該檢查解釋計劃並刪除那些未使用的索引。 您也可以創建虛擬列並在那裏創建索引。

+0

我不確定這是個好主意,因爲:'當WHERE子句選擇少於15%的大表行時,索引範圍掃描通常具有快速響應時間。如果表達式在基於函數的索引中實現,優化器可以更準確地估計表達式選擇多少行。'從這裏:http://docs.oracle.com/cd/E11882_01/appdev.112/e25518/adfns_indexes.htm#ADFNS005但在查詢中有任何地方,所以100%被選中... –

0

基本上,你就完蛋了,因爲一個不好的設計,歡迎來到我的世界=)

無論如何,當我看着它,你只有2場,你可以用它來加入交易回其引用交易:STOREDATE(儘管後者比較「粗糙」)。由於他們決定只存儲交易的最後7位數字,所以加快這一點的唯一方法是添加一個存儲這7位數字的新字段。但是,如果你走上這條路,將整個REF語法存儲在新的(計算的)字段中會更有意義。

這肯定會是最痛苦的解決了這個問題,因爲你將能夠添加一個索引在上述領域,然後改變你的查詢

SELECT * 
    FROM transactions t1 
    JOIN transactions t2 
    ON t2.TRAN_AS_REF = t1.REF 

但是,從我的理解,你會不能/允許向表中添加額外的(計算)字段?! 添加另一張可容納此信息的表,以便您可以使用它來鏈接信息也是一種解決方案,但它會增加一些複雜性以確保數據始終保持最新!但是,無論如何,從我的理解,這裏不是一個選項。 另一個解決方法可能是創建一個視圖,以重新映射TRAN及其REF-equivalent。您可以實現所述視圖並將其用作連接中的鏈接。這就像計算的字段方法一樣,總是能夠獲得最新的好處,而不需要額外的邏輯和/或對已有邏輯的改變。

最後,基於Wernfrieds的建議,也許有可能創建一個將TRAN編譯爲REF語法的索引?我沒有這方面的經驗,但它聽起來像是一個選擇。

索引然後將沿

CREATE INDEX ind_ref ON transactions (STORE + '*' + to_char(DATE, 'yy') + '*' + substr(to_char(TRAN), -7))) 

線的東西,而你的查詢將隨即成爲這樣的事情:

SELECT * 
FROM transactions t1 
    JOIN transactions t2 
    ON ((t2.STORE + '*' + to_char(t2.DATE, 'yy') + '*' + substr(to_char(t2.TRAN), -7)) = t1.REF) 

,並希望該服務器將然後挑去後指數正確的t2記錄。 但就像我說的,我沒有這方面的經驗,但它是值得拍攝恕我直言。

無論如何,如果所有這些都是nono,而且您只能優化查詢,那麼我會建議儘可能使用STORE和DATE信息來做到最好:

SELECT * 
    FROM transactions t1 
    JOIN transactions t2 
    ON ( 
      t2.store = substr(t1.REF, 1, instr(t1.REF, '*') - 1) 
     AND t2.DATE BETWEEN to_date('0101' + substr(t1.REF, instr(t1.REF, '*', 1, 1) + 1), instr(t1.REF, '*', 1, 2) - 1) 
         AND to_date('3112' + substr(t1.REF, instr(t1.REF, '*', 1, 1) + 1), instr(t1.REF, '*', 1, 2) - 1) 
     AND t2.TRAN % 10000000 = to_number(substr(t1.REF, instr(t1.REF, '*', 1, 2) + 1)) 
     ) 

思考一下,如果你可以添加該指數

CREATE INDEX ind_tst ON transactions (STORE, DATE, TRAN % 10000000) 

那麼它很可能是上面已經查詢是你現在所擁有的相當的改善。並大聲思索這意味着你可以試試這個:

CREATE INDEX ind_tst ON transactions (STORE, to_char(DATE, 'yy'), TRAN % 10000000) 

SELECT * 
    FROM transactions t1 
    JOIN transactions t2 
    ON ( 
      t2.store = substr(t1.REF, 1, instr(t1.REF, '*') - 1) 
     AND to_char(t2.DATE, 'yy') = substr(t1.REF, instr(t1.REF, '*', 1, 1) + 1), instr(t1.REF, '*', 1, 2) - 1) 
     AND t2.TRAN % 10000000 = to_number(substr(t1.REF, instr(t1.REF, '*', 1, 2) + 1)) 
     ) 

希望這有助於一點。由於我沒有使用Oracle的經驗,您可能需要修復這裏和那裏的語法,對此感到抱歉......無論如何,祝您好運!