EXECUTE IMMEDIATE dynamic_sql_string [USING [IN | OUT | IN OUT] bind_argument [...]] [RETURNING INTO out_argument [...]];
dynamic_sql_string:字符串類型的 SQL 語句或 PL/SQL 塊USING 子句:綁定輸入/輸出/輸入輸出參數,防止 SQL 注入、提升解析復用RETURNING INTO:DML 語句返回受影響行的列值(如序列值、觸發器生成值)CREATE OR REPLACE PROCEDURE p_stu_info (p_id NUMBER) IS
v_name VARCHAR2(50);
v_age NUMBER;
BEGIN
-- 動態 SQL:查詢學生姓名年齡
EXECUTE IMMEDIATE
'SELECT name, age FROM student WHERE id = :1'
USING p_id -- 輸入綁定
RETURNING INTO v_name, v_age; -- 輸出綁定
DBMS_OUTPUT.PUT_LINE(v_name || '的年齡為:' || v_age);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('找不到相應學生,ID:' || p_id);
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('異常:' || SQLERRM);
END p_stu_info;
/
-- 測試調用
EXEC p_stu_info(1001);
DECLARE
v_new_id NUMBER;
v_ts TIMESTAMP;
BEGIN
-- 動態插入並返回序列值與時間戳
EXECUTE IMMEDIATE
'INSERT INTO orders (id, cust_id, amt, created_at)
VALUES (seq_order.NEXTVAL, :cust, :amt, SYSTIMESTAMP)
RETURNING id, created_at INTO :rid, :rts'
USING 1001, 999.99 -- 輸入參數
RETURNING INTO v_new_id, v_ts; -- 雙輸出綁定
DBMS_OUTPUT.PUT_LINE('新訂單ID:' || v_new_id || ',創建時間:' || v_ts);
END;
/
BEGIN
EXECUTE IMMEDIATE
'BEGIN
UPDATE accounts SET balance = balance - :amt WHERE id = :acc_id;
IF SQL%ROWCOUNT = 0 THEN RAISE_APPLICATION_ERROR(-20001, ''賬戶不存在''); END IF;
END;'
USING 500, 1001; -- 綁定金額與賬戶ID
EXCEPTION
WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('轉賬失敗:' || SQLERRM);
END;
/
| 原則 | 錯誤示範 | 正確示範 | 收益 |
|---|---|---|---|
| 強制綁定變量 | EXECUTE IMMEDIATE 'SELECT * FROM t WHERE x=' || v | EXECUTE IMMEDIATE 'SELECT * FROM t WHERE x=:1' USING v | 防注入、游標共享、解析復用 |
| 避免不必要的動態化 | 靜態 SQL 能解決卻用動態 | 優先用靜態 SQL / 靜態游標 | 編譯期檢查、PL/SQL 優化器支持 |
| DBMS_SQL 複雜場景 | 未知列數/類型的動態查詢 | 使用 DBMS_SQL 包描述/定義列 | 支持完全動態的結果集處理 |
| 異常處理完備 | 無 EXCEPTION 塊 | 捕獲 NO_DATA_FOUND、TOO_MANY_ROWS、OTHERS | 避免非預期中斷、記錄診斷信息 |
-- 測試:10萬次循環調用 -- 靜態游標:~0.8秒 -- 動態 SQL(綁定變量):~1.2秒 -- 動態 SQL(拼接字符串):~4.5秒 + 共享池壓力
核心結論:動態 SQL 是雙刃劍——運行時靈活性換取解析開銷與維護複雜度。**僅在對象名、條件結構、DDL 操作真正需要動態時使用**,並嚴格遵循綁定變量原則。