样本表和数据
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
UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2;
IF found THEN
RETURN;
END IF;
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
END;
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
您将需要修改此存储函数的逻辑,以使其完全按照所需的方式更新列。像这样调用
SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');