128

我在表中有两列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 
....
4

11 回答 11

98

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

* 除了唯一索引,您还可以使用排除约束。这些比唯一约束更通用。假设您的表有idand列valid_time(并且valid_time是 a tsrange),并且您希望允许重复ids,但不允许重叠时间段。唯一约束对您没有帮助,但使用排除约束,您可以说“排除新记录,如果它们id等于旧记录id并且它们valid_time与它重叠valid_time。”

于 2016-06-23T15:21:51.413 回答
65

样本表和数据

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.

文件说什么

conflict_target 可以执行唯一索引推断。执行推理时,它包含一个或多个 index_column_name 列和/或 index_expression 表达式,以及一个可选的 index_predicate。所有 table_name 唯一索引,不考虑顺序,完全包含冲突目标指定的列/表达式,都被推断(选择)为仲裁索引。如果指定了 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 查询中,postgresql 是否应该col1在发生冲突时更新col2?但是,如果这导致另一个冲突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');
于 2016-06-28T02:13:14.073 回答
7

在当今是(似乎)不可能的。ON CONFLICT 语法的最后一个版本不允许重复该子句,也不允许使用CTE:不可能从 ON CONFLICT 中断 INSERT 以添加更多冲突目标。

于 2016-03-12T20:13:07.367 回答
5

如果您使用的是 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;
于 2018-01-18T18:55:13.350 回答
3

弗拉德的想法是对的。

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

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT ON CONSTRAINT dupes_pkey 
DO UPDATE SET col3 = 'c', col2 = 2
于 2016-08-18T04:01:24.230 回答
2
  1. 创建一个约束(例如外部索引)。

或/与

  1. 查看现有约束(psq 中的 \d)。
  2. 在 INSERT 子句中使用 ON CONSTRAINT(constraint_name)。
于 2016-04-09T08:21:53.150 回答
1

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

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

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

于 2019-01-09T23:51:34.447 回答
0
ON CONFLICT ( col1, col2 )
DO UPDATE 
SET 

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

于 2021-05-12T12:21:19.097 回答
0

You can typically (I would think) generate a statement with only one on conflict that specifies the one and only constraint that is of relevance, for the thing you are inserting.

Because typically, only one constraint is the "relevant" one, at a time. (If many, then I'm wondering if something is weird / oddly-designed, hmm.)

Example:
(License: Not CC0, only 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)

And:

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")

The on conflict clause is dynamically generated, depending on what I'm trying to do. If I'm inserting a notification preference, for a page — then there can be a unique conflict, on the site_id, people_id, page_id constraint. And if I'm configuring notification prefs, for a category — then instead I know that the constraint that can get violated, is site_id, people_id, category_id.

So I can, and fairly likely you too, in your case?, generate the correct on conflict (... columns ), because I know what I want to do, and then I know which single one of the many unique constraints, is the one that can get violated.

于 2018-10-29T07:02:18.293 回答
-1

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

INSERT INTO tbl_Employee 
VALUES (6,'Noor')
ON CONFLICT (EmpID,EmpName)
DO NOTHING;
于 2021-01-22T11:43:08.747 回答
-5

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

于 2019-06-22T08:43:32.400 回答