在PostgreSQL中重复更新时插入?
几个月前,我从一个关于堆栈溢出的回答中学到了如何使用以下语法在MySQL中一次执行多个更新:在PostgreSQL中重复更新时插入?,sql,postgresql,upsert,sql-merge,Sql,Postgresql,Upsert,Sql Merge,几个月前,我从一个关于堆栈溢出的回答中学到了如何使用以下语法在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,显然这是不正确的。它指的是所有正确的表,所以我认为这是使用不同关键字的问题,但我不确定
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文档中在哪里介绍了这一点
为了澄清,我想插入一些内容,如果它们已经存在,则更新它们。根据,不支持在重复键上处理
案例。这部分语法是一个专有的MySQL扩展。没有简单的命令来执行
最正确的方法是使用函数,就像来自
另一个解决方案(虽然不是那么安全)是通过返回进行更新,检查哪些行是更新,然后插入其余的行
大致如下:
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');
当然,它迟早会退出(在并发环境中),因为这里有明确的竞争条件,但通常它会工作
这里有一个。PostgreSQL,因为9.5版有语法,带有子句。具有以下语法(类似于MySQL)
在postgresql的电子邮件组存档中搜索“upsert”可以找到:
示例38-2。更新/插入的例外情况
此示例使用异常处理执行更新或插入(视情况而定):
可能有一个示例说明了如何使用9.1及以上版本中的CTE批量执行此操作:
有关更清晰的示例,请参见。有关合并小集合的信息,可以使用上述函数。但是,如果您要合并大量数据,我建议您
据我所知,目前的最佳实践是:
将新的/更新的数据复制到临时表中(当然,如果成本合适,也可以进行插入)
获取锁[可选](建议优先于表锁,IMO)
合并。(有趣的部分)
当我来到这里的时候,我也在寻找同样的东西,但是缺少一个通用的“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;
$$;
CREATE RULE replace_dns AS
ON INSERT TO dns
WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time")
AND (dns.customer_id = new.customer_id))))
DO INSTEAD UPDATE dns
SET hits = new.hits
WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));
也许要做您最初想要做的事情,批处理“upsert”,您可以使用Tcl拆分sql_更新并循环单个更新,性能影响将非常小,请参见
最高的成本是从代码中执行查询,在数据库端,执行成本要小得多。警告:如果同时从多个会话执行,这是不安全的(请参阅下面的注意事项)
在postgresql中执行“UPSERT”的另一个聪明方法是执行两个连续的UPDATE/INSERT语句,每个语句都设计为成功或无效
UPDATE table SET field='C', field2='Z' WHERE id=3;
INSERT INTO table (id, field, field2)
SELECT 3, 'C', 'Z'
WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);
如果已经存在“id=3”的行,则更新将成功,否则将无效
UPDATE table SET field='C', field2='Z' WHERE id=3;
INSERT INTO table (id, field, field2)
SELECT 3, 'C', 'Z'
WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);
只有当“id=3”的行不存在时,插入才会成功
您可以将这两个字符串组合成一个字符串,并使用从应用程序执行的单个SQL语句来运行它们。强烈建议在单个事务中同时运行它们
这在隔离运行或在锁定表上运行时效果很好,但会受到竞争条件的影响,这意味着如果同时插入一行,它仍可能失败并出现重复键错误,或者在同时删除一行时,它可能终止而不插入任何行。PostgreSQL 9.1或更高版本上的SERIALIZABLE
事务将以极高的序列化失败率为代价可靠地处理该事务,这意味着您必须多次重试。请参阅,其中更详细地讨论了此案例
如果要插入和替换以下内容,此方法也是。I上面的自定义“upsert”函数:
`
在执行之后,执行以下操作:
SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)
放置双美元逗号以避免编译器错误很重要
- 检查速度李>
我在管理帐户设置时遇到了与名称-值对相同的问题。
设计标准是不同的客户端可以有不同的设置集
我的解决方案类似于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
编辑:这不符合预期。与公认的答案不同,当两个进程同时重复调用upsert\u foo
时,这会产生唯一的密钥冲突
尤里卡!我在一个查询中找到了一种方法:使用UPDATE。。。返回
以测试是否有任何行受到影响:
CREATE TABLE foo (k INT PRIMARY KEY, v TEXT);
CREATE FUNCTION update_foo(k INT, v TEXT)
RETURNS SETOF INT AS $$
UPDATE foo SET v = $2 WHERE k = $1 RETURNING $1
$$ LANGUAGE sql;
CREATE FUNCTION upsert_foo(k INT, v TEXT)
RETURNS VOID AS $$
INSERT INTO foo
SELECT $1, $2
WHERE NOT EXISTS (SELECT update_foo($1, $2))
$$ LANGUAGE sql;
更新必须在单独的过程中完成,因为不幸的是,这是一个语法错误:
... WHERE NOT EXISTS (UPDATE ...)
现在,它可以根据需要工作:
SELECT upsert_foo(1, 'hi');
SELECT upsert_foo(1, 'bye');
SELECT upsert_foo(3, 'hi');
SELECT upsert_foo(3, 'bye');
对于PostgreSQL 9.1,这可以通过使用可写CTE()实现:
请参阅以下博客:
请注意,此解决方案不会防止唯一密钥冲突,但不会受到丢失更新的攻击。
请参见本人,我已在insert语句后面设置了一条“规则”。假设您有一个“dns”表,记录每个客户每次的dns点击次数:
CREATE TABLE dns (
"time" timestamp without time zone NOT NULL,
customer_id integer NOT NULL,
hits integer
);
您希望能够使用更新的值重新插入行,或者在行不存在时创建它们。键入客户id和时间。大概是这样的:
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;
$$;
CREATE RULE replace_dns AS
ON INSERT TO dns
WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time")
AND (dns.customer_id = new.customer_id))))
DO INSTEAD UPDATE dns
SET hits = new.hits
WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));
最新消息:这具有强大的
CREATE TABLE dns (
"time" timestamp without time zone NOT NULL,
customer_id integer NOT NULL,
hits integer
);
CREATE RULE replace_dns AS
ON INSERT TO dns
WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time")
AND (dns.customer_id = new.customer_id))))
DO INSTEAD UPDATE dns
SET hits = new.hits
WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));
CREATE OR REPLACE FUNCTION save_user(_id integer, _name character varying)
RETURNS boolean AS
$BODY$
BEGIN
UPDATE users SET name = _name WHERE id = _id;
IF FOUND THEN
RETURN true;
END IF;
BEGIN
INSERT INTO users (id, name) VALUES (_id, _name);
EXCEPTION WHEN OTHERS THEN
UPDATE users SET name = _name WHERE id = _id;
END;
RETURN TRUE;
END;
$BODY$
LANGUAGE plpgsql VOLATILE STRICT
WITH upsert AS (UPDATE spider_count SET tally=1 WHERE date='today' RETURNING *)
INSERT INTO spider_count (spider, tally) SELECT 'Googlebot', 1 WHERE NOT EXISTS (SELECT * FROM upsert)
CREATE OR REPLACE FUNCTION merge_tabla(key INT, data TEXT)
RETURNS void AS
$BODY$
BEGIN
IF EXISTS(SELECT a FROM tabla WHERE a = key)
THEN
UPDATE tabla SET b = data WHERE a = key;
RETURN;
ELSE
INSERT INTO tabla(a,b) VALUES (key, data);
RETURN;
END IF;
END;
$BODY$
LANGUAGE plpgsql
CREATE TABLE tablename (a integer primary key, b integer, c integer);
INSERT INTO tablename (a, b, c) values (1, 2, 3);
INSERT INTO tablename (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE c=c+1;
INSERT INTO tablename (a, b, c) values (1, 2, 10)
ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;