关于数据库:如何使用postgresql模拟“插入忽略”和“重复密钥更新”(sql merge)?

how to emulate “insert ignore” and “on duplicate key update” (sql merge) with postgresql?

某些SQL服务器具有一个功能,如果它违反主/唯一键约束,则跳过INSERT。 例如,MySQL有INSERT IGNORE

使用PostgreSQL模拟INSERT IGNOREON DUPLICATE KEY UPDATE的最佳方法是什么?


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

INSERT ... ON CONFLICT DO NOTHING/UPDATE ("UPSERT")

9.5 brings support for"UPSERT" operations.
INSERT is extended to accept an ON CONFLICT DO UPDATE/IGNORE clause. This clause specifies an alternative action to take in the event of a would-be duplicate violation.

...

Further example of new syntax:

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


编辑:如果你错过了沃伦的回答,那么PG9.5现在已经有了这个原因;时间升级!

在Bill Karwin的回答的基础上,阐明基于规则的方法将是什么样的(从同一个数据库中的另一个模式转移,并使用多列主键):

1
2
3
4
5
6
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操作,直到删除规则,因此不是特别的。


尝试做一个更新。如果它没有修改任何意味着它不存在的行,那么插入也是如此。显然,您在事务中执行此操作。

如果您不想在客户端添加额外的代码,您当然可以将其包装在一个函数中。在这种想法中,你还需要一个非常罕见的竞争条件的循环。

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

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

这适用于单行或几行值。如果你正在处理大量的行,例如来自子查询,你最好把它分成两个查询,一个用于INSERT,一个用于UPDATE(当然是一个适当的连接/子选择 - 不需要写你的主要过滤两次)


对于那些拥有Postgres 9.5或更高版本的人来说,新的ON CONFLICT DO NOTHING语法应该有效:

1
2
3
4
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;

对于我们这些拥有早期版本的人来说,这种正确的联接将起作用:

1
2
3
4
5
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;


要获得插入忽略逻辑,您可以执行以下操作。我发现只是从文字值的select语句插入效果最好,然后你可以用NOT EXISTS子句掩盖重复的键。为了获得重复逻辑的更新,我怀疑需要一个pl / pgsql循环。

1
2
3
4
5
6
7
8
9
10
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)
)


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


看起来PostgreSQL支持称为规则的模式对象。

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

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

我自己没试过,所以我不能从经验中说话或提供一个例子。


正如@hanmari在评论中提到的那样。当插入postgres表时,on conflict(..)什么都不做是用于不插入重复数据的最佳代码:

1
2
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列。
例:

1
q = 'ALTER id_column serial RESTART WITH 1'

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


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

1
2
3
4
5
6
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):

A block containing an EXCEPTION clause is significantly more expensive
to enter and exit than a block without one. Therefore, don't use
EXCEPTION without need.


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


对于数据导入脚本,要取代"IF NOT EXISTS",在某种程度上,有一个稍微尴尬的配方仍然有效:

1
2
3
4
5
6
7
8
9
10
11
DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;