[英]PL/SQL Error: A loop that contains DML statements should be refactored to use BULK COLLECT and FORALL
我已經在整個互聯網上搜索了一些示例,但是我仍然無法理解為什么不能在此光標內使用DML語句。 我有點想念它背后的理論,但是我不會否認如何正確編寫示例的示例,這也會使我的生活變得更加輕松。 這是我正在處理的查詢(注意:未找到結果時,我刪除了出口;如果游標已經打開,則將其關閉;類似的操作只是將重點放在此處):
DECLARE
// lots of vars
// the cursor below gets all datasources connected to Node XXYZ123
CURSOR DataSourceCheck
IS
SELECT NODENAAM, NAAM, URL, DBNODE1, DBNODE2, DBUSERNAAM, DBNAAM
FROM SCHEMA.TABLENAME
WHERE NODENAAM = 'XXYZ123';
// this cursor will execute row-by-row based on the result set of above cursor
CURSOR CheckIfOnlyDataSource
IS
SELECT NODENAAM, NAAM, URL, DBNODE1, DBNODE2, DBUSERNAAM, DBNAAM
FROM SCHEMA.TABLENAME
WHERE DBUSERNAAM = var_dbusernaam AND (DBNode1 = var_dbnode1 OR DBNode2 = var_dbnode2);
BEGIN
OPEN DataSourceCheck;
LOOP
FETCH DataSourceCheck into var_nodenaam, var_naam, var_URL, var_dbnode1, var_dbnode2, var_dbusernaam, var_dbnaam;
var_rowcount:= 0;
OPEN CheckIfOnlyDataSource;
LOOP
FETCH CheckIfOnlyDataSource into var_nodenaam2, var_naam2, var_URL2, var_dbnode12, var_dbnode22, var_dbusernaam2, var_dbnaam2;
var_rowcount:= var_rowcount + 1;
END LOOP;
// only save result in a temp table when var_rowcount is 1 and not higher.
IF var_rowcount = 1
THEN
INSERT INTO global_temp_table
(t_dbusernaam, t_nodenaam, t_dbnode1, t_dbnode2, t_distinctcount)
VALUES
(var_dbusernaam2, var_nodenaam2, var_dbnode12, var_dbnode22, var_rowcount)
END IF;
CLOSE CheckIfOnlyDataSource;
END LOOP;
END;
失敗的地方是這一部分,並帶有以下消息:應將DML重新配置為FORALL或BULK INTO語句:
IF var_rowcount = 1
THEN
INSERT INTO global_temp_table
(t_dbusernaam, t_nodenaam, t_dbnode1, t_dbnode2, t_distinctcount)
VALUES
(var_dbusernaam2, var_nodenaam2, var_dbnode12, var_dbnode22, var_rowcount)
END IF;
我不明白為什么DML無法以逐行方式工作? 輸出顯然存儲在變量var_dbusernaam2
, var_nodenaam2
, var_dbnode12
和var_dbnode22
,因此我可以執行dbms_output.put_line
來顯示它們。 但是,如果已經將其存儲在變量中,那為什么不能將其簡單地存儲到表中呢(這不是數十億的批量數據,甚至沒有1000條記錄!)。
有沒有簡單的解決方法? 我嘗試了BULK COLLECT和FORALL,但是我需要更多的時間來理解它並使查詢正確-游標中的游標絕對不會使它變得更簡單。
除了Mottor答案中的建議外,Toad標記代碼的原因還在於逐行處理速度很慢。 您在PL / SQL和SQL引擎之間進行了很多上下文切換。
可以將其想象為在房屋附近建造新牆-如果將磚塊運送到驅動器的底部,您是否:
(這等效於逐行處理)
要么:
(這相當於批量處理。)
當然,如果您精明,可以通過將磚頭放在牆旁邊的方式來避免上述情況下需要的所有行走和搬運。 (這等效於基於集合的處理)。
將您的過程轉變為基於集合的方法(結合Mottor的答案)將使其變得簡單:
declare
-- lots of vars
begin
insert into global_temp_table (t_dbusernaam,
t_nodenaam,
t_dbnode1,
t_dbnode2,
t_distinctcount)
select dbusernaam,
nodenaam,
dbnode1,
dbnode2,
cnt
from (select nodenaam,
naam,
url,
dbnode1,
dbnode2,
dbusernaam,
dbnaam,
count(*) over (partition by dbnode1, dbnode2, dbusernaam) cnt
from schema.tablename
where nodenaam = 'XXYZ123')
where cnt = 1;
end;
/
這具有比原始代碼更緊湊的優勢,從而更易於閱讀,理解和調試。 另外,您可以在過程之外單獨運行select語句-輕松查看其操作方式。
它也將比您最初通過兩個游標循環的方法(順便說一句,是重新發明了嵌套循環聯接的方法-數據庫已優化為在純SQL中執行的操作...)可能不是最快的方法。無論如何都要進行連接,如果您一直堅持保持連接!)。
我也想知道為什么您需要將行插入GLOBAL_TEMP_TABLE(我懷疑這是GTT-全局臨時表-而不是普通的堆表)-您不能在單個SQL語句中進行后續處理,使用上面的select語句而不是將數據插入GTT?
這不是錯誤,而是編號為4809的TOAD建議。
PS如果兩個查詢中的表相同,則可以使用
..., COUNT(*) OVER (PARTITION BY DBNODE1, DBNODE2, DBUSERNAAM) c
在第一個查詢中獲取每個DBNODE1,DBNODE2,DBUSERNAAM的行數,而無需第二個查詢。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.