Tei*_*ion 611 sql postgresql upsert sql-merge
几个月前,我从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);
Run Code Online (Sandbox Code Playgroud)
我现在切换到PostgreSQL,显然这是不正确的.它指的是所有正确的表,所以我认为这是使用不同关键字的问题,但我不确定PostgreSQL文档中的哪个被覆盖.
为了澄清,我想插入几个东西,如果它们已经存在则更新它们.
Ste*_*nne 473
从版本9.5开始,PostgreSQL具有UPSERT语法,带有ON CONFLICT子句.使用以下语法(类似于MySQL)
INSERT INTO the_table (id, column_1, column_2)
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE
SET column_1 = excluded.column_1,
column_2 = excluded.column_2;
Run Code Online (Sandbox Code Playgroud)
搜索postgresql的电子邮件组档案以获取"upsert"会导致在手册中找到执行您可能想要执行的操作的示例:
例38-2.UPDATE/INSERT的例外情况
此示例根据需要使用异常处理来执行UPDATE或INSERT:
CREATE TABLE db (a INT PRIMARY KEY, b TEXT);
CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
LOOP
-- first try to update the key
-- note that "a" must be unique
UPDATE db SET b = data WHERE a = key;
IF found THEN
RETURN;
END IF;
-- not there, so try to insert the key
-- if someone else inserts the same key concurrently,
-- we could get a unique-key failure
BEGIN
INSERT INTO db(a,b) VALUES (key, data);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- do nothing, and loop to try the UPDATE again
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');
Run Code Online (Sandbox Code Playgroud)
在黑客邮件列表中,可能有一个如何使用9.1及以上的CTE批量执行此操作的示例:
WITH foos AS (SELECT (UNNEST(%foo[])).*)
updated as (UPDATE foo SET foo.a = foos.a ... RETURNING foo.id)
INSERT INTO foo SELECT foos.* FROM foos LEFT JOIN updated USING(id)
WHERE updated.id IS NULL;
Run Code Online (Sandbox Code Playgroud)
有关更清晰的示例,请参阅a_horse_with_no_name的答案.
bov*_*ine 424
警告:如果同时从多个会话执行,这是不安全的(请参阅下面的警告).
在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);
Run Code Online (Sandbox Code Playgroud)
如果已存在具有"id = 3"的行,则UPDATE将成功,否则它将无效.
仅当"id = 3"的行尚不存在时,INSERT才会成功.
您可以将这两者组合成一个字符串,并使用从应用程序执行的单个SQL语句来运行它们.强烈建议在单个事务中一起运行它们.
这在隔离或在锁定的表上运行时非常有效,但是受竞争条件的影响,这意味着如果同时插入行,它可能仍会因重复键错误而失败,或者可能在并发删除行时终止而没有插入行.SERIALIZABLE
PostgreSQL 9.1或更高版本上的事务将以非常高的序列化失败率为代价可靠地处理它,这意味着您将不得不重试.看看为什么这么复杂,这将更详细地讨论这个案例.
除非应用程序检查受影响的行计数并验证受影响的行,否则此方法也会受到read committed
隔离更新丢失insert
的update
影响.
a_h*_*ame 225
使用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)
Run Code Online (Sandbox Code Playgroud)
查看这些博客条目:
请注意,此解决方案并不能阻止一个唯一键冲突,但它是不容易丢失更新.
请参阅dba.stackexchange.com上的Craig Ringer的跟进
Cra*_*ger 124
在PostgreSQL 9.5及更新版本中你可以使用INSERT ... ON CONFLICT UPDATE
.
请参阅文档.
MySQL INSERT ... ON DUPLICATE KEY UPDATE
可以直接改写为ON CONFLICT UPDATE
.它们都不是SQL标准语法,它们都是特定于数据库的扩展.有很好的理由MERGE
没有用于此,新的语法不是为了好玩而创建的.(MySQL的语法也存在意味着它没有被直接采用的问题).
例如给定设置:
CREATE TABLE tablename (a integer primary key, b integer, c integer);
INSERT INTO tablename (a, b, c) values (1, 2, 3);
Run Code Online (Sandbox Code Playgroud)
MySQL查询:
INSERT INTO tablename (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE c=c+1;
Run Code Online (Sandbox Code Playgroud)
变为:
INSERT INTO tablename (a, b, c) values (1, 2, 10)
ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;
Run Code Online (Sandbox Code Playgroud)
区别:
您必须指定要用于唯一性检查的列名称(或唯一约束名称).那就是ON CONFLICT (columnname) DO
SET
必须使用关键字,就好像这是一个普通的UPDATE
语句
它也有一些不错的功能:
你可以有一个WHERE
对您的条款UPDATE
(让你有效地将ON CONFLICT UPDATE
变成ON CONFLICT IGNORE
为特定值)
建议的插入值可用作行变量EXCLUDED
,其具有与目标表相同的结构.您可以使用表名获取表中的原始值.因此,在这种情况下EXCLUDED.c
将是10
(因为这是我们试图插入),并"table".c
会3
因为这是在表中的当前值.您可以在SET
表达式和WHERE
子句中使用其中一个或两个.
有关upsert的背景信息,请参阅PostgreSQL中的如何UPSERT(MERGE,INSERT ... ON DUPLICATE UPDATE)?
Pau*_*ema 17
当我来到这里的时候我正在找同样的东西,但缺乏一个通用的"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;
$$;
Run Code Online (Sandbox Code Playgroud)
也许要做你最初想要做的事情,批量"upsert",你可以使用Tcl来分割sql_update并循环各个更新,性能命中率将非常小,请参阅http://archives.postgresql.org/pgsql-性能/ 2006-04/msg00557.php
最高成本是从您的代码执行查询,在数据库端执行成本要小得多
小智 13
没有简单的命令可以做到这一点.
最正确的方法是使用函数,就像文档中的函数一样.
另一个解决方案(虽然不安全)是通过返回进行更新,检查哪些行是更新,然后插入其余行
有点像:
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;
Run Code Online (Sandbox Code Playgroud)
假设id:2返回:
insert into table (id, column) values (1, 'aa'), (3, 'cc');
Run Code Online (Sandbox Code Playgroud)
当然,它会迟早(在并发环境中)纾困,因为这里有明显的竞争条件,但通常它会起作用.
Ch'*_*arr 10
就个人而言,我已经设置了一个附加到insert语句的"规则".假设您有一个"dns"表,每个时间记录每个客户的dns点击次数:
CREATE TABLE dns (
"time" timestamp without time zone NOT NULL,
customer_id integer NOT NULL,
hits integer
);
Run Code Online (Sandbox Code Playgroud)
您希望能够重新插入具有更新值的行,或者如果它们不存在则创建它们.键入customer_id和时间.像这样的东西:
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));
Run Code Online (Sandbox Code Playgroud)
更新:如果同时发生插入,则可能会失败,因为它会生成unique_violation异常.但是,未终止的事务将继续并成功,您只需重复已终止的事务.
但是,如果一直有大量的插入事件发生,您将需要在insert语句周围放置一个表锁:SHARE ROW EXCLUSIVE锁定将阻止任何可以在目标表中插入,删除或更新行的操作.但是,不更新唯一键的更新是安全的,因此如果您不执行此操作,请改用咨询锁.
此外,COPY命令不使用RULES,因此如果您使用COPY进行插入,则需要使用触发器.
我自定义上面的"upsert"函数,如果你想INSERT AND REPLACE:
`
CREATE OR REPLACE FUNCTION upsert(sql_insert text, sql_update text)
RETURNS void AS
$BODY$
BEGIN
-- first try to insert and after to update. Note : insert has pk and update not...
EXECUTE sql_insert;
RETURN;
EXCEPTION WHEN unique_violation THEN
EXECUTE sql_update;
IF FOUND THEN
RETURN;
END IF;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION upsert(text, text)
OWNER TO postgres;`
Run Code Online (Sandbox Code Playgroud)
然后执行,执行以下操作:
SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)
Run Code Online (Sandbox Code Playgroud)
重要的是放双美元逗号以避免编译错误
与最喜欢的答案类似,但工作速度稍快:
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)
Run Code Online (Sandbox Code Playgroud)
(来源:http://www.the-art-of-web.com/sql/upsert/)
小智 6
我将帐户设置管理为名称值对时遇到同样的问题.设计标准是不同的客户端可以具有不同的设置集.
我的解决方案,类似于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
Run Code Online (Sandbox Code Playgroud)
对于合并小集合,使用上面的功能很好。但是,如果要合并大量数据,建议您浏览http://mbk.projects.postgresql.org
我知道的当前最佳实践是:
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
Run Code Online (Sandbox Code Playgroud)
UPDATE将返回修改后的行数。如果使用JDBC(Java),则可以将该值与0进行比较,如果没有受影响的行,则改为触发INSERT。如果您使用其他编程语言,也许仍然可以获得修改后的行数,请查看文档。
这可能不那么优雅,但是您可以使用更简单的SQL,而从调用代码中使用这些SQL则更为琐碎。以不同的方式,如果您用PL / PSQL编写十行脚本,则可能仅应单独使用一种或另一种类型的单元测试。
我用这个功能合并
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
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
402248 次 |
最近记录: |