У меня есть два столбца в таблице col1
, col2
они оба уникально проиндексированы (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
....
postgresql
upsert
postgresql-9.5
Ото Шавадзе
источник
источник
Ответы:
Примерная таблица и данные
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. Результат
Что говорится в документации
Создается впечатление, что следующий запрос должен работать, но это не так, потому что на самом деле он потребовал бы вместе уникального индекса для 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');
источник
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
* В дополнение к уникальным индексам вы также можете использовать ограничения исключения . Это немного более общие, чем уникальные ограничения. Предположим, в вашей таблице есть столбцы для
id
иvalid_time
(иvalid_time
есть atsrange
), и вы хотите разрешить дублированиеid
s, но не для перекрывающихся периодов времени. Ограничение уникальности вам не поможет, но с ограничением исключения вы можете сказать: «исключить новые записи, если ониid
равны старым,id
а также ихvalid_time
перекрываютvalid_time
».источник
ON CONFLICT
?on conflict
команды. Ошибка просто «столбец my_index_name не существует».В наши дни это (кажется) невозможно. Ни последняя версия
ON CONFLICT
синтаксиса не позволяет повторять предложение, ни CTE невозможно: невозможно нарушить INSERT из ON CONFLICT, чтобы добавить больше целей конфликта.источник
Если вы используете postgres 9.5, вы можете использовать ИСКЛЮЧЕННОЕ пространство.
Пример взят из раздела Что нового в 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;
источник
ИЛИ / И
источник
Влад понял правильную идею.
Сначала вам нужно создать уникальное ограничение таблицы для столбцов.
col1, col2
Затем, как только вы это сделаете, вы можете сделать следующее:INSERT INTO dupes values(3,2,'c') ON CONFLICT ON CONSTRAINT dupes_pkey DO UPDATE SET col3 = 'c', col2 = 2
источник
Вид хакерства, но я решил это, объединив два значения из col1 и col2 в новый столбец col3 (что-то вроде индекса этих двух) и сравнил с ним. Это работает, только если вам нужно, чтобы он соответствовал ОБЕИМ столбцам col1 и col2.
INSERT INTO table ... ON CONFLICT ( col3 ) DO UPDATE SET -- update needed columns here
Где col3 = объединение значений из col1 и col2.
источник
on conflict
.Обычно (я думаю) вы можете сгенерировать оператор только с одним,
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)
А также:
Предложение
on conflict
создается динамически, в зависимости от того, что я пытаюсь сделать. Если я вставляю предпочтение уведомления для страницы, тогда может возникнуть уникальный конфликтsite_id, people_id, page_id
ограничения. И если я настраиваю параметры уведомлений для категории, тогда я знаю, что ограничение, которое может быть нарушено, естьsite_id, people_id, category_id
.Итак, я могу, и, скорее всего, вы тоже, в вашем случае?, Сгенерировать правильное
on conflict (... columns )
, потому что я знаю, что я хочу сделать, а затем я знаю, какое из многих уникальных ограничений является тем, которое может быть нарушено.источник
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 и другими базами данных
источник