[英]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.