几个月前,我从Stack Overflow上的一个回答中学到了如何在MySQL中使用以下语法一次执行多个更新:
INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);
我现在已经切换到PostgreSQL,显然这是不正确的。它引用了所有正确的表,所以我假设这是使用不同关键字的问题,但我不确定在PostgreSQL文档中这是被覆盖的。
为了澄清,我想插入一些东西,如果它们已经存在,则更新它们。
PostgreSQL >= v15
关于这个话题的大新闻是在PostgreSQL v15中,可以使用MERGE命令。事实上,这个期待已久的特性被列为v15版本的第一个改进。
这类似于INSERT…ON冲突,但更面向批处理。它具有强大的WHEN MATCHED vs WHEN NOT MATCHED结构,可以在这种情况下执行INSERT、UPDATE或DELETE操作。
它不仅简化了批量更改,而且甚至比传统的UPSERT和INSERT增加了更多的控制…在冲突
看看这个非常完整的样本从官方页面:
MERGE INTO wines w
USING wine_stock_changes s
ON s.winename = w.winename
WHEN NOT MATCHED AND s.stock_delta > 0 THEN
INSERT VALUES(s.winename, s.stock_delta)
WHEN MATCHED AND w.stock + s.stock_delta > 0 THEN
UPDATE SET stock = w.stock + s.stock_delta
WHEN MATCHED THEN
DELETE;
PostgreSQL v9, v10, v11, v12, v13, v14
如果版本低于v15而高于v9.5,可能最好的选择是使用UPSERT语法,带有ON CONFLICT子句
没有简单的命令可以做到这一点。
最正确的方法是使用函数,就像docs中的函数一样。
另一种解决方案(尽管不是那么安全)是在执行update的同时返回,检查哪些行是更新的,然后插入其余的行
大致如下:
update table
set column = x.column
from (values (1,'aa'),(2,'bb'),(3,'cc')) as x (id, column)
where table.id = x.id
returning id;
假设返回id:2:
insert into table (id, column) values (1, 'aa'), (3, 'cc');
当然,(在并发环境中)它迟早会崩溃,因为这里有明确的竞态条件,但通常它会工作。
这里有一篇关于这个主题的更长的、更全面的文章。
当我来这里的时候,我也在寻找同样的东西,但缺乏通用的“upsert”函数让我有点困扰,所以我认为你可以通过更新和插入sql作为该函数的参数形式手册
它看起来是这样的:
CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
LOOP
-- first try to update
EXECUTE sql_update;
-- check if the row is found
IF FOUND THEN
RETURN;
END IF;
-- not found so insert the row
BEGIN
EXECUTE sql_insert;
RETURN;
EXCEPTION WHEN unique_violation THEN
-- do nothing and loop
END;
END LOOP;
END;
$$;
也许要做你最初想做的事情,批处理“upsert”,你可以使用Tcl分割sql_update并循环各个更新,性能的影响将非常小,参见http://archives.postgresql.org/pgsql-performance/2006-04/msg00557.php
最高的成本是从您的代码执行查询,在数据库方面的执行成本要小得多
在PostgreSQL 9.1中,这可以使用可写的CTE(公共表表达式)来实现:
WITH new_values (id, field1, field2) as (
values
(1, 'A', 'X'),
(2, 'B', 'Y'),
(3, 'C', 'Z')
),
upsert as
(
update mytable m
set field1 = nv.field1,
field2 = nv.field2
FROM new_values nv
WHERE m.id = nv.id
RETURNING m.*
)
INSERT INTO mytable (id, field1, field2)
SELECT id, field1, field2
FROM new_values
WHERE NOT EXISTS (SELECT 1
FROM upsert up
WHERE up.id = new_values.id)
看看这些博客:
通过可写CTE上传
等待9.1 -可写cte
为什么upsert这么复杂?
请注意,此解决方案不能防止唯一键违反,但不容易丢失更新。
请在dba.stackexchange.com上查看Craig Ringer的后续报道
我在管理帐户设置时遇到了与名称值对相同的问题。
设计标准是不同的客户端可以有不同的设置集。
我的解决方案(类似于JWP)是批量擦除和替换,在应用程序中生成合并记录。
这是非常防弹的,平台无关的,因为每个客户端从来没有超过20个设置,这只是3个相当低负载的db调用——可能是最快的方法。
更新单个行的替代方案——检查异常然后插入——或者两者的某种组合是丑陋的代码,缓慢且经常中断,因为(如上所述)非标准SQL异常处理从一个db更改到另一个db——甚至从一个版本更改到另一个版本。
#This is pseudo-code - within the application:
BEGIN TRANSACTION - get transaction lock
SELECT all current name value pairs where id = $id into a hash record
create a merge record from the current and update record
(set intersection where shared keys in new win, and empty values in new are deleted).
DELETE all name value pairs where id = $id
COPY/INSERT merged records
END TRANSACTION