2015-02-24 195 views
0

我做了一個可以工作的查詢,但在我看來有點慢。當我將輸出壓縮到10行時,執行查詢需要13分鐘。這是查詢,從一些東西剝離:在Oracle SQL中優化慢相關查詢

SELECT 
    (SELECT ANSWER 
     FROM (
      SELECT to_number(fiit.ANSWER, '999') ANSWER, 
        foin.CLIENT_ID id, 
        foin.STARTDATE start_date, 
        row_number() over(PARTITION BY foin.CLIENT_ID ORDER BY foin.FORM_ID ASC) rnk 
       FROM forms_filled foin, forms_items_filled fiit, treatment trtm 
       WHERE foin.FORM_ID = fiit.FORM_ID 
       AND foin.CLIENT_ID = trtm.CLIENT_ID 
       AND fiit.FORM_NUMBER = 607 
       AND fiit.FORM_ITEM_NUMBER = 3779 
       AND length(fiit.ANSWER) >= 1 
       AND trtm.TREATMENTCODE = 'K' 
       AND trtm.ENDDATE BETWEEN TRUNC(to_date('01/01/2014', 'dd/mm/yyyy'), 'DDD') AND TRUNC(to_date('31/12/2014', 'dd/mm/yyyy'), 'DDD') 
       AND foin.STARTDATE BETWEEN trtm.STARTDATUM AND NVL(trtm.ENDDATE, to_date('01/01/9999', 'dd/mm/yyyy')) 
       ) inn 
    WHERE rnk = 1 
    AND inn.id = client.CLIENT_ID 
    ) form1 
FROM treatment trtm, CLIENT client 
WHERE trtm.TREATMENTCODE = 'K' 
AND client.CLIENT_ID = trtm.CLIENT_ID 
AND trtm.ENDDATE BETWEEN TRUNC(to_date('01/01/2014', 'dd/mm/yyyy'), 'DDD') AND TRUNC(to_date('31/12/2014', 'dd/mm/yyyy'), 'DDD') 

外部查詢結果175級的客戶誰擁有特定的治療代碼,並在2014年治療結束日期現在爲這些客戶的很多其他的數據是檢索(如姓名,年齡,治療時間)這是不相關的,我現在離開了。然後有大約30個類似的子查詢,從表單中檢索答案。我使用了相關的查詢,因爲要從這些表單中檢索答案,客戶端ID必須是已知的。如果這是子查詢查找數據所需的唯一東西,這不會是一個問題,但還有一個其他要求:檢索的表單必須在治療期內填寫,因爲我無法找到一種方法將這些數據從外部查詢推送到子子查詢,我再次在子查詢中查詢,這導致了速度緩慢。

有一個子查詢和一個子查詢的原因是因爲必須找到表單的第N個排名答案。在我的代碼的以前版本中,我沒有子查詢的where子句中的處理代碼,處理開始和結束日期要求。這導致子子查詢得出例如4個排名爲1,2,3,4的結果,但不一定是在治療期內形成的形式,這是錯誤的。

所以加上幾行:

AND trtm.TREATMENTCODE = 'K' 
AND trtm.ENDDATE BETWEEN TRUNC(to_date('01/01/2014', 'dd/mm/yyyy'), 'DDD') 
AND TRUNC(to_date('31/12/2014', 'dd/mm/yyyy'), 'DDD') 
AND foin.STARTDATE BETWEEN trtm.STARTDATUM AND NVL(trtm.ENDDATE, to_date('01/01/9999', 'dd/mm/yyyy')) 

導致查詢是正確的,它以前是不完全正確的。他們還導致查詢需要幾個小時,而不是約175個行的40秒。

我現在的問題是,我如何重寫這個查詢,使其更快?我將Oracle 11.2.40與Toad Data Point 3.5結合使用,但不幸的是我看不到解釋計劃。

+0

您最好打賭的是在您的查詢上運行EXPLAIN PLAN,並查看查詢的執行方式以及它正在使用的索引或可以使用的索引。 – thedoctor 2015-02-24 11:52:45

+0

我只對數據庫有SELECT權限(以及更新某些表的權限),爲了啓用Toad中的解釋計劃功能,您需要創建一個新表,對吧? – Benjamin 2015-02-24 11:58:11

+0

是的,也許你可以要求dba爲你運行解釋計劃。 – thedoctor 2015-02-24 12:00:52

回答

2

如果使用keep關鍵字獲取第一個值,則可以省略嵌套子查詢。這反過來又允許您使用與外部查詢相關的查詢,因此您不必重新計算所有行的結果以獲取給定行的值。

查詢看起來像:

SELECT (SELECT max(to_number(fiit.ANSWER, '999')) keep (dense_rank first order by foin.FORM_ID ASC) 
     FROM forms_filled foin JOIN 
      forms_items_filled fiit 
      ON foin.FORM_ID = fiit.FORM_ID 
     WHERE foin.CLIENT_ID = trtm.CLIENT_ID AND 
       fiit.FORM_NUMBER = 607 
       fiit.FORM_ITEM_NUMBER = 3779 AND 
       length(fiit.ANSWER) >= 1 AND 
       foin.STARTDATE BETWEEN trtm.STARTDATUM AND NVL(trtm.ENDDATE, DATE '1999-01-01') 
     ) 

我也鼓勵您使用現代明確join語法和關鍵字date表達日期常量。

+0

感謝您回答@Gordon!其實我已經在一些子查詢中使用了keep關鍵字,當我需要rank = 2或3時,我特別需要第一個或最後一個值(rank = 1並對ASC或DESC進行排序),或者3我認爲它不再可行?關於日期關鍵字,謝謝,我會牢記這一點。關於顯式(ANSI?)連接語法,你能否詳細說明爲什麼你推薦這個?效率更高,只是看起來更好,還是更好的跨平臺支持等等? – Benjamin 2015-02-25 20:28:23

+0

顯式'join's是比使用逗號的隱式連接更強大的語法。這就是你應該使用它們的原因。大多數人也發現它們更具可讀性和可維護性,因爲連接條件與表格引用相鄰(但是,對於哪些人可能不同意,有一定程度的意見)。至於獲取第二個或第三個值,你可以使用''行之間''子句。 – 2015-02-26 00:57:00

1

你在這裏有很多多餘的構造,如TRUNC(to_date('31/12/2014', 'dd/mm/yyyy'), 'DDD')。您打電話trunc說「剝離任何時間組件」,然後傳遞一個沒有時間組件的構建日期開始。只要說date '2014-01-01'並完成它。

至於使用日期範圍,如果您想選擇2014年的日期,最好的方法是像這樣比較:myDate >= date '2014-01-01' and myDate < date '2015-01-01'。這樣你就不用擔心myDate有時間分量和可能的時間值。保存between的數據類型的謹慎值或日期,你知道已經在你想要的謹慎組件。

這些建議都不能解決您的特定問題。但要養成寫「瘦」的代碼的習慣,如果運行速度太慢,它會簡化你對問題的搜索。

一個主要的建議,可能會加快你的查詢,但即使不會明顯簡化它,所以增加了可維護性,是從選擇列表中刪除子查詢。

一般來說,對於複雜的查詢,不要試圖一口氣寫完整件事情。選擇一個表格(在你的情況下治療)並選擇你知道你將需要的數據。檢查結果。如果你還沒有認識它,確保它是完整和準確的。

select t.CLIENT_ID, t.TREATMENTCODE, t.ENDDATE 
from treatment t 
where t.TREATMENTCODE = 'K' 
    and t.ENDDATE >= date '2014-01-01' 
    and t.ENDDATE < date '2015-01-01'; 

現在加入鄰桌來了,增加了選擇列表你想從該表中看到,除去從第一個數據是您滿意,不以其他方式所需要的數據。

select t.CLIENT_ID, c.CLIENT_ID 
from treatment t 
join client c 
    on c.CLIENT_ID = t.CLIENT_ID 
where t.TREATMENTCODE = 'K' 
    and t.ENDDATE >= date '2014-01-01' 
    and t.ENDDATE < date '2015-01-01'; 

添加您在選擇列表中需要驗證您得到完全正確的結果(條件到目前爲止您已經指定)的任何領域。重複每個其他表,直到你得到最終結果爲止。這樣,如果你突然開始得到錯誤的結果,你會知道哪個表開始出現問題。

您的最終結果集可能包含大量不需要的行。沒關係,只要它包含需要的所有行。保存最後一個過濾器,因爲您希望能夠看到查詢生成的所有數據。當您知道數據包含您需要的所有內容時,最後一步是過濾出不需要的結果,直到您只有自己想要的結果。但能夠查看所有數據可以向您顯示執行該過濾的多種方法,如果您儘早過濾數據,這些方式可能並不明顯。

我沒有任何測試數據,所以我不能在下面測試我的候選人。但是,除非我完全錯過了某些東西(明顯的可能性),否則它應該相當接近。如果沒有別的,也許它可以指向你的解決方案。

SELECT c.CLIENT_ID, to_number(fif.ANSWER, '999') form1 
FROM treatment t 
join CLIENT  c 
    on c.CLIENT_ID  = t.CLIENT_ID 
join forms_filled ff 
    on ff.CLIENT_ID = c.CLIENT_ID 
join forms_items_filled fif 
    on fif.FORM_ID  = ff.FORM_ID 
WHERE t.TREATMENTCODE = 'K' 
    and fif.FORM_NUMBER = 607 
    AND fif.FORM_ITEM_NUMBER = 3779 
    AND length(fif.ANSWER) >= 1 
    AND t.ENDDATE >= date '2014-01-01' 
    AND t.ENDDATE < date '2015-01-01' 
    AND ff.STARTDATE BETWEEN t.STARTDATUM AND NVL(t.ENDDATE, date '9999-12-31'); 

另外一個建議:「有沒有定義沒有結束日期」當你有一個像場end_date時,首先想到的是用NULL作爲一個指標嘗試將其設置爲NOT NULL,並使用默認的最大日期值date '9999-12-31'。這意味着同樣的事情,並通過擺脫nvl或其他處理NULL值的方式來簡化比較。

編輯:糟糕。我已經移動了窗口函數來避開它,因爲我只是在部分結果之後。它包含在我剪切/粘貼代碼時。

哦,好吧,不妨在最終答案中包含這一點。

with 
Partial(CLIENT_ID, form1, rnk)as(
    SELECT c.CLIENT_ID, to_number(fif.ANSWER, '999') form1, 
      row_number() over(PARTITION BY ff.CLIENT_ID ORDER BY ff.FORM_ID ASC) rnk 
    FROM treatment t 
    join CLIENT  c 
     on c.CLIENT_ID  = t.CLIENT_ID 
    join forms_filled ff 
     on ff.CLIENT_ID = c.CLIENT_ID 
    join forms_items_filled fif 
     on fif.FORM_ID  = ff.FORM_ID 
    WHERE t.TREATMENTCODE = 'K' 
     and fif.FORM_NUMBER = 607 
     AND fif.FORM_ITEM_NUMBER = 3779 
     AND fif.ANSWER is not null 
     AND t.ENDDATE >= date '2014-01-01' 
     AND t.ENDDATE < date '2015-01-01' 
     AND ff.STARTDATE BETWEEN t.STARTDATUM AND NVL(t.ENDDATE, date '9999-12-31') 
) 
select CLIENT_ID, form1 
from Partial 
where rnk = 1; 

假設這讓你非常接近,如果你看看這個和你原來的執行計劃,你應該看到一個重大的改進。

還有一個變化。您正在測試一個字符串以確保它至少有一個字符。在Oracle中,不需要將空字符串視爲NULL。只要檢查NOT NULL。

+0

首先,感謝@TommCatt花時間寫出你的答案。關於格式化日期的建議很有幫助,謝謝。我會用部分代碼嘗試你的建議,也許稍後再回來。 – Benjamin 2015-02-25 20:36:52