关于sql:插入多行时重复项会发生什么?

What happens with duplicates when inserting multiple rows?

我正在运行一个python脚本,它将大量数据插入Postgres数据库,我使用一个查询来执行多行插入:

1
INSERT INTO TABLE (col1,col2) VALUES ('v1','v2'),('v3','v4') ... etc

我想知道如果它碰到插入的重复键会发生什么。它会停止整个查询并引发异常吗?或者它只是忽略了特定行的插入并继续?


INSERT将只插入所有行,不会发生任何特殊情况,除非您有某种约束禁止重复/重叠值(PRIMARY KEYUNIQUECHECKEXCLUDE约束),您在问题中没有提到。但这可能是你担心的。

假设UNIQUE或pk对(col1,col2)的约束,您将处理教科书UPSERT的情况。这里有许多相关的问题和答案。

通常,如果违反了任何约束,则会引发一个异常(除非被困在过程服务器端语言(如plpgsql)中),该异常不仅会回滚语句,还会回滚整个事务。

没有并发写入

即:没有其他事务会试图同时写入同一个表。

  • 排除使用WHERE NOT EXISTS ...或任何其他适用技术的表中已有的行:

    • 选择其他表中不存在的行
  • 也不要忘记删除插入集内的重复项,半反连接WHERE NOT EXISTS ...不会排除这些重复项。

一种同时处理这两个问题的方法是EXCEPT

1
2
3
4
5
6
INSERT INTO tbl (col1, col2)
VALUES
  (text 'v1', text 'v2')  -- explicit type cast may be needed in 1st row
, ('v3', 'v4')
, ('v3', 'v4')  -- beware of dupes in source
EXCEPT SELECT col1, col2 FROM tbl;

没有关键字ALLEXCEPT在源中折叠重复的行。如果你知道没有重复,EXCEPT ALL或其他技术之一将更快。相关:

  • 在PostgreSQL中使用except子句

一般来说,如果目标表很大,那么源上的WHERE NOT EXISTSDISTINCT组合可能会更快:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
INSERT INTO tbl (col1, col2)
SELECT *
FROM  (
   SELECT DISTINCT *
   FROM  (
       VALUES
         (text 'v1', text'v2')
       , ('v3', 'v4')
       , ('v3', 'v4')  -- dupes in source
      ) t(c1, c2)
   ) t
WHERE NOT EXISTS (
   SELECT 1
   FROM   tbl
   WHERE  col1 = t.c1 AND col2 = t.c2
   );

如果有很多重复的话,首先将它们折叠到源代码中是值得的。否则就少用一个子查询。

相关:

  • 选择其他表中不存在的行

同时写入

在Postgres 9.5或更高版本中使用Postgres UPSERT实现INSERT ... ON CONFLICT ...

1
2
3
4
5
6
7
8
9
INSERT INTO tbl (col1,col2)
SELECT DISTINCT *  -- still can't insert the same row more than once
FROM  (
   VALUES
     (text 'v1', text 'v2')
   , ('v3','v4')
   , ('v3','v4')  -- you still need to fold dupes in source!
  ) t(c1, c2)
ON CONFLICT DO NOTHING;  -- ignores rows with *any* conflict!

更详细的相关答案:

  • 如何在PostgreSQL中使用返回with on conflict?
  • 如何插入包含外键的行?

文档:

  • 手册
  • 提交页面
  • Postgres wiki页面

克雷格对UPSERT问题的参考答案:

  • 如何向上插入(合并、插入…在PostgreSQL中?

它会停止整个查询并引发异常吗?对。

为了避免这种情况,您可以在这里查看下面的so问题,它描述了如何避免Postgres在DB上已经存在一些插入的键时对多个插入抛出错误。

你基本上应该这样做:

1
2
3
4
5
6
7
INSERT INTO DBtable
        (id, field1)
    SELECT 1, 'value'
    WHERE
        NOT EXISTS (
            SELECT id FROM DBtable WHERE id = 1
);