在ON CONFLICT子句中使用多个flict_target


93

我在表中两列col1col2他们都是独一无二的索引(COL1是唯一的,因此是COL2)。

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

有用:

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

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

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

4
“ col1,col2,它们都具有唯一索引。” 这意味着col1是唯一的,而col2是唯一的,还是col1,col2的组合是唯一的?
e4c5

1
这是否意味着col1是唯一的,而col2是唯一的
Oto Shavadze

Answers:


48

样本表和数据

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.

文档说什么

flict_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当出现冲突时,应该更新postgresqlcol2吗?但是,如果那导致另一场冲突col1呢?PostgreSQL应该如何处理呢?

一个办法

解决方案是将ON CONFLICT与老式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');

3
这是可行的方法,但工作/逻辑比必要的要多,您要做的实际上是在两列上创建唯一约束。请参阅下面的答案。
Jubair

如果一次插入多组VALUES,也可以使用merge_db解决方案吗?
daniyel

@daniyel,您将必须重写存储的功能
e4c5

3
对我来说,尚不清楚建议使用老式的ups有什么用-这个问题被“ postgres upsert 9.5”很好地引用,并且可以通过解释如何在所有constraint_names选项中使用它来更好。
2014年

3
@Pak您不清楚,因为您没有清楚地阅读问题。运营商不在这些字段上寻找复合键。另一个答案适用于复合键
e4c5

65

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

*除了唯一索引,您还可以使用排除约束。这些比唯一性约束要笼统一些。假设你的表有列idvalid_time(和valid_timetsrange),并且你想允许重复idS,但不重叠的时间段。唯一约束不会帮助您,但是使用排除约束,您可以说“如果新记录id等于旧记录id并且valid_time与之重叠,则排除新记录valid_time。”


4
这将创建一个共同的唯一索引,在t(id,a)上创建唯一索引idx_t_id_a;当然,OP并没有明确说明这两列是单独的还是一起唯一的。
e4c5

为什么postgres有时说没有以索引命名的列并且无法使用ON CONFLICT
2014年

@Pak听起来您应该使用正在使用的特定命令和收到的错误消息来编写自己的问题。
Paul A Jungwirth,2013年

@PaulAJungwirth我不知道,您的答案是正确的-唯一索引作为on conflict命令的约束。错误只是“列my_index_name不存在”。
2014年

无论如何,我确实尝试过此操作,因为OP要求在每列上使用单独的唯一约束,但是它没有用。不是我期望的那样,但是我希望。
sudo

5

在当今(似乎)是不可能的。ON CONFLICT 语法的最新版本既不允许重复该子句,也不允许CTE:不可能从ON CONFLICT插入INSERT以添加更多冲突目标。


3

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

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


1

弗拉德有个正确的主意。

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

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

4
抱歉,您对这个问题有误解。OP不需要共同的唯一约束。
e4c5

1

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

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

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


3
您可以为这两列创建唯一索引,并在中赋予该约束on conflict
Kishore Relangi

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 ),因为我知道我想做的事,然后我知道许多唯一约束中的哪一个是可以违反的。


-4

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和所有其他数据库


它不是原子的,因此如果同时存在多个连接,它可能会失败并产生错误的结果。
Bogdan Mart,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.