Upsert 错误(On Conflict Do Update)指向重复的约束值

Upsert error (On Conflict Do Update) pointing to duplicate constrained values

当我尝试在 FROM 语句中使用多个来源时,我在 Postgres 9.5 中遇到 ON CONFLICT DO UPDATE 问题。

工作代码示例:

    INSERT INTO new.bookmonographs  (citavi_id, abstract, createdon, edition, title, year)
SELECT "ID", "Abstract", "CreatedOn"::timestamp, "Edition", "Title", "Year"
FROM old."Reference"
WHERE old."Reference"."ReferenceType" = 'Book'
    AND old."Reference"."Year" IS NOT NULL
    AND old."Reference"."Title" IS NOT NULL
ON CONFLICT (citavi_id) DO UPDATE 
    SET (abstract, createdon, edition, title, year) = (excluded.abstract, excluded.createdon, excluded.edition, excluded.title, excluded.year)
; 

故障代码:

    INSERT INTO new.bookmonographs  (citavi_id, abstract, createdon, edition, title, year)
SELECT "ID", "Abstract", "CreatedOn"::timestamp, "Edition", "Title", "Year"
FROM old."Reference", old."ReferenceAuthor"
WHERE old."Reference"."ReferenceType" = 'Book'
    AND old."Reference"."Year" IS NOT NULL
    AND old."Reference"."Title" IS NOT NULL
    AND old."ReferenceAuthor"."ReferenceID" = old."Reference"."ID"
    --Year, Title and Author must be present in the data, otherwise the entry is deemed useless, hence won't be included
ON CONFLICT (citavi_id) DO UPDATE 
    SET (abstract, createdon, edition, title, year) = (excluded.abstract, excluded.createdon, excluded.edition, excluded.title, excluded.year)
; 

我在 FROM 语句和一个 WHERE 语句中添加了一个额外的源,以确保只有具有标题、年份和作者的条目被插入到新数据库中。 (如果 old."Reference"."ID" 存在于 old."ReferenceAuthor" 作为 "ReferenceID",则作者存在。)即使没有附加的 WHERE 语句,查询也是错误的。我在 SELECT 中指定的列仅存在于 old."Reference" 中,而不存在于 old."ReferenceAuthor" 中。 当前 old."ReferenceAuthor"old."Reference" 没有 UNIQUE CONSTRAINT,专着的唯一约束是:

CONSTRAINT bookmonographs_pk PRIMARY KEY (bookmonographsid),
CONSTRAINT bookmonographs_bookseries FOREIGN KEY (bookseriesid)
      REFERENCES new.bookseries (bookseriesid) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT bookmonographs_citaviid_unique UNIQUE (citavi_id)

PSQL 抛出的错误:

ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values. ********** Error **********

ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time SQL state: 21000 Hint: Ensure that no rows proposed for insertion within the same command have duplicate constrained values.

我不知道出了什么问题,也不知道为什么提示指向重复的约束值。

使用显式 INNER JOIN 将两个源表连接在一起:

INSERT INTO new.bookmonographs  (citavi_id, abstract, createdon, edition, title, year)
SELECT "ID", "Abstract", "CreatedOn"::timestamp, "Edition", "Title", "Year"
FROM old."Reference"
INNER JOIN old."ReferenceAuthor"                                       -- explicit join
    ON old."ReferenceAuthor"."ReferenceID" = old."Reference"."ID"      -- ON condition
WHERE old."Reference"."ReferenceType" = 'Book' AND
      old."Reference"."Year" IS NOT NULL       AND
      old."Reference"."Title" IS NOT NULL
ON CONFLICT (citavi_id) DO UPDATE 
SET (abstract, createdon, edition, title, year) =
    (excluded.abstract, excluded.createdon, excluded.edition, excluded.title,
     excluded.year)

问题是由于某些条目显然有多个作者而引起的。因此,您编写的 select 查询中的内部联接将为同一条目 return 多行,而 INSERT ... ON CONFLICT 不喜欢这样。由于您仅使用 ReferenceAuthor table 进行过滤,因此您可以简单地重写查询,以便它使用 table 通过执行 [= 来仅过滤没有任何作者的条目13=] 在相关子查询上。方法如下:

INSERT INTO new.bookmonographs  (citavi_id, abstract, createdon, edition, title, year)
SELECT "ID", "Abstract", "CreatedOn"::timestamp, "Edition", "Title", "Year"
FROM old."Reference"
WHERE old."Reference"."ReferenceType" = 'Book'
    AND old."Reference"."Year" IS NOT NULL
    AND old."Reference"."Title" IS NOT NULL
    AND exists(SELECT FROM old."ReferenceAuthor" WHERE old."ReferenceAuthor"."ReferenceID" = old."Reference"."ID")
    --Year, Title and Author must be present in the data, otherwise the entry is deemed useless, hence won't be included
ON CONFLICT (citavi_id) DO UPDATE 
    SET (abstract, createdon, edition, title, year) = (excluded.abstract, excluded.createdon, excluded.edition, excluded.title, excluded.year)
; 

postgres' docsctrl + f"Cardinality violation" errors in detail,因为没有直接的 link)对这个问题有很好的解释。

引用文档:

The idea of raising "cardinality violation" errors is to ensure that any one row is affected no more than once per statement executed. In the lexicon of the SQL standard's discussion of SQL MERGE, the SQL statement is "deterministic". The user ought to be confident that a row will not be affected more than once - if that isn't the case, then it isn't predictable what the final value of a row affected multiple times will be.

重现他们更简单的例子,在 table upsert 上,下面的查询无法工作,因为我们无法可靠地知道 select val from upsert where key = 1 是否等于 'Foo'或 'Bar':

 INSERT INTO upsert(key, val)
   VALUES(1, 'Foo'), (1, 'Bar')
   ON CONFLICT (key) UPDATE SET val = EXCLUDED.val;
 ERROR:  21000: ON CONFLICT UPDATE command could not lock/update self-inserted tuple
 HINT:  Ensure that no rows proposed for insertion within the same command have duplicate constrained values.