核心語義:誰在什麼時候返回什麼?
| DML 類型 | RETURNING 返回內容 | 典型場景 |
| INSERT | 插入**後**的列值(含默認值、觸發器賦值、序列填充) | 獲取自增主鍵、審計新增記錄 |
| UPDATE | 更新**後**的列值 | 確認修改結果、捕獲觸發器衍生值 |
| DELETE | 刪除**前**的列值 | 歸檔刪除數據、異步同步舊值 |
語法規範與限制
-- 單行 DML
INSERT INTO t_test VALUES (1, 'zhangsan', 27) RETURNING age INTO v_age;
UPDATE t_test SET age = 28 RETURNING age INTO v_age;
DELETE FROM t_test WHERE id = 1 RETURNING age INTO v_age;
-- 多行 DML(僅 PL/SQL 支持,返回集合)
DECLARE
TYPE age_arr IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
v_ages age_arr;
BEGIN
UPDATE t_test SET age = age + 1 RETURNING age BULK COLLECT INTO v_ages;
FOR i IN 1..v_ages.COUNT LOOP DBMS_OUTPUT.PUT_LINE(v_ages(i)); END LOOP;
END;
支持矩陣
| 語句形式 | 支持 RETURNING | 備註 |
| INSERT ... VALUES | ✅ | 單行 |
| INSERT ... SELECT | ❌ | 多行不可預測 |
| UPDATE | ✅ | 單行/多行(PL/SQL BULK COLLECT) |
| DELETE | ✅ | 單行/多行(PL/SQL BULK COLLECT) |
| MERGE | ❌ | 12c+ 部分支持,建議拆分 INSERT/UPDATE |
實戰應用模式
模式 1:主鍵回填(替代序列 CURRVAL)
DECLARE
v_id t_test.id%TYPE;
BEGIN
INSERT INTO t_test (name, age) VALUES ('lisi', 30) RETURNING id INTO v_id;
-- v_id 即為新生成的主鍵,無需 SELECT seq.CURRVAL,避免會話級併發風險
INSERT INTO t_test_log (ref_id, action) VALUES (v_id, 'INSERT');
END;
模式 2:審計觸發器替代方案(應用層捕獲舊/新值)
-- 更新前捕獲舊值
UPDATE t_test SET age = 31 WHERE id = v_id RETURNING age INTO v_old_age;
-- v_old_age 為更新前值(注意:UPDATE RETURNING 返回新值,若需舊值需先查詢或用觸發器)
修正:UPDATE RETURNING 返回**新值**;若需同時獲取舊值與新值,需在觸發器中使用 :OLD/:NEW 或應用層先查後改。
模式 3:批量處理性能優化
FORALL i IN 1..v_cnt
UPDATE t_test SET age = v_new_ages(i) WHERE id = v_ids(i)
RETURNING age BULK COLLECT INTO v_updated_ages;
-- 單次上下文切換完成批量更新與結果回收,性能提升數量級
與觸發器的協作邊界
- 觸發器:數據庫內強制業務規則、跨表同步、安全審計,**不可繞過**。
- RETURNING INTO:應用層即時獲取 DML 結果,**可選**、輕量、無額外上下文切換。
- **最佳組合**:觸發器維護衍生列(如
updated_at、version),應用層 RETURNING 獲取觸發器生成的值。
版本演進亮點
- 10g:基礎單行支持。
- 11g:
BULK COLLECT 支持多行 RETURNING。
- 12c:
RETURNING 可用於 MERGE 的 WHEN MATCHED THEN UPDATE 分支(受限)。
- 21c:
RETURNING * 簡寫返回所有列(實驗性)。