25 февраля 2026
Тэги: PostgreSQL, SQL.
Оператор MERGE появился в Postgres 15, затем в Postgres 17 он был улучшен. До этого момента в PostgreSQL не было полноценной реализации стандартного SQL-оператора MERGE, хотя в Oracle и MS SQL Server он существовал уже много лет. Его добавление заметно облегчило написание UPSERT-скриптов (обновление данных, комбинированное со вставкой).
Предположим, у нас есть таблица с пользователями, которая называется person. В этой таблице есть автоинкрементный id, а также login с ограничением уникальности. Помимо этого есть текстовые поля first_name (имя) и last_name (фамилия):
Также предположим, что мы хотим организовать синхронизацию пользователей по следующей логике: если пользователь с таким логином уже есть в таблице - нужно обновить эту строку. Если пользователя с таким логином ещё нет - нужно добавить новую запись. По сути это и есть UPSERT.
До появления оператора MERGE самый популярный способ реализовать эту логику — INSERT ... ON CONFLICT, который появился в PostgreSQL 9.5. Он решает задачу UPSERT, но только по полю с уникальными значениями.
Здесь excluded — это строка, которую пытались вставить. Если login уже занят, вместо ошибки произойдёт UPDATE целевой строки. Если такого логина нет в таблице - произойдёт insert.
Теперь рассмотрим эквивалент с MERGE:
Как видите, он более многословный. Здесь мы для наглядности выбираем алиас s (сокращение от «source») для источника данных, и алиас d («destination») для целевой таблицы person, куда хотим сохранять данные. Источник не обязательно должен быть набором статичных значений - это может быть и выборка из другой таблицы.
Для сравнения мы используем поле login в источнике и приёмнике. А далее у нас имеются две ветки WHEN MATCHED и WHEN NOT MATCHED. Если в таблице-приёмнике уже есть такой же логин, как в источнике - выполняем update. Если такого логина нет - выполняем insert.
В некоторых кейсах синхронизации данных нам важно только вставлять новые записи, но при этом не требуется обновлять существующие. В таком случае мы можем использовать выражение DO NOTHING:
То же самое возможно и в случае с MERGE:
Аналогичным образом можно «заглушать» и вставку.
Однако многословность оператора MERGE компенсируется его читаемостью и универсальностью. Начиная с Postgres 17, помимо явного обозначения двух равнозначных веток, вы можете использовать выражения NOT MATCHED BY TARGET (нет в таблице-приёмнике - вставляем) и NOT MATCHED BY SOURCE (нет в источнике - удаляем). Это позволяет добавить третью ветку с удалением из таблицы-приёмника:
Поскольку в данном примере в качестве источника у нас фиксированные значения (по сути одна строка), то у нас автоматически удалятся все остальные записи из таблицы. В реальном запросе у вас здесь будет скорее всего select.
Конструкция INSERT ... ON CONFLICT не позволяет удалять записи. Она всегда сначала пытается вставить. И только если вставка не удалась - обновляет. Побочным эффектом такого поведения является рост значений sequence для автоинкрементного поля, даже если вставка не потребовалась. То есть в таблице значения primary key будут идти не подряд, а с пропусками.
Кроме того, INSERT ... ON CONFLICT является специфичным расширением Postgres, тогда как оператор MERGE входит в стандарт SQL и поддерживается другими популярными СУБД.
При этом для простых однострочных вставок INSERT ... ON CONFLICT будет работать быстрее. Тогда как MERGE позволяет реализовать более сложную логику с несколькими ветками условий.
Отсюда следует вывод:
MERGE, если синхронизируете таблицу целиком, оперируете множеством строк из этой таблицы или если у вас сложная логика синхронизации. Также используйте этот вариант, если вам важна совместимость с другими СУБД.INSERT ... ON CONFLICT, если вставляете только 1 строку и если вам не требуется удаление.Kotlin, Java, Spring, Spring Boot, Spring Data, Spring AI, SQL, PostgreSQL, Oracle, H2, Linux, Hibernate, Collections, Stream API, многопоточность, чат-боты, нейросети, файлы, devops, Docker, Nginx, Apache, maven, gradle, JUnit, YouTube, новости, руководство, ООП, алгоритмы, головоломки, rest, GraphQL, Excel, XML, json, yaml.