PostgreSQL MERGE INTO: обновить или вставить строки

Категория: Postgresql · Язык: SQL · Обновлено:

MERGE сравнивает целевую таблицу с источником по условию ON и выполняет UPDATE, INSERT или DELETE одной инструкцией. В PostgreSQL команда появилась в версии 15, а RETURNING и merge_action() для неё добавили только в 17. Это удобно для синхронизации справочников и пакетной записи без отдельного INSERT ... ON CONFLICT.

Синхронизировать каталог из VALUES

Источником может быть таблица, подзапрос или список VALUES. Пример обновляет изменившуюся цену и добавляет товар, которого ещё нет:

SQL
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+

SQL
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().