PostgreSQL MERGE INTO: обновить или вставить строки
MERGE сравнивает целевую таблицу с источником по условию ON и выполняет UPDATE, INSERT или DELETE одной инструкцией. В PostgreSQL команда появилась в версии 15, а RETURNING и merge_action() для неё добавили только в 17. Это удобно для синхронизации справочников и пакетной записи без отдельного INSERT ... ON CONFLICT.
Синхронизировать каталог из VALUES
Источником может быть таблица, подзапрос или список VALUES. Пример обновляет изменившуюся цену и добавляет товар, которого ещё нет:
CREATE TABLE products (
id integer PRIMARY KEY,
name text NOT NULL,
price numeric(10, 2) NOT NULL
);
INSERT INTO products VALUES
(1, 'Клавиатура', 4500.00),
(2, 'Мышь', 1900.00);
MERGE INTO products AS p
USING (VALUES
(1, 'Клавиатура', 4900.00),
(3, 'Монитор', 21900.00)
) AS s(id, name, price)
ON p.id = s.id
WHEN MATCHED AND p.price IS DISTINCT FROM s.price THEN
UPDATE SET name = s.name, price = s.price
WHEN NOT MATCHED THEN
INSERT (id, name, price)
VALUES (s.id, s.name, s.price);
SELECT id, name, price FROM products ORDER BY id;IS DISTINCT FROM не считает NULL равным NULL, поэтому условие корректно и для необязательных полей. Если несколько WHEN MATCHED подходят к строке, срабатывает первая ветка, поэтому располагайте более узкие условия выше. Условие ON должно однозначно определять строку назначения; иначе результат зависит от порядка обработки источника.
Получить изменённые строки в PostgreSQL 17+
MERGE INTO products AS p
USING (VALUES (2, 'Мышь', 2100.00)) AS s(id, name, price)
ON p.id = s.id
WHEN MATCHED THEN
UPDATE SET price = s.price
RETURNING p.id, p.price, merge_action();merge_action() возвращает INSERT, UPDATE или DELETE для каждой затронутой строки. На PostgreSQL 15 и 16 RETURNING у MERGE отсутствует: разделите операцию на UPDATE ... RETURNING и INSERT, либо выполните MERGE и отдельный SELECT.
MERGE не отменяет уникальный индекс: два конкурентных запроса всё ещё могут попытаться вставить один ключ, и один получит unique_violation. Обрабатывайте эту ошибку повтором транзакции. Для большой синхронизации ограничивайте источник и проверяйте план через EXPLAIN (ANALYZE, BUFFERS).
Документация: MERGE, merge_action().