Как использовать RETURNING с ON CONFLICT в PostgreSQL?
У меня есть следующий 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
конфликтующих столбцов.
Можно ли это сделать? Если это так, как?
Ответы
Ответ 1
У меня была точно такая же проблема, и я решил ее с помощью "do update" вместо "ничего не делать", хотя мне нечего обновлять. В вашем случае это будет примерно так:
INSERT INTO chats ("user", "contact", "name")
VALUES ($1, $2, $3),
($2, $1, NULL)
ON CONFLICT("user", "contact") DO UPDATE SET name=EXCLUDED.name RETURNING id;
Этот запрос вернет все строки, независимо от того, они только что были вставлены или они существовали раньше.
Ответ 2
принятый в настоящее время ответ кажется одобренным для нескольких дубликатов, небольших кортежей, триггеров и отсутствия или небольшой одновременной нагрузки на таблицу. Простое решение имеет свою привлекательность, побочные эффекты могут быть менее важными.
Однако для всех остальных случаев не не нужно обновлять одинаковые строки. Даже если вы не видите разницы на поверхности, существуют различные побочные эффекты:
-
Он может запускать триггеры, которые не должны запускаться.
-
Он записывает блокировки "невинных" строк, возможно, из-за затрат на параллельные транзакции.
-
Может показаться, что строка кажется новой, хотя она и старая (временная метка транзакции).
-
Самое главное. модель PostgreSQL MVCC новая версия строки написана в любом случае, независимо от того, данные строки одинаковы. Это приводит к штрафу за производительность самого UPSERT, раздуванию таблицы, раздуванию индекса, штрафу за производительность для всех последующих операций в таблице, стоимости VACUUM
. Небольшой эффект для нескольких дубликатов, но массивный для большинства обманов.
Вы можете добиться одинакового результата без пустых обновлений и побочных эффектов:
WITH input_rows("user", "contact", "name") AS (
VALUES
(text 'foo', text 'bar', text 'bob') -- type casts in first row
, ('foo1', 'bar1', 'barb')
-- more?
)
, ins AS (
INSERT INTO chats ("user", "contact", "name")
SELECT * FROM input_rows
ON CONFLICT ("user", "contact") DO NOTHING
RETURNING id
)
SELECT 'i' AS source, id
FROM ins -- 'i' for 'inserted'
UNION ALL
SELECT 's' AS source, c.id -- 's' for 'selected'
FROM input_rows
JOIN chats c USING ("user", "contact"); -- columns of unique index
Столбец source
является необязательным дополнением, чтобы продемонстрировать, как это работает. Возможно, вам действительно нужно это, чтобы рассказать о различии между обоими случаями (другое преимущество перед пустым письмом).
Заключительный JOIN chats
работает, потому что недавно вставленные строки из привязанного к изменению данных CTE еще не видны в базовой таблице. (Все части одной и той же команды видят те же снимки базовых таблиц.)
Поскольку выражение VALUES
свободно (не привязано непосредственно к INSERT
), Postgres не может выводить типы данных из целевых столбцов, и вам, возможно, придется добавлять явные типы приведения. Руководство:
Когда VALUES
используется в INSERT
, все значения автоматически принуждение к типу данных соответствующего столбца назначения. когда он используется в других контекстах, может потребоваться указать правильный тип данных. Если в записях указаны все литеральные константы, принудительного первого достаточно, чтобы определить предполагаемый тип для всех.
Сам запрос может быть немного дороже для нескольких обманов из-за накладных расходов CTE и дополнительного SELECT
(что должно быть дешево, поскольку идеальный индекс существует по определению - уникальное ограничение реализуется с помощью индекс).
Может быть (намного) быстрее для многих дубликатов. Эффективная стоимость дополнительных записей зависит от многих факторов.
Но в любом случае меньше побочных эффектов и скрытых затрат. (Прикрепленные последовательности все еще расширены, поскольку значения по умолчанию заполняются перед тестированием конфликтов.) Это, скорее всего, более дешевое в целом.
Если параллельные транзакции могут писать задействованные столбцы затронутых строк, и вы должны убедиться, что найденные вами строки все еще находятся на более позднем этапе транзакции, вы можете заблокировать строки дешево:
...
ON CONFLICT ("user", "contact") DO UPDATE
SET name = name WHERE FALSE -- never executed, only locks rows
...
Подробнее и объяснение:
Кроме того: никогда не используйте цитируемые зарезервированные слова, такие как "user"
как идентификатор. Это загруженный нож для ног. Используйте только правовые, строчные, некотируемые идентификаторы.
Существующая таблица в качестве шаблона для типов данных
Явные типы приведения для первой строки данных в свободностоящем выражении VALUES
могут быть неудобными. Есть способы обойти это. Вы можете использовать любое существующее отношение (таблица, представление,...) в качестве шаблона строки. Целевая таблица является очевидным выбором для использования. Входные данные принудительно применяются к соответствующим типам, как в предложении VALUES
INSERT
:
WITH input_rows AS (
(SELECT "user", contact, name FROM chats LIMIT 0) -- only copies column names and types
UNION ALL
VALUES
('foo' , 'bar' , 'bob') -- no type casts needed
, ('foo1', 'bar1', 'barb')
)
...
... и имена
Если вы вставляете целые строки, вы также можете опустить имена столбцов - если хотите. Предполагая, что таблица chats
в примере содержит только три столбца:
WITH input_rows AS (
(TABLE chats LIMIT 0) -- copy whole row structure
UNION ALL
VALUES
('foo' , 'bar' , 'bob') -- no type casts needed
, ('foo1', 'bar1', 'barb')
)
...
TABLE
является просто сокращением синтаксиса для SELECT * FROM
здесь.
Подробное объяснение и альтернативы:
Ответ 3
Согласно http://michael.otacoo.com/postgresql-2/postgres-9-5-feature-highlight-upsert/:
Upsert, являющийся расширением запроса INSERT
, может быть задан с двумя разными типами поведения в случае конфликта ограничений: DO NOTHING
или DO UPDATE
.
INSERT INTO upsert_table VALUES (2, 6, 'upserted')
ON CONFLICT DO NOTHING RETURNING *;
id | sub_id | status
----+--------+--------
(0 rows)
Обратите также внимание на то, что RETURNING
ничего не возвращает, потому что не были вставлены кортежи. Теперь с DO UPDATE
можно выполнить операции над кортежем, с которым существует конфликт. Прежде всего обратите внимание на то, что важно определить ограничение, которое будет использоваться для определения конфликта.
INSERT INTO upsert_table VALUES (2, 2, 'inserted')
ON CONFLICT ON CONSTRAINT upsert_table_sub_id_key
DO UPDATE SET status = 'upserted' RETURNING *;
id | sub_id | status
----+--------+----------
2 | 2 | upserted
(1 row)