動態SQL代碼可能很難調試。舉個例子,讓我們來看看如何應對這一挑戰的6個技巧。
MariaDB的TX , 成熟的生產和由社區驅動,爲任何和每一個企業一個完整的數據庫解決方案-爲現代應用的現代數據庫。
通過我們的AskTOM PL / SQL辦公時間 計劃得到了這個請求 :
親愛的專家,我在下面寫了以下代碼:
----------------------------------------------
宣佈
v_Table all_tables .TABLE_NAME%type;
v_Mnt varchar2(2):= '08' ;
類型Cur_type 是 Ref Cursor;
C Cur_type;
開始
v_Table:= 'ddi_ticket_10_1018' ;
打開C表示“SELECT * from bill”。|| v_Table || v_Mnt || 'where called_nbr = 123' ;
結束;
-------------------------------------------------- -----------------
執行此代碼時,我面臨此錯誤消息。
ORA- 00933 -SQL命令未正確結束
ORA- 06512:第9行。
請檢查上面的代碼並修改語法更正
我一眼就能猜到問題是什麼。
你可以嗎?
我不是在吹噓。我只是鼓勵你 不要 進一步閱讀,而是檢查代碼。什麼可能導致他的問題?
動態SQL可能很棘手 - 在OPEN-FOR或EXECUTE IMMEDIATE是PL / SQL語言的複雜部分之前並非如此。但是因爲它很容易搞亂你動態構建的SQL或PL / SQL。你可以:
遺漏“;” (來自PL / SQL代碼)。
忘記在SQL的各個部分之間留下空格。
有無與倫比的括號。
一直打開。
在這種情況下,我回信說:“我很確定你會發現問題是你在”Where“關鍵字之前沒有空格:
v_Mnt || 'where called_nbr = 123' ;
然後,這個交換提醒我,我應該寫一篇文章,其中包含一些簡單的技巧,可以讓您更輕鬆地調試動態SQL代碼並確保它按預期工作。開始。
使用AUTHID CURRENT_USER(調用者權限)定義子程序。
始終執行IMMEDIATE或OPEN FOR變量。
始終處理動態SQL執行可能引起的異常。
記錄錯誤信息 以及 您嘗試執行的變量。
在子程序中構建測試模式。
我將通過一個超級有用+危險程序的版本開始來證明這些點的價值,這些程序忽略了所有這些:drop_whatever程序。
PROCEDURE drop_whatever(nm IN VARCHAR2 DEFAULT '%',
典型的IN VARCHAR2 DEFAULT '%')
IS
CURSOR object_cur
IS
SELECT object_name,object_type
FROM user_objects
WHERE object_name LIKE UPPER(nm)
AND object_type LIKE UPPER(典型值)
AND object_name <> 'DROP_WHATEVER' ;
開始
FOR rec IN object_cur
循環
執行立即
'DROP'
|| rec .object_type
|| ''
|| rec .object_name
|| 案件
當rec .object_type IN('TABLE','OBJECT')時
然後
'CASCADE CONSTRAINTS'
其他
空值
結束;
結束循環;
結束;
在這個過程中,我使用靜態遊標來查找所有匹配的對象,然後對於找到的每個對象,我執行動態DDL DROP語句。
它很有用,因爲我可以通過輸入任何內容來刪除模式中的所有數據庫對象
執行drop_whatever()
而且出於同樣的原因它也很危險 。
哦,等等。鑑於它有多麼有用,也許我們應該讓 每個人都 能夠使用它。我知道,我會運行這個命令:
GRANT EXECUTE ON drop_whatever TO PUBLIC
嘿,怎麼可能出錯?:-)
非常非常。讓我們逐步完善我的建議並突出潛在的問題。
1.使用AUTHID CURRENT_USER(調用者權限)定義子程序。
該過程沒有AUTHID子句(我打賭你的大多數存儲的程序單元都沒有)。這意味着它默認爲“定義權限”。這意味着它始終以過程的定義者/所有者的特權執行。
這意味着,如果,例如,HR擁有drop_whatever然後SCOTT執行它(謝謝你,GRANT到PUBLIC!),如:
EXEC HR .drop_whatever()
那麼SCOTT將剛剛刪除HR模式中的所有數據庫對象 !
關於DDL語句的事情是Oracle在執行 DDL語句之前和之後都執行 隱式提交。因此,如果您有一個執行動態DDL的存儲過程,您必須警告可能使用它的每個人,他們的會話中的任何未完成的更改(這只是粗魯)或您將此語句添加到您的過程:
PRAGMA AUTONOMOUS_TRANSACTION;
現在,在程序執行的任何提交(或回滾)將僅影響所做的更改 中 的程序。
3.始終執行IMMEDIATE或OPEN FOR變量。
這是一件非常簡單的事情,但是當你試圖找出你的程序有什麼問題時,它可以節省你很多時間。
事情就是這樣:不難想出如何使用EXECUTE IMMEDIATE。但是 在運行時正確構造字符串可能 非常棘手。如此多的小錯誤都可能導致錯誤。如果直接在EXECUTE IMMEDIATE語句中構造字符串,那麼如何查看執行的內容以及可能出錯的位置?
例如,假設在drop_whatever過程中,我構造了我的DROP語句,如下所示:
執行立即
'DROP'
|| rec .object_type
|| rec .object_name ...
當我試圖放下桌子時,我看到:
ORA - 00950:無效的 DROP 選項
這告訴我什麼?不多。它認爲我認爲哪個選項無效?我剛剛嘗試做什麼?
另一方面,如果我將我希望執行的表達式分配給變量,然後調用EXECUTE IMMEDIATE,我可以捕獲錯誤並記錄/顯示該變量(請參閱下面的drop_whatever的第二個實現)。然後我可能會看到類似的東西:
DROP SYNONYMV $ SQL - 失敗
哦! 我現在明白了。我沒有在對象類型和對象名稱之間包含空格。傻我。因此,總是聲明一個變量,將動態構造的SQL語句分配給該變量,然後執行它立即執行。
4.始終處理動態SQL執行可能引起的異常。
5.記錄錯誤信息 以及 您嘗試執行的變量。
如果未捕獲異常,則無法記錄或顯示該變量。如果您不保留該變量值,則很難向您的支持團隊提供有用的問題報告。
除了嘲笑代碼的糟糕設計之外,你做不了什麼。
6.在子程序中構建測試模式。
我一直在編寫代碼並且長時間搞砸了代碼,我已經瞭解到它非常有用 - 尤其是當代碼對錶中的數據進行更改時 - 實現一個不“執行”任何操作的測試模式。只是告訴我如果我願意的話會怎麼做。
當我爲just_checking參數傳遞TRUE(默認值)時,您可以在下面的代碼中看到它。
更好(?)Drop_Whatever
“?” 該標題只是提醒我們,這個程序本質上是危險的。
這是drop_whatever的版本,遵循我的建議。需要注意的是真實的,生產代碼,你應該 永遠不會 “報告”或通過調用DBMS_OUTPUT.PUT_LINE“日誌”的錯誤。誰會看到這個?相反,調用標準錯誤記錄過程,如果沒有,則獲取並使用 Logger。
程序drop_whatever(
nm IN VARCHAR2 DEFAULT '%'
,典型 IN VARCHAR2 DEFAULT '%'
,just_checking IN BOOLEAN DEFAULT TRUE
)
AUTHID CURRENT_USER
IS
PRAGMA AUTONOMOUS_TRANSACTION;
dropstr VARCHAR2(32767);
CURSOR object_cur
IS
SELECT object_name,object_type
FROM user_objects
WHERE object_name LIKE UPPER(nm)
AND object_type LIKE UPPER(典型值)
AND object_name <> 'DROP_WHATEVER' ;
開始
FOR rec IN object_cur
循環
dropstr:=
'DROP'
|| rec .object_type
|| ''
|| rec .object_name
|| 案件
當rec .object_type IN('TABLE','OBJECT')時
然後''CASCADE CONSTRAINTS'
ELSE NULL
結束;
開始
如果just_checking
然後
DBMS_OUTPUT .put_line(dropstr || ' - 只是檢查!');
其他
EXECUTE IMMEDIATE dropstr;
DBMS_OUTPUT .put_line(dropstr || ' - SUCCESSFUL!');
萬一;
例外
其他時候
然後
DBMS_OUTPUT .put_line(dropstr || ' - FAILURE!');
DBMS_OUTPUT .put_line(DBMS_UTILITY .format_error_stack);
結束;
結束循環;
結束;