У меня есть следующий UPSERT в PostgreSQL 9.5:
INSERT INTO chats ("user", "contact", "name")
VALUES ($1, $2, $3),
($2, $1, NULL)
ON CONFLICT("user", "contact") DO NOTHING
RETURNING id;
Если нет конфликтов, возвращается что-то вроде этого:
----------
| id |
----------
1 | 50 |
----------
2 | 51 |
----------
Но если есть конфликты, он не возвращает никаких строк:
----------
| id |
----------
Я хочу вернуть новые id
столбцы, если нет конфликтов, или вернуть существующие id
столбцы конфликтующих столбцов.
Можно ли это сделать? Если так, то как?
ON CONFLICT UPDATE
чтобы изменения в строке. ТогдаRETURNING
поймаете это.Ответы:
У меня была точно такая же проблема, и я решил ее с помощью «сделать обновление» вместо «ничего не делать», хотя мне нечего было обновлять. В вашем случае это будет примерно так:
Этот запрос вернет все строки, независимо от того, были ли они только что вставлены или существовали ранее.
источник
DO NOTHING
аспекту исходного вопроса - для меня он, по-видимому, обновляет поле без конфликтов (здесь «имя») для всех строк.В настоящее время принятый ответ кажется приемлемым для одной цели конфликта, нескольких конфликтов, небольших кортежей и отсутствия триггеров. Это позволяет избежать проблемы параллелизма 1 (см. Ниже) с помощью грубой силы. Простое решение имеет свою привлекательность, побочные эффекты могут быть менее важными.
Однако во всех остальных случаях не обновляйте идентичные строки без необходимости. Даже если вы не видите никакой разницы на поверхности, существуют различные побочные эффекты :
Это может вызвать срабатывание триггеров, которые не должны срабатывать.
Он блокирует «невинные» строки, что может повлечь за собой затраты для одновременных транзакций.
Строка может показаться новой, хотя она и старая (временная метка транзакции).
Наиболее важно то , что в модели MVCC PostgreSQL для каждой строки записывается новая версия строки
UPDATE
, независимо от того, изменились ли данные строки. Это влечет за собой снижение производительности для самого UPSERT, раздувание таблиц, увеличение индекса, снижение производительности для последующих операций над таблицей,VACUUM
стоимость. Незначительный эффект для нескольких дубликатов, но огромный для большей части дубликатов .Плюс , иногда это не практично или даже невозможно использовать
ON CONFLICT DO UPDATE
. Руководство:Сингл «целевой конфликт» не представляется возможным , если несколько индексов / ограничения вовлечены.
Вы можете достичь (почти) того же самого без пустых обновлений и побочных эффектов. Некоторые из следующих решений также работают
ON CONFLICT DO NOTHING
(без «цели конфликта»), чтобы уловить все возможные конфликты, которые могут возникнуть - что может быть или не быть желательным.Без одновременной загрузки записи
source
Столбец является необязательным дополнением , чтобы продемонстрировать , как это работает. Возможно, вам это понадобится, чтобы определить разницу между обоими случаями (еще одно преимущество перед пустыми записями).Финал
JOIN chats
работает, потому что вновь вставленные строки из присоединенного CTE, модифицирующего данные, еще не видны в базовой таблице. (Все части одного и того же оператора SQL видят одинаковые снимки базовых таблиц.)Поскольку
VALUES
выражение является автономным (напрямую не связано сINSERT
), Postgres не может извлекать типы данных из целевых столбцов, и вам, возможно, придется добавить явное приведение типов. Руководство:Сам запрос (не считая побочных эффектов) может быть немного дороже для нескольких дупликов из-за накладных расходов CTE и дополнительного
SELECT
(который должен быть дешевым, поскольку по определению существует идеальный индекс - уникальное ограничение реализуется с помощью индекс).Может быть (намного) быстрее для многих дубликатов. Эффективная стоимость дополнительных записей зависит от многих факторов.
Но в любом случае побочных эффектов и скрытых затрат меньше . Это, скорее всего, дешевле в целом.
Прикрепленные последовательности все еще продвинуты, поскольку значения по умолчанию заполняются перед проверкой на конфликты.
О CTE:
С одновременной загрузкой записи
Предполагая
READ COMMITTED
изоляцию транзакции по умолчанию . Связанный:Наилучшая стратегия защиты от условий гонки зависит от точных требований, количества и размера строк в таблице и в UPSERT, количества одновременных транзакций, вероятности конфликтов, доступных ресурсов и других факторов ...
Проблема параллелизма 1
Если параллельная транзакция записала в строку, которую ваша транзакция сейчас пытается UPSERT, ваша транзакция должна дождаться завершения другой.
Если другая транзакция заканчивается
ROLLBACK
(или любой ошибкой, т.е. автоматическойROLLBACK
), ваша транзакция может продолжаться в обычном режиме. Незначительный возможный побочный эффект: пробелы в последовательных номерах. Но нет пропущенных строк.Если другая транзакция заканчивается нормально (неявно или явно
COMMIT
), выINSERT
обнаружите конфликт (UNIQUE
индекс / ограничение является абсолютным) иDO NOTHING
, следовательно, также не вернете строку. (Также не может заблокировать строку, как показано в проблеме 2 параллелизма ниже, так как она не видна .) ОнаSELECT
видит тот же моментальный снимок с начала запроса и также не может вернуть еще невидимую строку.Любые такие строки отсутствуют в наборе результатов (даже если они существуют в базовой таблице)!
это может быть хорошо, как есть . Особенно, если вы не возвращаете строки, как в примере, и удовлетворены, зная, что строка есть. Если этого недостаточно, есть разные способы обойти это.
Вы можете проверить количество строк на выходе и повторить оператор, если он не совпадает с количеством строк на входе. Может быть достаточно для редкого случая. Смысл в том, чтобы начать новый запрос (может быть в той же транзакции), который затем увидит вновь зафиксированные строки.
Или проверьте пропущенные строки результатов в том же запросе и замените их с помощью метода грубой силы, продемонстрированного в ответе Алекстони .
Это как запрос выше, но мы добавляем еще один шаг с CTE
ups
, прежде чем мы вернем полный набор результатов. Последний CTE ничего не сделает большую часть времени. Только если строки возвращаются из возвращаемого результата, мы используем грубую силу.Еще больше накладных расходов. Чем больше конфликтов с уже существующими строками, тем больше вероятность, что это превзойдет простой подход.
Один побочный эффект: второй UPSERT записывает строки не по порядку, поэтому он вновь вводит возможность взаимоблокировок (см. Ниже), если три или более транзакций, записывающих в одни и те же строки, перекрываются. Если это проблема, вам нужно другое решение - например, повторить все утверждение, как упомянуто выше.
Проблема параллелизма 2
Если одновременные транзакции могут записывать в соответствующие столбцы затронутых строк, и вы должны убедиться, что найденные строки все еще находятся там на более позднем этапе той же транзакции, вы можете дешево заблокировать существующие строки в CTE
ins
(который в противном случае был бы разблокирован) с участием:И добавьте блокирующую оговорку к тому
SELECT
же, вродеFOR UPDATE
.Это заставляет конкурирующие операции записи ждать до конца транзакции, когда все блокировки сняты. Так что будь краток.
Более подробная информация и объяснение:
Тупики?
Защитите от взаимных блокировок , вставляя строки в последовательном порядке . Видеть:
Типы данных и приведение
Существующая таблица как шаблон для типов данных ...
Явное приведение типов для первой строки данных в автономном
VALUES
выражении может быть неудобным. Есть способы обойти это. Вы можете использовать любое существующее отношение (таблица, представление, ...) в качестве шаблона строки. Целевая таблица является очевидным выбором для варианта использования. Входные данные автоматически приводятся к соответствующим типам, как вVALUES
разделеINSERT
:Это не работает для некоторых типов данных. Видеть:
... и имена
Это также работает для всех типов данных.
При вставке во все (ведущие) столбцы таблицы вы можете опустить имена столбцов. Предполагая, что таблица
chats
в примере состоит только из 3 столбцов, используемых в UPSERT:В сторону: не используйте зарезервированные слова, такие
"user"
как идентификатор. Это заряженный пулемет. Используйте допустимые, строчные, без кавычек идентификаторы. Я заменил его наusr
.источник
ON CONFLICT SELECT...
где вещь, хотя :)Upsert, являющийся расширением
INSERT
запроса, может быть определен с двумя различными вариантами поведения в случае конфликта ограничений:DO NOTHING
илиDO UPDATE
.Заметьте также, что
RETURNING
ничего не возвращается, потому что никакие кортежи не были вставлены . ТеперьDO UPDATE
можно выполнять операции с кортежем, с которым существует конфликт. Прежде всего обратите внимание, что важно определить ограничение, которое будет использоваться для определения наличия конфликта.источник
Для вставки одного элемента, я бы, вероятно, использовал бы coalesce при возврате id:
источник
Основная цель использования
ON CONFLICT DO NOTHING
состоит в том, чтобы избежать появления ошибки, но это не приведет к возврату строки. Поэтому нам нужен другой,SELECT
чтобы получить существующий идентификатор.В этом SQL, если он потерпит неудачу в конфликтах, он ничего не возвратит, тогда второй
SELECT
получит существующую строку; если он вставлен успешно, то будет две одинаковые записи, тогда нам нужноUNION
объединить результат.источник
Я изменил удивительный ответ Эрвина Брандштеттера, который не увеличивает последовательность, а также не блокирует запись каких-либо строк. Я относительно новичок в PostgreSQL, поэтому, пожалуйста, дайте мне знать, если вы видите какие-либо недостатки этого метода:
Это предполагает, что таблица
chats
имеет уникальное ограничение на столбцы(usr, contact)
.Обновление: добавлены предложенные ревизии от spatar (ниже). Спасибо!
источник
CASE WHEN r.id IS NULL THEN FALSE ELSE TRUE END AS row_exists
просто написатьr.id IS NOT NULL as row_exists
. Вместо того, чтобыWHERE row_exists=FALSE
просто написатьWHERE NOT row_exists
.