SQLite 中 INSERT ... SELECT 与 ON CONFLICT 一起使用时报错怎么办?
数据阿明
结论
在 SELECT 查询末尾加上 WHERE true,然后写 ON CONFLICT。解析 INSERT ... SELECT 时,SQLite 可能无法判断 ON 是 SELECT 中 JOIN 子句的一部分,还是 UPSERT 子句的开头。加上 WHERE true 就能消除这种语法歧义。
INSERT INTO my_table(col1, col2)
SELECT col1, col2
FROM new_data
WHERE true
ON CONFLICT (col1) DO UPDATE
SET col2 = excluded.col2;
为什么会在 DO 附近报错
这是语句解析阶段的问题,与数据是否真的发生冲突无关。原查询的 SELECT 没有 WHERE 子句,SQLite 可能会把后面的 ON 继续解析为 JOIN 条件,因此解析到 DO 时,无法识别预期的 UPSERT 语法。
所以,INSERT ... VALUES ... ON CONFLICT 和不带 UPSERT 子句的 INSERT ... SELECT 都可以分别正常执行,但组合起来就会报错。
修改方法
如果 SELECT 已经有过滤条件,直接使用该条件即可:
INSERT INTO my_table(col1, col2)
SELECT col1, col2
FROM new_data
WHERE col2 > 0
ON CONFLICT (col1) DO UPDATE
SET col2 = excluded.col2;
如果不需要过滤行,就加上 WHERE true。它不会筛掉任何行,只是明确划分 SELECT 和 UPSERT 子句。
excluded.col2 表示本次插入尝试提供的 col2 值。发生 col1 冲突时,语句会用这个值更新现有记录的 col2。需要更新其他字段时,在 SET 中逐一列出;主键 col1 通常保留为冲突匹配字段。
关于 INSERT OR REPLACE
这里不需要为了绕过语法错误改用 INSERT OR REPLACE。题目中要实现的是主键冲突时更新记录,ON CONFLICT ... DO UPDATE 已经表达了这个操作,也更适合需要依赖 UPDATE 触发器的场景。REPLACE 的冲突处理语义不同,不能直接视为 DO UPDATE 的等价写法。
备注:内容仅供参考。
