Эквивалент ON CONFLICT ничего не делает для обновления postgres - PullRequest
0 голосов
/ 11 января 2019

Я хочу обновить строки в моей базе данных postgres, если обновленная версия не будет нарушать ограничение первичного ключа. Если это так, я хочу оставить строку как есть.

Предполагая, что у таблицы есть первичные ключи на col1, col2, col3, если я выполню такой запрос:

UPDATE table SET (col1, col2) = ('AAA', 'BBB') 
      WHERE col1='AAB' AND col2='BBA';

Запрос не будет выполнен, и я получу ошибку дублирующего ключа, если существует две записи:

'AAA', 'BBB', 'CCC'
'AAB', 'BBA', 'CCC'

То есть col3 одинаково между существующей строкой и строкой, подлежащей обновлению.

Если бы я был INSERT строк, я бы использовал ON CONFLICT DO NOTHING, но я не могу найти реализацию этого для UPDATE. Существует ли эквивалент?

Ответы [ 2 ]

0 голосов
/ 11 января 2019

AFAIK, такого эквивалента нет.

Допустим, вы разрабатываете приложение, которое подключается к базе данных postgresql. В контексте вашего вопроса необходимо учитывать несколько вещей:

  • Это может быть нелогично, но вы должны рассматривать ошибки, выбрасываемые БД, как хорошая вещь.
    Это просто получение статуса, это не означает сбой приложения.
  • Для вставки существует альтернативный вариант действия on conflict (обновить или ничего), поэтому имеет смысл использовать синтаксис, который позволит вам принять решение.
    Для обновлений единственное, что вы можете сделать, это ... ничего.
    Так почему же SQL позволил вам попросить сделать что-то конкретное, поскольку выбора нет? Помните, что сообщения об ошибках БД хорошо , поэтому позвольте БД ничего не делать и объясните, почему.
  • Наконец, обновление первичных ключей - плохая практика.
    ON CONFLICT ... для вставок не предназначен для обновления полей первичного ключа. На самом деле все наоборот: он предназначен для обновления всех полей , кроме полей из первичного ключа в одной записи.

Пока я нахожусь в этом месте, обратите внимание, что не было необходимости конфликтовать с первичным ключом для сбоя запроса
1 запись с «удобным» внешним ключом ON UPDATE NO ACTION также привела бы к его отказу (что все же лучше, чем обновление 10M + записей в 50 таблицах с ON UPDATE CASCADE ...). Кстати, вы знали, что у Oracle даже нет пункта ON UPDATE CASCADE? Как вы думаете, в чем причина?


Что вы можете / не должны делать в этой ситуации?

  1. Не обновляйте первичный ключ , как я уже сказал. Ваш вопрос по-прежнему действителен для ограничений UNIQUE, но, пожалуйста, НИКОГДА не обновляйте первичные ключи.
  2. Не пытайтесь проверить, существует ли конфликтующая запись . Это может занять много времени и все еще быть ненадежным.
    Вы действительно хотите выбрать миллионы записей, чтобы избежать кодов ошибок?
    Кроме того, когда вы расширяете другие ограничения (CHECK или EXCLUSION), действительно ли вы наберете дополнительный код, который он принимает без ошибок, чтобы, опять же, избежать только кода ошибки?
    Наконец, если вы внедрили защиту на уровне строк, конфликт может возникнуть из-за записи, которую вы не видите.
  3. Обработайте код ошибки в вашем приложении . Статус получения: ХОРОШО .
  4. Используйте точки сохранения, если вы находитесь в середине транзакции .
    Это единственная неприятная вещь, связанная с ошибками БД: если вы получите ошибку в середине транзакции, вы начнете получать current transaction is aborted, commands ignored until end of transaction block за все.
    Надеюсь, вам не нужно откатывать всю транзакцию назад и переделывать все с нуля. Вы можете уйти, используя следующий фрагмент кода.

Вот, пожалуйста,

BEGIN;
SAVEPOINT MySavepoint;
UPDATE mytable set myuniquefield = 3; /*2+ records are going to be updated */
rollback to savepoint MySavepoint;
/*Insert Some more queries here*/
COMMIT;
0 голосов
/ 11 января 2019

Вы можете использовать коррелированный подзапрос с предложением WHERE NOT EXISTS, чтобы гарантировать, что ваше обновление не будет генерировать дубликаты, например:

UPDATE mytable t
SET (col1, col2) = ('AAA', 'BBB')
WHERE t.col1 = 'AAB' and t.col2 = 'BBA'
AND NOT EXISTS (
   SELECT 1 FROM mytable WHERE col1 = 'AAA' AND col2 = 'BBB' AND col3 = t.col3
);

Проверено на этой скрипке дБ .

EDIT

Как прокомментировал Роман Коновал, обратите внимание, что это все равно приведет к ошибке дубликата ключа, если параллельная транзакция вставит тот же ключ во время работы UPDATE. Это указывает на то, что обновление первичного ключа таблицы не является хорошей практикой (подробное обсуждение этого вопроса см. В ответе @Lau ниже).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...