MERGE (PostgreSQL 15+, стандартний SQL) порівнює цільову таблицю з джерелом і для кожного рядка виконує дію залежно від того, знайдено збіг чи ні:
MERGE INTO products AS p
USING staging_products AS s
ON p.sku = s.sku
WHEN MATCHED AND s.discontinued THEN
DELETE
WHEN MATCHED THEN
UPDATE SET price = s.price, updated_at = now()
WHEN NOT MATCHED THEN
INSERT (sku, name, price) VALUES (s.sku, s.name, s.price);
PostgreSQL 17+: WHEN NOT MATCHED BY SOURCE - рядки цільової таблиці, яких немає в джерелі (наприклад, видалити товари, що зникли з фіда), і RETURNING з функцією merge_action(), що показує, яку дію виконано для рядка.
Порівняння:
INSERT ... ON CONFLICT |
MERGE |
|
|---|---|---|
| дії | вставити чи оновити | вставити, оновити, видалити, нічого |
| умова збігу | лише унікальний індекс чи обмеження | довільна умова ON |
| потрібен унікальний індекс | так | ні |
| атомарність при паралельних вставках | так, конфлікт розв'язує індекс | ні - можливі помилки унікальності |
| джерело | VALUES чи SELECT |
таблиця, підзапит, VALUES |
Головна відмінність - поведінка при конкуренції. ON CONFLICT гарантовано обробляє ситуацію, коли інший запит одночасно вставляє той самий ключ: він чекає й оновлює. MERGE спершу визначає, чи є збіг, а потім виконує дію - якщо паралельний запит встиг вставити рядок, MERGE спробує вставити його вдруге й отримає помилку унікальності.
Коли що обирати:
ON CONFLICT- upsert з високою конкуренцією: лічильники, кеш-таблиці, синхронізація окремих записів з багатьох процесів;MERGE- пакетна синхронізація з таблицею-джерелом: імпорт фіда, ETL з проміжної таблиці, де потрібні і вставки, і оновлення, і видалення, а паралельних записувачів немає (чи їх виключено блокуванням).
Типовий сценарій імпорту:
COPYфайлу в тимчасову таблицю;MERGEз неї в робочу таблицю;- одна транзакція - або все застосовано, або нічого.
У Laravel конструктор запитів не має методу для MERGE - використовують DB::statement() з параметрами.