SQL批量插入多行时如何逐行校验记录不存在才执行插入
逐行校验不存在才插入多行的实现方案
核心逻辑是先把待插入的多行数据构造成可查询的临时结果集,再对每一行单独做存在性判断,筛选出不存在的行再执行插入,避免直接写VALUES加全局WHERE导致的全量生效问题。
方案1:通用CTE写法(兼容绝大多数支持SQL:2003标准的数据库:PostgreSQL、SQL Server、MySQL 8.0+、SQLite 3.8.3+等)
-- 先通过CTE定义所有待插入的行,结构和目标表字段一一对应 WITH pending_insert_rows (promotion_name, discount, start_date, expired_date) AS ( VALUES ('2019 Summer Promotion', 0.15, '20190601', '20190901'), ('2019 Fall Promotion', 0.20, '20191001', '20191101'), ('2019 Winter Promotion', 0.25, '20191201', '20200101') ) INSERT INTO sales.promotions (promotion_name, discount, start_date, expired_date) SELECT pir.promotion_name, pir.discount, pir.start_date, pir.expired_date FROM pending_insert_rows pir -- 逐行判断是否已存在 WHERE NOT EXISTS ( SELECT 1 FROM sales.promotions sp -- 这里替换成你实际判定重复的唯一条件,多字段联合唯一就加多个等值判断 WHERE sp.promotion_name = pir.promotion_name );
方案2:低版本兼容写法(适用于不支持CTE的旧版数据库,比如MySQL 5.x)
用UNION ALL把多行值拼接成派生表,实现和CTE一样的效果:
INSERT INTO sales.promotions (promotion_name, discount, start_date, expired_date) SELECT pir.promotion_name, pir.discount, pir.start_date, pir.expired_date FROM ( SELECT '2019 Summer Promotion' AS promotion_name, 0.15 AS discount, '20190601' AS start_date, '20190901' AS expired_date UNION ALL SELECT '2019 Fall Promotion', 0.20, '20191001', '20191101' UNION ALL SELECT '2019 Winter Promotion', 0.25, '20191201', '20200101' ) pir WHERE NOT EXISTS ( SELECT 1 FROM sales.promotions sp WHERE sp.promotion_name = pir.promotion_name );
注意事项
- 重复判定条件必须和业务唯一规则对齐:如果业务上要求活动名+开始时间联合唯一,就把NOT EXISTS里的条件改成
sp.promotion_name = pir.promotion_name AND sp.start_date = pir.start_date,不要只写单个字段导致漏判。 - 高并发写入场景下,必须给判定重复的字段建唯一索引做兜底:NOT EXISTS的校验是查询时的快照判断,并发写入时可能出现两个事务同时查到不存在、都插入成功的竞态问题,唯一索引能从数据库层阻止重复数据写入。
- 如果你用的数据库支持原生Upsert语法(比如MySQL的
INSERT ... ON DUPLICATE KEY UPDATE、PostgreSQL的INSERT ... ON CONFLICT DO NOTHING),也可以直接用对应语法实现相同效果,性能通常更好,但上面的写法是跨数据库通用的标准SQL,不需要依赖数据库特有关键字。
内容的提问来源于stack exchange,提问作者MattHodson
相关产品推荐
相关产品推荐

