CodeGym /课程 /SQL SELF /数据导入时的错误处理(`ON CONFLICT`)

数据导入时的错误处理(`ON CONFLICT`)

SQL SELF
第 23 级 , 课程 3
可用

欢迎来到批量数据导入最戏剧性的场景!今天我们要学会用 ON CONFLICT 这个招式,优雅地搞定数据导入时遇到的各种错误。就像给飞机开了自动驾驶:哪怕出点岔子,你也知道怎么操作,不会翻车。来吧,咱们一起拆解 PostgreSQL 的小心机!

没人喜欢意外,尤其是数据死活导不进去的时候!批量导入时你可能会遇到这些常见问题:

  • 数据重复。 比如表里有 UNIQUE 限制,而你的数据文件里一堆重复行。
  • 约束冲突。 比如你想往有 NOT NULL 限制的列里塞个空值。结果?报错。PostgreSQL 在这方面一向很严。
  • 主键信息重复。 表里可能已经有和你 CSV 文件一样 id 的数据了。

来看看怎么用 ON CONFLICT 躲开这些“坑”。

ON CONFLICT 处理错误

ON CONFLICT 语法让你指定遇到约束冲突(比如 UNIQUEPRIMARY KEY)时该咋办。PostgreSQL 让你可以选择更新已有数据,或者直接无视冲突行。

最基本的 ON CONFLICT 语法长这样:

INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...)
ON CONFLICT (conflict_target)
DO UPDATE SET column1 = new_value1, column2 = new_value2;

如果你只想忽略冲突,把 DO UPDATE 换成 DO NOTHING 就行。

例子:冲突时更新数据

假设我们有个 students 表:

CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    age INT
);

现在我们要导入新数据,但有些已经在库里了:

INSERT INTO students (id, name, age)
VALUES 
    (1, 'Peter', 22),  -- 这个学生已经有了
    (2, 'Anna', 20),  -- 新学生
    (3, 'Mal', 25) -- 新学生
ON CONFLICT (id) DO UPDATE SET 
    name = EXCLUDED.name, 
    age = EXCLUDED.age;

这个例子里,如果已经有同样 ID 的学生,他的数据会被更新:

ON CONFLICT (id) DO UPDATE SET
    name = EXCLUDED.name, 
    age = EXCLUDED.age;

注意 EXCLUDED 这个神奇的词。它指的是“你本来想插入但因为冲突被排除掉的值”。

结果:

  • id = 1 的学生会更新(名字和年龄)。
  • id = 2id = 3 的学生会被加进表里。

例子:忽略冲突

如果你不想更新,只想跳过有冲突的行,用 DO NOTHING

INSERT INTO students (id, name, age)
VALUES 
    (1, 'Peter', 22),  -- 这个学生已经有了
    (2, 'Anna', 20),  -- 新学生
    (3, 'Mal', 25) -- 新学生
ON CONFLICT (id) DO NOTHING;

这样有冲突的行就不会插入,剩下的会乖乖进库。

错误日志记录

有时候光忽略或更新还不够。比如你想把冲突都记下来,方便后面分析。我们可以建个专门的日志表:

CREATE TABLE conflict_log (
    conflict_time TIMESTAMP DEFAULT NOW(),
    id INT,
    name TEXT,
    age INT,
    conflict_reason TEXT
);

然后加上日志记录的错误处理:

INSERT INTO students (id, name, age)
VALUES 
    (1, 'Peter', 22), 
    (2, 'Anna', 20), 
    (3, 'Mal', 25)
ON CONFLICT (id) DO UPDATE SET 
    name = EXCLUDED.name, 
    age = EXCLUDED.age
RETURNING EXCLUDED.id, EXCLUDED.name, EXCLUDED.age
INTO conflict_log;

最后这个例子只能在 存储过程 里用。具体怎么搞,等我们学 PL-SQL 的时候你就懂了。我这算提前剧透,主要是想让你知道还有这种处理数据冲突的方法:把所有有问题的行都记下来。

这样你就能分析冲突原因了。这招在复杂系统里特别有用,能保留批量导入时的“痕迹”。

实战例子

来,把咱们学的都串起来做个小任务。假设你有个学生更新的 CSV 文件,要导入到表里:

文件 students_update.csv

id name age
1 Otto 23
2 Anna 21
4 Wally 30

数据导入和冲突处理

  1. 先建个临时表 tmp_students
CREATE TEMP TABLE tmp_students (
  id   INTEGER,
  name TEXT,
  age  INTEGER
);
  1. \COPY 从文件导入数据:
\COPY tmp_students FROM 'students_update.csv' DELIMITER ',' CSV HEADER
  1. 把临时表的数据用 INSERT ON CONFLICT 插到正式表:
INSERT INTO students (id, name, age)
SELECT id, name, age FROM tmp_students
ON CONFLICT (id) DO UPDATE
  SET name = EXCLUDED.name,
      age = EXCLUDED.age;

现在所有数据,包括更新(比如 id = 1 这行),都顺利导进去了。

常见错误和避免方法

就算是老司机也会犯错,但知道怎么避坑能帮你省下不少时间和头发。

  • UNIQUE 约束冲突。 一定要在 ON CONFLICT 里写对字段。比如你写错了 key(用 id 代替 email),PostgreSQL 会直接把你的请求“请走”。
  • 错用 EXCLUDED 这个别名只对当前请求里传的值有效,别在别的地方乱用。
  • 漏写列。 确保 SET 里写的所有列都在表里。比如你写了 SET non_existing_column = 'value',那就等着报错吧。

ON CONFLICT,批量导入 PostgreSQL 数据就又灵活又安全。你不仅能避免因为冲突导致请求失败,还能自己掌控怎么处理数据。你的用户(还有服务器!)都会感谢你的。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION