在 PostgreSQL 9.5 及更高版本中处理此问题的(最可能的)最佳方法是使用INSERT ... ON CONFLICT ... DO UPDATE。
假设这是您的原始表格(非常简单,为了这个示例):
CREATE TABLE tbl
(
tbl_id INTEGER,
payload JSONB,
CONSTRAINT tbl_pk
PRIMARY KEY (tbl_id)
) ;
我们用起始数据填充它:
INSERT INTO tbl
(tbl_id, payload)
VALUES
(1, '{"a":12}'),
(2, '{"a":13, "b": 25}'),
(3, '{"a":15, "b": [12,13,14]}'),
(4, '{"a":12, "c": "something"}'),
(5, '{"a":13, "x": 1234.567}'),
(6, '{"a":12, "x": 1234.789}') ;
现在我们执行非冲突插入(即:不会执行 ON CONFLICT ... DO):
-- A normal insert, no conflict
INSERT INTO tbl
(tbl_id, payload)
VALUES
(7, '{"x": 1234.56, "y": 3456.78}')
ON CONFLICT ON CONSTRAINT tbl_pk DO
UPDATE
SET payload = excluded.payload ; -- Note: the excluded pseudo-table comprises the conflicting rows
现在我们执行一个INSERT,它将产生一个PRIMARY KEY 冲突,该冲突将由ON CONFLICT 子句处理并执行更新
-- A conflicting insert
INSERT INTO tbl
(tbl_id, payload)
VALUES
(3, '{"a": 16, "b": "I don''t know"}')
ON CONFLICT ON CONSTRAINT tbl_pk DO
UPDATE
SET payload = excluded.payload ;
现在,两行插入会在一行发生冲突,然后插入另一行:
-- Now one of each
-- A conflicting insert
INSERT INTO tbl
(tbl_id, payload)
VALUES
(4, '{"a": 18, "b": "I will we updated"}'),
(9, '{"a": 17, "b": "I am nuber 9"}')
ON CONFLICT ON CONSTRAINT tbl_pk DO UPDATE
SET payload = excluded.payload ;
我们现在检查表格:
SELECT * FROM tbl ORDER BY tbl_id ;
tbl_id |有效载荷
-----: | :------------------------------------
1 | {“一”:12}
2 | {“a”:13,“b”:25}
3 | {“a”:16,“b”:“我不知道”}
4 | {“a”:18,“b”:“我会更新”}
5 | {“a”:13,“x”:1234.567}
6 | {“a”:12,“x”:1234.789}
7 | {“x”:1234.56,“y”:3456.78}
9 | {“a”:17,“b”:“我是 nuber 9”}
您的代码应该循环遍历传入的数据,获取它,并执行所有INSERT/UPDATE(有时称为MERGE 或UPSERT)一行,或分批,多行VALUES .
您可以在 dbfiddle here
获取所有代码
还有一种选择,如果您分批工作,它更适合。使用WITH 语句,该语句有一个UPDATE 子句,后跟一个INSERT:
-- Avoiding (most) concurrency issues.
BEGIN TRANSACTION ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE ;
WITH data_to_load (tbl_id, payload) AS
(
VALUES
(3, '{"a": 16, "b": "I don''t know"}' :: jsonb),
(4, '{"a": 18, "b": "I will we updated"}'),
(7, '{"x": 1234.56, "y": 3456.78}'),
(9, '{"a": 17, "b": "I am nuber 9"}')
),
update_existing AS
(
UPDATE
tbl
SET
payload = data_to_load.payload
FROM
data_to_load
WHERE
tbl.tbl_id = data_to_load.tbl_id
)
-- Insert the non-existing
INSERT INTO
tbl
(tbl_id, payload)
SELECT
tbl_id, payload
FROM
data_to_load
WHERE
data_to_load.tbl_id NOT IN (SELECT tbl_id FROM tbl) ;
COMMIT TRANSACTION ;
您将获得相同的结果,正如您在 dbfiddle here 中看到的那样。
在这两种情况下,准备好进行错误处理,并准备好重试您的事务,如果它们因并发操作同时修改您的数据库而发生冲突。你的事务可以是显式的(如第二种情况),或者隐式,如果你有某种自动提交每个INSERT