如何使用postgresql模拟“插入忽略”和“重复键更新”(SQL合并)?


140

某些SQL Server的功能INSERT如果违反主/唯一键约束,则会跳过该功能。例如,MySQL具有INSERT IGNORE

什么是模仿的最好方式INSERT IGNORE,并ON DUPLICATE KEY UPDATE与PostgreSQL的?




6
从9.5开始,它本身就是可能的:stackoverflow.com/a/34639631/4418
沃伦,2016年

模拟MySQL:ON DUPLICATE KEY UPDATE在PgSQL 9.5上仍然是不可能的,因为ON CLAUSE等效的PgSQL 需要您提供约束名称,而MySQL可以捕获任何约束而无需定义它。这样可以防止我“模拟”此功能而无需重写查询。
NeverEndingQueue '18

Answers:


35

尝试进行更新。如果它不修改任何表示该行不存在的行,则执行插入。显然,您是在事务内部执行此操作的。

当然,如果您不想将多余的代码放在客户端,则可以将其包装在一个函数中。您还需要一个循环,以解决那种非常罕见的比赛条件。

文档中有一个示例:http : //www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html,示例40-2位于底部。

这通常是最简单的方法。您可以使用规则做一些魔术,但可能会变得更加混乱。我建议在这一天中使用函数内包装方法。

这适用于单行或几行值。如果您要处理例如来自子查询的大量行,则最好将其拆分为两个查询,一个用于INSERT,一个用于UPDATE(当然,作为适当的联接/子选择-无需编写主行过滤两次)


4
“如果您要处理大量行”,这正是我的情况。我想批量更新/插入行,并使用mysql,我可以只使用一个查询而无需任何循环。现在,我想知道postgresql是否也可以:仅使用一个查询批量更新或插入。您说:“最好将其分为两个查询,一个查询用于INSERT,一个查询用于UPDATE”,但是我该如何做一个不会在重复键上引发错误的插入?(即“ INSERT IGNORE”)
gpilotino,2009年

4
Magnus意味着您使用这样的查询:“开始事务;创建临时表temporary_table,将其作为select * from test的地方为false;从'data_file.csv'复制temporary_table;锁定表的测试;从temporary_table中更新测试集的数据= temporary_table.data test.id = temporary_table.id;插入到测试中,从临时表中选择* *,其中id不在(从测试中选择ID)为“”
Tometzky 2009年

25
更新:在PostgreSQL 9.5中,现在变得简单INSERT ... ON CONFLICT DO NOTHING;。另请参阅答案stackoverflow.com/a/34639631/2091700
Alphaaa

重要的MERGE是,除非您是第一个,否则SQL标准不是并发安全的ups LOCK TABLE。人们以这种方式使用它,但这是错误的。
Craig Ringer's

1
有了v9.5,它现在已是“本机”功能,因此请检查@Alphaaa的评论(只是在广告中刊登广告以宣传答案)
Camilo Delvasto

178

在PostgreSQL 9.5中,这现在是本机功能(就像MySQL已经使用了几年):

插入...冲突时不做/更新(“ UPSERT”)

9.5带来了对“ UPSERT”操作的支持。INSERT被扩展为接受ON CONFLICT DO UPDATE / IGNORE子句。本节指定了在可能重复的违规情况下要采取的替代措施。

...

新语法的进一步示例:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;

100

编辑:万一您错过了沃伦的答案,PG9.5现在就具有此功能;是时候升级了!


在Bill Karwin的答案的基础上,阐明基于规则的方法的外观(从同一DB中的另一个模式进行转移,并使用多列主键):

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

注意:该规则适用于所有INSERT操作,直到删除该规则为止,因此不是特别的。


@sema您的意思是如果another_schema.my_table根据的约束包含重复项my_table
EoghanM 2014年

2
@EoghanM我在PostgreSQL 9.3中测试了该规则,仍然可以使用多个行插入语句插入重复项,例如INSERT INTO“ my_table”(a,b),(a,b); (假设(a,b)行在“ my_table”中尚不存在。)
sema 2014年

@sema,陷阱-必须表示该规则从一开始就对所有要插入的数据执行,并且在插入每一行后不会重新执行。一种方法是先将您的数据插入没有任何约束的另一个临时表中,然后再进行INSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
EoghanM 2014年

@EoghanM另一种方法是暂时放宽重复约束,并接受插入时的重复,但之后用DELETE FROM my_table WHERE ctid IN (SELECT ctid FROM (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY pk_col_1,pk_col_2) AS rn FROM my_table) AS dups WHERE dups.rn > 1);
sema

我遇到了@sema描述的问题。如果我执行插入(a,b),(a,b),则会引发错误。在这种情况下,有没有办法抑制错误?
Diogo Melo 2014年

35

对于拥有Postgres 9.5或更高版本的人,新的ON CONFLICT DO NOTHING语法应该可以工作:

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

对于拥有较早版本的我们来说,这种正确的连接将起作用:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;

在并发环境中进行大插入时,第二种方法不起作用。你得到一个Unique violation: 7 ERROR: duplicate key value violates unique constrainttarget_table了另一行插入到它,而正在执行此查询,如果他们的钥匙,的确,相互重复。我相信锁定target_table会有所帮助,但并发显然会受到影响。
G. Kashtanov '18

1
ON CONFLICT (field_one) DO NOTHING是答案的最好部分。
Abel Callejo

24

要获得插入忽略逻辑,您可以执行以下操作。我发现仅从文字值的select语句中插入效果最佳,然后可以使用NOT EXISTS子句掩盖重复的键。为了获得有关重复逻辑的更新,我怀疑有必要使用pl / pgsql循环。

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)

如果tmp包含重复行怎么办?
Henley Chiu

您始终可以使用与众不同的关键字进行选择。
Keyo

5
与FYI一样,“ WHERE NOT EXISTS”技巧在多个事务中也不起作用,因为不同的事务无法看到其他事务中新添加的数据。
戴夫·约翰森

21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')

都试图做同一件事的多个事务有什么影响?在不存在的地方执行与执行其他事务的插入之间是否可能插入一行?如果Postgres可以防止这种情况发生,那么Postgres难道不是在所有交易达成时就在所有这些交易中引入了一个同步点吗?
Καrτhικ

这不适用于多个事务,因为新添加的数据对其他事务不可见。
戴夫·约翰森

12

看起来PostgreSQL支持称为规则的架构对象。

http://www.postgresql.org/docs/current/static/rules-update.html

您可以ON INSERT为给定的表创建规则,NOTHING如果存在具有给定主键值的行,则执行该规则,否则使该规则执行UPDATE而不是INSERT如果行具有给定主键值存在。

我自己还没有尝试过,所以我不能凭经验说话或提供示例。


1
如果我理解得很好,这些规则是每次调用语句时都会执行的触发器。如果我只想对一个查询应用规则怎么办?我必须创建规则然后立即销毁它?(关于比赛条件如何?)
gpilotino

3
是的,我也有同样的问题。规则机制是我在PostgreSQL中可以找到的最接近MySQL的INSERT IGNORE或ON DUPLICATE KEY UPDATE的东西。如果我们用Google搜索“重复密钥更新后的PostgreSQL”,您会发现其他人推荐使用Rule机制,即使Rule不仅适用于临时性,也适用于任何INSERT。
Bill Karwin 09年

4
PostgreSQL支持事务性DDL,这意味着,如果您创建规则并将其放在单个事务中,则该规则在该事务之外永远是不可见的(因此,在该事务之外也不会产生任何影响)。
cdhowie 2015年

6

正如@hanmari在他的评论中提到的。当插入到postgres表中时,on冲突(..)什么也不做是不插入重复数据的最佳代码。

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

代码的ON CONFLICT行将允许insert语句仍然插入数据行。查询和值代码是将日期从Excel插入到postgres db表中的示例。我将约束添加到postgres表中,以确保ID字段唯一。我没有对相同的数据行运行删除操作,而是添加了一行SQL代码,该行对从1开始的ID列进行了重新编号。示例:

q = 'ALTER id_column serial RESTART WITH 1'

如果我的数据有一个ID字段,则我不将其用作主要ID /序列ID,而是创建一个ID列,并将其设置为Serial。我希望这些信息对每个人都有帮助。*我没有软件开发/编码的大学学位。我在编码方面所知道的一切,我都自己学习。


这不适用于复合唯一索引!
Nulik

4

此解决方案避免使用规则:

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

但是它具有性能缺陷(请参阅PostgreSQL.org):

包含EXCEPTION子句的块的进入和退出比没有一个块的块要昂贵得多。因此,请勿在不需要的情况下使用EXCEPTION。


1

批量存储时,您始终可以删除插入之前的行。删除不存在的行不会导致错误,因此可以安全地跳过它。


2
这种方法很容易出现奇怪的比赛条件,我不建议这样做……
Steven Schlansker

1
+1这是简单而通用的。如果谨慎使用,实际上可能是一个简单的解决方案。
Wouter van Nifterick 2012年

1
当在插入后更改现有数据(但不在重复键上)并且我们希望保留更新时,它也将不起作用。当为多个稍微不同的系统编写SQL脚本(例如在生产,QA,开发和测试系统上运行的数据库更新)编写SQL脚本时就是这种情况。
Hanno Fietz

1
如果使用DEFERRABLE INITIALLY DEFERRED标志创建外键,则可以毫无问题。
temoto

-1

对于数据导入脚本,以某种方式代替“ IF NOT EXISTS”,尽管如此,还是有一个稍微尴尬的公式可以起作用:

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.