Oracle INSERT ALL 多表多行插入语法详解

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_atable_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 ... SELECTFORALL 通常比 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 实现多行/多表插入的利器,核心套路:

sql 复制代码
INSERT 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、归档、分表)。

相关推荐
zzhongcy3 小时前
Flyway 数据库版本管理工具使用指南
数据库·人工智能
志栋智能3 小时前
效率革命:超自动化巡检如何将小时压缩为分钟?
运维·数据库·自动化
十年编程老舅3 小时前
Linux NUMA架构深度剖析:内存管理、进程调度与性能优化
linux·数据库·c++·内存管理·numa
fly_over4 小时前
AI Agent 开发实战教程(三):记忆与数据库集成
数据库·人工智能·python·ai agent
_Evan_Yao4 小时前
从 select 到 epoll,再到 Agent 循环:如何用 I/O 多路复用撑起千军万马?
java·数据库·人工智能·后端
鸽芷咕4 小时前
金仓数据库字符集与国际化支持:多语言环境下的编码处理方案
数据库·oracle
m0_702036534 小时前
如何通过SQL视图对比两表差异_利用FULL JOIN构建视图
jvm·数据库·python
老纪4 小时前
golang如何实现工作流引擎_golang工作流引擎实现要点
jvm·数据库·python
夏贰四4 小时前
数据转换的本质是什么?数据转换适配哪些业务场景?
大数据·数据库·数据转换