Зразок таблиці та даних
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.
конфликт_цель може виконувати унікальний умовивід індексу. При виконанні умовиводу він складається з одного або декількох стовпців_імена_індексу та / або виразів_вираження індексу та необов'язкового предиката_індексу. Усі унікальні індекси табличних імен, які, незалежно від порядку, містять точно вказані стовпці / вирази, задані конфліктом, визначаються (вибираються) як індекси арбітра. Якщо вказано предикат index_, він, як додаткова вимога для умовиводу, повинен задовольняти індекси арбітра.
Це створює враження, що наступний запит повинен працювати, але це не так, оскільки він насправді потребує спільного унікального індексу на 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
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');