在 ON CONFLICT 子句中使用多个 conflict_target

Use multiple conflict_target in ON CONFLICT clause

我在 table col1col2 中有两列,它们都是唯一索引的(col1 是唯一的,col2 也是唯一的)。

我需要插入此 table,使用 ON CONFLICT 语法并更新其他列,但我不能在 conflict_target 子句中同时使用这两个列。

有效:

INSERT INTO table
...
ON CONFLICT ( col1 ) 
DO UPDATE 
SET 
-- update needed columns here

但是如何对多个列执行此操作,如下所示:

...
ON CONFLICT ( col1, col2 )
DO UPDATE 
SET 
....

在如今(似乎)是不可能的。 ON CONFLICT syntax permits to repeat the clause, nor with CTE 的最后一个版本都不可能:不可能从 ON CONFLICT 中断 INSERT 以添加更多冲突目标。

  1. 创建约束(例如外索引)。

OR/AND

  1. 查看现有约束(psq 中的\d)。
  2. 在 INSERT 子句中使用 ON CONSTRAINT(constraint_name)。

ON CONFLICT 需要唯一索引* 来进行冲突检测。所以你只需要在两列上创建一个唯一索引:

t=# create table t (id integer, a text, b text);
CREATE TABLE
t=# create unique index idx_t_id_a on t (id, a);
CREATE INDEX
t=# insert into t values (1, 'a', 'foo');
INSERT 0 1
t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar';
INSERT 0 1
t=# select * from t;
 id | a |  b  
----+---+-----
  1 | a | bar

* 除了唯一索引,还可以使用exclusion constraints。这些比唯一约束更通用。假设您的 table 有 idvalid_time 的列(并且 valid_timetsrange),并且您希望允许重复的 id,但不适用于重叠的时间段。唯一约束对你没有帮助,但使用排除约束你可以说 "exclude new records if their id equals an old id and also their valid_time overlaps its valid_time."

样本table和数据

CREATE TABLE dupes(col1 int primary key, col2 int, col3 text,
   CONSTRAINT col2_unique UNIQUE (col2)
);

INSERT INTO dupes values(1,1,'a'),(2,2,'b');

重现问题

INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2

我们称之为 Q1。结果是

ERROR:  duplicate key value violates unique constraint "col2_unique"
DETAIL:  Key (col2)=(2) already exists.

documentation 说了什么

conflict_target can perform unique index inference. When performing inference, it consists of one or more index_column_name columns and/or index_expression expressions, and an optional index_predicate. All table_name unique indexes that, without regard to order, contain exactly the conflict_target-specified columns/expressions are inferred (chosen) as arbiter indexes. If an index_predicate is specified, it must, as a further requirement for inference, satisfy arbiter indexes.

这给人的印象是以下查询应该有效,但实际上并没有,因为它实际上需要 col1 和 col2 上的唯一索引。但是,这样的索引不能保证 col1 和 col2 单独是唯一的,这是 OP 的要求之一。

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2

我们将此查询称为 Q2(由于语法错误而失败)

为什么?

Postgresql 之所以这样是因为没有很好地定义当第二列发生冲突时应该发生什么。有多种可能性。例如在上面的Q1查询中,postgresql是否应该在col2上发生冲突时更新col1?但是,如果这导致 col1 上的另一场冲突怎么办? postgresql 如何处理这个问题?

一个解决方案

一种解决方案是将 ON CONFLICT 与 old fashioned UPSERT.

结合使用
CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2;
        IF found THEN
            RETURN;
        END IF;

        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently, or key2
        -- already exists in col2,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data;
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            BEGIN
                INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data;
                RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- Do nothing, and loop to try the UPDATE again.
            END;
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

您需要修改此存储函数的逻辑,以便它完全按照您希望的方式更新列。像

一样调用它
SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');

Vlad 的想法是正确的。

首先,您必须在列上创建一个 table 唯一约束 col1, col2 然后,一旦您这样做了,您就可以执行以下操作:

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT ON CONSTRAINT dupes_pkey 
DO UPDATE SET col3 = 'c', col2 = 2

如果你使用的是 postgres 9.5,你可以使用 EXCLUDED space.

示例取自 What's new in PostgreSQL 9.5

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

您通常(我认为)可以生成一个仅包含一个 on conflict 的语句,该语句为您要插入的内容指定一个且唯一相关的约束。

因为通常一次只有一个约束是 "relevant" 一个。 (如果很多,那么我想知道是否有什么东西很奇怪/设计得很奇怪,嗯。)

示例:
(许可证:不是 CC0,仅 CC-By)

// there're these unique constraints:
//   unique (site_id, people_id, page_id)
//   unique (site_id, people_id, pages_in_whole_site)
//   unique (site_id, people_id, pages_in_category_id)
// and only *one* of page-id, category-id, whole-site-true/false
// can be specified. So only one constraint is "active", at a time.

val thingColumnName = thingColumnName(notfificationPreference)

val insertStatement = s"""
  insert into page_notf_prefs (
    site_id,
    people_id,
    notf_level,
    page_id,
    pages_in_whole_site,
    pages_in_category_id)
  values (?, ?, ?, ?, ?, ?)
  -- There can be only one on-conflict clause.
  on conflict (site_id, people_id, $thingColumnName)   <—— look
  do update set
    notf_level = excluded.notf_level
  """

val values = List(
  siteId.asAnyRef,
  notfPref.peopleId.asAnyRef,
  notfPref.notfLevel.toInt.asAnyRef,
  // Only one of these is non-null:
  notfPref.pageId.orNullVarchar,
  if (notfPref.wholeSite) true.asAnyRef else NullBoolean,
  notfPref.pagesInCategoryId.orNullInt)

runUpdateSingleRow(insertStatement, values)

并且:

private def thingColumnName(notfPref: PageNotfPref): String =
  if (notfPref.pageId.isDefined)
    "page_id"
  else if (notfPref.pagesInCategoryId.isDefined)
    "pages_in_category_id"
  else if (notfPref.wholeSite)
    "pages_in_whole_site"
  else
    die("TyE2ABK057")

on conflict 子句是动态生成的,具体取决于我要执行的操作。如果我为页面插入通知首选项 — 那么在 site_id, people_id, page_id 约束上可能存在唯一冲突。如果我正在为一个类别配置通知首选项——那么我知道可能违反的约束是 site_id, people_id, category_id.

所以我可以,很可能你也可以,在你的情况下?生成正确的 on conflict (... columns ),因为我知道我 想要 做什么,然后我知道许多独特约束中的哪一个是可能被违反的。

有点老套,但我通过将 col1 和 col2 的两个值连接到一个新列 col3(有点像两者的索引)并与之进行比较来解决这个问题。这仅在您需要它同时匹配 col1 和 col2 时才有效。

INSERT INTO table
...
ON CONFLICT ( col3 ) 
DO UPDATE 
SET 
-- update needed columns here

其中 col3 = col1 和 col2 值的串联。

ON CONFLICT 是非常笨拙的解决方案,运行

UPDATE dupes SET key1=, key2= where key3=    
if rowcount > 0    
  INSERT dupes (key1, key2, key3) values (,,);

适用于 Oracle、Postgres 和所有其他数据库

我知道我参加聚会迟到了,但对于寻找答案的人,我发现了这个: here

INSERT INTO tbl_Employee 
VALUES (6,'Noor')
ON CONFLICT (EmpID,EmpName)
DO NOTHING;
ON CONFLICT ( col1, col2 )
DO UPDATE 
SET 

工作正常。但是你不应该更新 col1, col2 SET 部分。