在ON CONFLICT子句中使用多个conflict_target

时间:2016-03-09 09:46:21

标签: postgresql upsert postgresql-9.5

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

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

有效:

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

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

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

11 个答案:

答案 0 :(得分:42)

id需要唯一索引*来执行冲突检测。因此,您只需要在两列上创建唯一索引:

valid_time

*除了唯一索引外,您还可以使用docs。这些比唯一约束更通用。假设您的表格包含valid_timetsrange的列(并且idid),并且您希望允许重复的id,但不能重叠时间段。唯一约束不会对您有所帮助,但如果排除约束,您可以说如果valid_time等于旧valid_time并且其<video>重叠,则排除新记录它的<video>。&#34;

答案 1 :(得分:28)

样本表和数据

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可以执行唯一索引推断。表演时   推理,它由一个或多个index_column_name列和/或组成   index_expression表达式和可选的index_predicate。所有   table_name唯一索引,不考虑顺序,包含   确切地推断出conflict_target指定的列/表达式   (选择)作为仲裁指标。如果指定了index_predicate,则为   作为推断的进一步要求,必须满足仲裁指标。

这给人的印象是以下查询应该有效,但它没有,因为它实际上需要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查询中,当col1发生冲突时,postgresql应该更新col2吗?但如果这会导致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');

答案 2 :(得分:4)

现在(似乎)不可能。 ON CONFLICT syntax的最后一个版本都不允许重复该条款,也不允许使用CTE:不可能从ON CONFLICT中删除INSERT以添加更多冲突目标。< / p>

答案 3 :(得分:3)

  1. 创建约束(例如,外部索引)。
  2. OR / AND

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

答案 4 :(得分:2)

弗拉德有了正确的想法。

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

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

答案 5 :(得分:2)

如果您使用的是postgres 9.5,则可以使用EXCLUDED空格。

取自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;

答案 6 :(得分:0)

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

通常,一次仅一个约束是“相关”约束。 (如果有很多,那么我想知道是否有些奇怪/设计怪异的东西,嗯。)

示例:
(许可证:不是 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 ),因为我知道我想做的事,然后知道哪一个?众多独特约束中的一个就是可以被违反的约束。

答案 7 :(得分:0)

有点骇人听闻,但我通过将col1和col2中的两个值连接到新列col3(类似于两者的索引)中来进行解决,并与之进行了比较。仅当您需要它同时匹配col1和col2时才有效。

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

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

答案 8 :(得分:0)

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

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

可在Oracle,Postgres和所有其他数据库上运行

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

ON CONFLICT ( col1, col2 )
DO UPDATE 
SET 

工作正常。但您不应更新 col1, col2SET 部分。