Где пункт в upsert цель конфликта - PullRequest
0 голосов
/ 13 октября 2019

Предположим, у меня есть таблица, созданная следующим образом:

CREATE TABLE actions (name text primary key, value text);
INSERT INTO actions (name) VALUES ('bla');

При попытке вставить новую строку есть три возможности:

  1. Строка для 'name' не существует,Новая строка должна быть вставлена.
  2. Существует строка для 'name' со значением NULL. Существующая строка должна быть обновлена ​​новым значением.
  3. Существует строка для 'name', значение которой не равно NULL. Вставка должна завершиться неудачей.

Я подумал, что смогу использовать синтаксис UPSERT следующим образом:

INSERT INTO actions (name, value) values ('bla', 'val1')
ON CONFLICT(name) WHERE value IS NULL
DO UPDATE SET value = excluded.value;

INSERT INTO actions (name, value) values ('bla', 'val2')
ON CONFLICT(name) WHERE value IS NULL
DO UPDATE SET value = excluded.value;

Первая команда обновляет существующую строку, как и ожидалось. Но вторая команда снова обновляет строку, чего я не ожидал.

Что должен делать оператор where в цели конфликта? В моих тестах не имеет значения, оценивается ли оно как true или false.

Основано на PostgreSQL Upsert с предложением WHERE Я пытался использовать уникальный частичный индекс вместо первичного ключа, но потом я получаю несколько строк с одинаковым именем.

Ответы [ 2 ]

2 голосов
/ 13 октября 2019

Использование second WHERE сделает то, что вы хотите:

INSERT INTO actions(name) VALUES ('bla');
INSERT INTO actions(name, value) VALUES ('bla', 'val1')
 ON CONFLICT(name) DO UPDATE SET value = excluded.value WHERE value IS NULL;
SELECT * FROM actions;
name        value
----------  ----------
bla         val1
INSERT INTO actions(name, value) VALUES ('bla', 'val2')
 ON CONFLICT(name) DO UPDATE SET value = excluded.value WHERE value IS NULL;
SELECT * FROM actions;
name        value
----------  ----------
bla         val1

Первый WHERE после столбца (-ов) конфликта предназначен для выбораконкретный частичный индекс, чтобы использовать для обнаружения конфликтов. Вот пример того, как вы это чувствуете:

sqlite> CREATE TABLE ex(name, value1, value2);
sqlite> CREATE UNIQUE INDEX ex_idx_name ON ex(name) WHERE value1 IS NULL;
sqlite> INSERT INTO ex(name) VALUES ('bla');
sqlite> INSERT INTO ex(name, value2) VALUES ('bla', 'val1');
Error: UNIQUE constraint failed: ex.name
sqlite> INSERT INTO ex(name, value1, value2) VALUES ('bla', 1, 'val2');
sqlite> SELECT * from ex;
name        value1      value2
----------  ----------  ----------
bla         (null)      (null)
bla         1           val2
sqlite> INSERT INTO ex(name, value2) VALUES ('bla', 'val3')
   ...> ON CONFLICT(name) WHERE value1 IS NULL DO UPDATE SET value2 = excluded.value2;
sqlite> SELECT * from ex;
name        value1      value2
----------  ----------  ----------
bla         (null)      val3
bla         1           val2
sqlite> INSERT INTO ex(name, value2) VALUES ('bla', 'val4')
   ...> ON CONFLICT(name) DO UPDATE SET value2 = excluded.value2;
Error: ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint

Обратите внимание, в частности, на два последних INSERT s: первый обновляет строку с нулевым значением, а второй генерирует ошибку, потому что онне полностью указывает уникальный индекс.

1 голос
/ 13 октября 2019

Это похоже на ошибку для меня. Но вы можете легко обойти это:

INSERT INTO actions (name, value) values ('bla', 'val1')
ON CONFLICT(name) 
DO UPDATE SET value = COALESCE(value, excluded.value);

Здесь - это дб <> скрипка.

...