Oracle INSERT ALL 多表/多行插入语法详解
一、什么是 INSERT ALL
INSERT ALL 是 Oracle 特有的语法,一条 SQL 语句完成多个 INSERT 操作。
可以实现:
-
✅ 一次性向多个表插入数据
-
✅ 一次性向同一个表插入多行
-
✅ 根据条件动态选择插入到哪个表
普通 INSERT → 一次插一行到一个表
INSERT ALL → 一次插多行到多个表(或同一个表)
二、四种语法形式
INSERT ALL
├── 1. 无条件多表插入 (Unconditional Multi-table Insert)
├── 2. 条件多表插入 ALL (Conditional ALL)
├── 3. 条件多表插入 FIRST (Conditional FIRST)
└── 4. 数据透视/分裂 (Pivoting Insert)
INSERT FIRST -- 条件 FIRST 的简写形式
三、形式 1:无条件多表插入
语法
sql
INSERT ALL
INTO 表1 (列...) VALUES (值...)
INTO 表2 (列...) VALUES (值...)
INTO 表N (列...) VALUES (值...)
SELECT ... FROM ...;
实战 1:插入到同一个表的多行(最常用)⭐
替代多条 INSERT 语句,性能更好、网络往返少。
sql
-- 传统写法(4 次网络往返)
INSERT INTO products VALUES (1, 'iPhone', 6999);
INSERT INTO products VALUES (2, 'Galaxy', 5999);
INSERT INTO products VALUES (3, 'Pixel', 4999);
INSERT INTO products VALUES (4, 'MiPhone', 3999);
-- INSERT ALL 写法(1 次网络往返)
INSERT ALL
INTO products VALUES (1, 'iPhone', 6999)
INTO products VALUES (2, 'Galaxy', 5999)
INTO products VALUES (3, 'Pixel', 4999)
INTO products VALUES (4, 'MiPhone', 3999)
SELECT * FROM DUAL; -- 必须有 SELECT 子句,DUAL 是占位
⚠️ 注意:必须以
SELECT ... FROM ...结尾,没有源表时用SELECT * FROM DUAL。
实战 2:一份数据同时插入多个表
sql
-- 假设员工入职时,同时插入员工表、薪资表、考勤表
INSERT ALL
INTO employees (id, name, hire_date)
VALUES (emp_id, emp_name, hire_dt)
INTO salaries (emp_id, salary, effective_date)
VALUES (emp_id, init_salary, hire_dt)
INTO attendance (emp_id, status)
VALUES (emp_id, 'ACTIVE')
SELECT
1001 AS emp_id,
'张三' AS emp_name,
SYSDATE AS hire_dt,
8000 AS init_salary
FROM DUAL;
一条语句完成 3 张表的同步插入,原子性由事务保证。
实战 3:从一个表分发到多个表
sql
-- 把订单表的数据同时写入两个汇总表
INSERT ALL
INTO order_summary
(order_date, total_amount)
VALUES (order_date, amount)
INTO order_audit
(order_id, amount, audit_time)
VALUES (order_id, amount, SYSDATE)
SELECT
order_id,
order_date,
amount
FROM orders
WHERE order_date = TRUNC(SYSDATE);
四、形式 2:条件多表插入(ALL)
语法
sql
INSERT ALL
WHEN 条件1 THEN
INTO 表1 VALUES (...)
WHEN 条件2 THEN
INTO 表2 VALUES (...)
ELSE
INTO 表3 VALUES (...)
SELECT ... FROM ...;
特点 :每一行所有满足条件的 WHEN 都执行,可能插入到多个表。
实战:按销售额分类存储
sql
CREATE TABLE big_orders (order_id INT, amount NUMBER);
CREATE TABLE medium_orders (order_id INT, amount NUMBER);
CREATE TABLE small_orders (order_id INT, amount NUMBER);
CREATE TABLE all_orders (order_id INT, amount NUMBER);
-- 按金额分类同时写入"all_orders"和某个分级表
INSERT ALL
WHEN amount > 10000 THEN
INTO big_orders (order_id, amount) VALUES (order_id, amount)
WHEN amount BETWEEN 1000 AND 10000 THEN
INTO medium_orders (order_id, amount) VALUES (order_id, amount)
WHEN amount < 1000 THEN
INTO small_orders (order_id, amount) VALUES (order_id, amount)
-- 始终也写入总表
WHEN 1=1 THEN
INTO all_orders (order_id, amount) VALUES (order_id, amount)
SELECT order_id, amount FROM orders;
每条订单先进入对应金额段的表,同时也进入总表 (因为 WHEN 1=1 永远为真)。
五、形式 3:条件优先插入(FIRST)
语法
sql
INSERT FIRST
WHEN 条件1 THEN
INTO 表1 VALUES (...)
WHEN 条件2 THEN
INTO 表2 VALUES (...)
ELSE
INTO 表3 VALUES (...)
SELECT ... FROM ...;
特点 :每一行只匹配第一个满足条件的 WHEN ,类似编程中的 if/elif/else。
实战:客户等级划分(互斥)
sql
CREATE TABLE vip_customers (cust_id INT, total_spent NUMBER);
CREATE TABLE gold_customers (cust_id INT, total_spent NUMBER);
CREATE TABLE silver_customers (cust_id INT, total_spent NUMBER);
CREATE TABLE bronze_customers (cust_id INT, total_spent NUMBER);
-- 按消费总额分级,每个客户只进入一个表
INSERT FIRST
WHEN total_spent >= 100000 THEN
INTO vip_customers (cust_id, total_spent)
VALUES (cust_id, total_spent)
WHEN total_spent >= 50000 THEN
INTO gold_customers (cust_id, total_spent)
VALUES (cust_id, total_spent)
WHEN total_spent >= 10000 THEN
INTO silver_customers (cust_id, total_spent)
VALUES (cust_id, total_spent)
ELSE
INTO bronze_customers (cust_id, total_spent)
VALUES (cust_id, total_spent)
SELECT
cust_id,
SUM(amount) AS total_spent
FROM orders
GROUP BY cust_id;
六、ALL vs FIRST 关键区别
| 关键字 | 匹配规则 | 类比 |
|---|---|---|
| INSERT ALL ... WHEN | 每个 WHEN 独立判断,全部满足都执行 | 多个独立 if |
| INSERT FIRST ... WHEN | 自上而下匹配,遇到第一个就停止 | if / elif / else |
对比示例
数据:amount = 8000
sql
-- 假设条件
WHEN amount > 5000 THEN INTO table_a ...
WHEN amount > 1000 THEN INTO table_b ...
ELSE INTO table_c ...
| 写法 | 结果 |
|---|---|
INSERT ALL |
同时插入 table_a 和 table_b(两个条件都满足) |
INSERT FIRST |
只插入 table_a(匹配第一个就停止) |
七、形式 4:数据透视/分裂(Pivoting Insert)
把宽表 拆成长表(行列转换)。
场景:把季度数据从宽表拆成长表
sql
-- 宽表(每个季度一列)
CREATE TABLE sales_wide (
product_id INT,
q1_sales NUMBER,
q2_sales NUMBER,
q3_sales NUMBER,
q4_sales NUMBER
);
-- 长表(每条记录一个季度)
CREATE TABLE sales_long (
product_id INT,
quarter VARCHAR2(2),
sales NUMBER
);
INSERT INTO sales_wide VALUES (1, 100, 200, 300, 400);
INSERT INTO sales_wide VALUES (2, 500, 600, 700, 800);
-- 用 INSERT ALL 把一行宽表拆成 4 行长表
INSERT ALL
INTO sales_long (product_id, quarter, sales) VALUES (product_id, 'Q1', q1_sales)
INTO sales_long (product_id, quarter, sales) VALUES (product_id, 'Q2', q2_sales)
INTO sales_long (product_id, quarter, sales) VALUES (product_id, 'Q3', q3_sales)
INTO sales_long (product_id, quarter, sales) VALUES (product_id, 'Q4', q4_sales)
SELECT product_id, q1_sales, q2_sales, q3_sales, q4_sales
FROM sales_wide;
执行结果:
sales_long 表:
product_id | quarter | sales
-----------+---------+------
1 | Q1 | 100
1 | Q2 | 200
1 | Q3 | 300
1 | Q4 | 400
2 | Q1 | 500
2 | Q2 | 600
2 | Q3 | 700
2 | Q4 | 800
一条 SQL 完成 2 行 → 8 行 的数据展开。
八、综合实战案例
案例 1:ETL 数据分发
sql
-- 从原始日志表分发到不同业务表
INSERT FIRST
WHEN event_type = 'login' THEN
INTO user_login_log (user_id, login_time, ip)
VALUES (user_id, event_time, client_ip)
WHEN event_type = 'purchase' THEN
INTO order_log (user_id, order_time, amount)
VALUES (user_id, event_time, event_amount)
WHEN event_type = 'error' THEN
INTO error_log (user_id, error_time, error_msg)
VALUES (user_id, event_time, event_detail)
ELSE
INTO unknown_event_log (user_id, event_time, event_type)
VALUES (user_id, event_time, event_type)
SELECT
user_id,
event_time,
event_type,
client_ip,
event_amount,
event_detail
FROM raw_events
WHERE event_time >= TRUNC(SYSDATE) - 1;
案例 2:数据归档 + 业务表同步
sql
-- 订单同时写入历史归档表和明细表
INSERT ALL
INTO order_archive
(order_id, order_data, archive_time)
VALUES (order_id, order_json, SYSDATE)
INTO order_detail
(order_id, customer_id, amount, status)
VALUES (order_id, customer_id, amount, 'NEW')
INTO customer_order_summary
(customer_id, order_count, last_order_time)
VALUES (customer_id, 1, SYSDATE)
SELECT
seq_order_id.NEXTVAL AS order_id,
:p_customer_id AS customer_id,
:p_amount AS amount,
:p_order_json AS order_json
FROM DUAL;
案例 3:星级评分按区间统计
sql
-- 一条 SQL 把评分数据同时写入多个统计表
INSERT ALL
WHEN score >= 4.5 THEN INTO excellent_reviews VALUES (review_id, score)
WHEN score >= 3.5 THEN INTO good_reviews VALUES (review_id, score)
WHEN score >= 2.5 THEN INTO average_reviews VALUES (review_id, score)
ELSE INTO poor_reviews VALUES (review_id, score)
SELECT review_id, score FROM raw_reviews;
九、性能与限制
优势
- ✅ 网络往返减少:批量操作,1 次网络请求
- ✅ 事务原子性:所有插入要么全成功,要么全失败
- ✅ 代码简洁:替代复杂的 PL/SQL 循环
- ✅ 解析次数少:SQL 只解析 1 次
限制
| 限制 | 说明 |
|---|---|
| 表数量上限 | 最多 127 张表 |
| 不支持远程表 | 不能 INSERT 到 DB Link 的表(部分版本) |
| 不支持触发器循环 | 自引用触发器要小心 |
| 不能用 RETURNING | 无法获取插入后的行(直接 INSERT...RETURNING 才行) |
| 不支持序列在 VALUES 中重复使用 | seq.NEXTVAL 每次取值会变 |
序列陷阱⭐
sql
-- ❌ 陷阱: 每个 INTO 用 NEXTVAL 都会取新值
INSERT ALL
INTO t1 VALUES (seq.NEXTVAL, 'A') -- 假设取到 1
INTO t2 VALUES (seq.NEXTVAL, 'B') -- 取到 2
INTO t3 VALUES (seq.NEXTVAL, 'C') -- 取到 3
SELECT * FROM DUAL;
-- 三张表的 ID 不一样!
-- ✅ 正确写法: 在 SELECT 中先取值
INSERT ALL
INTO t1 VALUES (new_id, 'A')
INTO t2 VALUES (new_id, 'B')
INTO t3 VALUES (new_id, 'C')
SELECT seq.NEXTVAL AS new_id FROM DUAL;
-- 三张表 ID 一致!
十、性能对比
测试场景:插入 10000 行
sql
-- 方式1: 单条 INSERT 循环(应用层 10000 次往返)
FOR i IN 1..10000 LOOP
INSERT INTO t VALUES (i, 'data_' || i);
END LOOP;
-- 耗时: ~5 秒
-- 方式2: PL/SQL FORALL(推荐用于纯插入)
DECLARE TYPE t_arr IS TABLE OF VARCHAR2(50);
arr t_arr := t_arr();
BEGIN
FOR i IN 1..10000 LOOP arr.EXTEND; arr(i) := 'data_' || i; END LOOP;
FORALL i IN 1..10000 INSERT INTO t VALUES (i, arr(i));
END;
-- 耗时: ~0.3 秒
-- 方式3: INSERT ALL(适合多表)
INSERT ALL
INTO t1 VALUES (...)
INTO t2 VALUES (...)
SELECT ... FROM source;
-- 耗时: 取决于源数据量
⚠️ 如果只是向同一个表批量插入 ,
INSERT INTO ... SELECT或FORALL通常比INSERT ALL更快。INSERT ALL的优势在于多表分发场景。
十一、常见错误
错误 1:忘记 SELECT FROM DUAL
sql
-- ❌ ORA-00942: table or view does not exist
INSERT ALL
INTO t1 VALUES (1, 'A')
INTO t2 VALUES (2, 'B');
-- ✅
INSERT ALL
INTO t1 VALUES (1, 'A')
INTO t2 VALUES (2, 'B')
SELECT * FROM DUAL;
错误 2:VALUES 中引用了不存在的列别名
sql
-- ❌ 报错: 列 emp_name 不存在
INSERT ALL
INTO emp VALUES (emp_id, emp_name)
SELECT 1, '张三' FROM DUAL;
-- ✅ 必须给 SELECT 列起别名
INSERT ALL
INTO emp VALUES (emp_id, emp_name)
SELECT 1 AS emp_id, '张三' AS emp_name FROM DUAL;
错误 3:表数量过多(>127)
ORA-01747: invalid user.table.column...
-- 拆成多条 INSERT ALL
十二、与其他数据库的对比
| 数据库 | 多表插入方式 |
|---|---|
| Oracle | INSERT ALL / INSERT FIRST |
| PostgreSQL | 不支持原生多表 INSERT,用 CTE:WITH ins1 AS (INSERT ...) INSERT INTO ... |
| MySQL | 不支持多表,只能多次 INSERT 或写存储过程 |
| SQL Server | 用 OUTPUT INTO 配合 MERGE 间接实现 |
PostgreSQL 等价写法
sql
-- PG 用 Writable CTE
WITH inserted_main AS (
INSERT INTO orders (id, amount) VALUES (1, 100)
RETURNING id, amount
)
INSERT INTO order_audit (order_id, amount, audit_time)
SELECT id, amount, NOW() FROM inserted_main;
一句话总结
INSERT ALL是 Oracle 用一条 SQL 实现多行/多表插入的利器,核心套路:
sqlINSERT ALL -- 或 INSERT FIRST INTO 表1 VALUES (...) -- 可加 WHEN 条件 INTO 表2 VALUES (...) ... SELECT ... FROM 源表; -- 无源表时用 FROM DUAL
- 多行同一表插入 :
INSERT ALL INTO t VALUES(...) INTO t VALUES(...) SELECT * FROM DUAL- 数据分发到多表 :用
WHEN条件路由- 互斥分流 :用
INSERT FIRST(类似 if/elif)- 同时全收 :用
INSERT ALL(多个 WHEN 独立判断)- 宽表转长表:经典的 Pivoting Insert
注意序列
NEXTVAL陷阱------在 SELECT 中取值再引用,避免每个 INTO 重复取新值。
如果你有具体的数据分发场景(比如 ETL、归档、分表)。