欢迎来到批量数据导入最戏剧性的场景!今天我们要学会用 ON CONFLICT 这个招式,优雅地搞定数据导入时遇到的各种错误。就像给飞机开了自动驾驶:哪怕出点岔子,你也知道怎么操作,不会翻车。来吧,咱们一起拆解 PostgreSQL 的小心机!
没人喜欢意外,尤其是数据死活导不进去的时候!批量导入时你可能会遇到这些常见问题:
- 数据重复。 比如表里有
UNIQUE限制,而你的数据文件里一堆重复行。 - 约束冲突。 比如你想往有
NOT NULL限制的列里塞个空值。结果?报错。PostgreSQL 在这方面一向很严。 - 主键信息重复。 表里可能已经有和你 CSV 文件一样 id 的数据了。
来看看怎么用 ON CONFLICT 躲开这些“坑”。
用 ON CONFLICT 处理错误
ON CONFLICT 语法让你指定遇到约束冲突(比如 UNIQUE 或 PRIMARY 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 = 2和id = 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 |
数据导入和冲突处理
- 先建个临时表
tmp_students:
CREATE TEMP TABLE tmp_students (
id INTEGER,
name TEXT,
age INTEGER
);
- 用
\COPY从文件导入数据:
\COPY tmp_students FROM 'students_update.csv' DELIMITER ',' CSV HEADER
- 把临时表的数据用
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 数据就又灵活又安全。你不仅能避免因为冲突导致请求失败,还能自己掌控怎么处理数据。你的用户(还有服务器!)都会感谢你的。
GO TO FULL VERSION