簡體   English   中英

在Oracle SQL中優化慢速相關查詢

[英]Optimize slow correlated query in Oracle SQL

我進行了有效的查詢,但我認為這有點慢。 當我將輸出抑制為10行時,執行查詢需要13分鍾。 這是查詢,從一些內容中刪除:

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結合使用,但是不幸的是我看不到解釋計划。

如果使用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關鍵字來表示日期常量。

您在這里有很多多余的構造,例如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具有離散值或日期的數據類型between保存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。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM