MERGE в Oracle SQL. Введение
Есть операторы, которые экономят не просто строки кода, а целые часы жизни. MERGE в Oracle — один из них. Он умеет в одном выражении аккуратно соединить логику INSERT, UPDATE, а иногда и DELETE.
Если любишь чистый, предсказуемый SQL без лишней суеты, то с этим оператором у тебя быстро возникнет взаимность.
Синтаксис
MERGE INTO target_table t
USING source_dataset s
ON (t.key_col = s.key_col)
WHEN MATCHED THEN
UPDATE SET t.col1 = s.col1
WHEN NOT MATCHED THEN
INSERT (key_col,col1)
VALUES (s.key_col,s.col1)Всё крутится вокруг трёх частей:
цель INTO,
источник USING,
и условие сопоставления ON.
Если совпадение найдено — можно обновлять.
Если не найдено — вставлять.
Это идеально для синхронизации справочников, витрин, staging-слоя и пакетных загрузок.
Где используют
- upsert-операции без лишних отдельных UPDATE и INSERT
- синхронизация витрин и справочников
- обновление данных из staging-таблиц
- пакетные ETL-процессы
- загрузка дельт из внешних источников
- очистка устаревших строк через DELETE внутри matched-ветки
100 примеров
1. Базовый upsert по первичному ключу
MERGE INTO customers t
USING customers_stage s
ON (t.customer_id = s.customer_id)
WHEN MATCHED THEN
UPDATE SET t.customer_name = s.customer_name
WHEN NOT MATCHED THEN
INSERT (customer_id,customer_name)
VALUES (s.customer_id,s.customer_name)2. Синхронизация email-адресов
MERGE INTO users t
USING users_delta s
ON (t.user_id = s.user_id)
WHEN MATCHED THEN
UPDATE SET t.email = s.email
WHEN NOT MATCHED THEN
INSERT (user_id,email)
VALUES (s.user_id,s.email)3. Обновление статуса заказа или добавление нового
MERGE INTO orders t
USING orders_stage s
ON (t.order_id = s.order_id)
WHEN MATCHED THEN
UPDATE SET t.status = s.status
WHEN NOT MATCHED THEN
INSERT (order_id,status)
VALUES (s.order_id,s.status)4. Обновление цены товара
MERGE INTO products t
USING product_prices s
ON (t.product_id = s.product_id)
WHEN MATCHED THEN
UPDATE SET t.price = s.price
WHEN NOT MATCHED THEN
INSERT (product_id,price)
VALUES (s.product_id,s.price)5. Подтягивание даты последнего входа
MERGE INTO logins t
USING login_stage s
ON (t.user_id = s.user_id)
WHEN MATCHED THEN
UPDATE SET t.last_login = s.last_login
WHEN NOT MATCHED THEN
INSERT (user_id,last_login)
VALUES (s.user_id,s.last_login)6. MERGE по составному ключу
MERGE INTO order_items t
USING order_items_stage s
ON (t.order_id = s.order_id AND t.item_id = s.item_id)
WHEN MATCHED THEN
UPDATE SET t.qty = s.qty
WHEN NOT MATCHED THEN
INSERT (order_id,item_id,qty)
VALUES (s.order_id,s.item_id,s.qty)7. Источник как SELECT из DUAL
MERGE INTO feature_flags t
USING (
SELECT 'PAYMENTS' AS code,1 AS enabled
FROM DUAL
) s
ON (t.code = s.code)
WHEN MATCHED THEN
UPDATE SET t.enabled = s.enabled
WHEN NOT MATCHED THEN
INSERT (code,enabled)
VALUES (s.code,s.enabled)8. Источник как UNION ALL-константы
MERGE INTO statuses t
USING (
SELECT 'NEW' AS status_code FROM DUAL
UNION ALL
SELECT 'DONE' FROM DUAL
) s
ON (t.status_code = s.status_code)
WHEN NOT MATCHED THEN
INSERT (status_code)
VALUES (s.status_code)9. Обновление только имени без вставки лишних столбцов
MERGE INTO departments t
USING departments_stage s
ON (t.dept_id = s.dept_id)
WHEN MATCHED THEN
UPDATE SET t.dept_name = s.dept_name
WHEN NOT MATCHED THEN
INSERT (dept_id,dept_name)
VALUES (s.dept_id,s.dept_name)10. Загрузка новых ролей пользователей
MERGE INTO user_roles t
USING user_roles_stage s
ON (t.user_id = s.user_id)
WHEN MATCHED THEN
UPDATE SET t.role_name = s.role_name
WHEN NOT MATCHED THEN
INSERT (user_id,role_name)
VALUES (s.user_id,s.role_name)11. Синхронизация адресов клиентов
MERGE INTO addresses t
USING addresses_stage s
ON (t.customer_id = s.customer_id)
WHEN MATCHED THEN
UPDATE SET t.city = s.city,t.street = s.street
WHEN NOT MATCHED THEN
INSERT (customer_id,city,street)
VALUES (s.customer_id,s.city,s.street)12. Дельта по складу с количеством
MERGE INTO stock t
USING stock_stage s
ON (t.sku = s.sku)
WHEN MATCHED THEN
UPDATE SET t.qty = s.qty
WHEN NOT MATCHED THEN
INSERT (sku,qty)
VALUES (s.sku,s.qty)13. Обновление описаний товаров
MERGE INTO product_descr t
USING product_descr_stage s
ON (t.product_id = s.product_id)
WHEN MATCHED THEN
UPDATE SET t.descr = s.descr
WHEN NOT MATCHED THEN
INSERT (product_id,descr)
VALUES (s.product_id,s.descr)14. Изменение цены только если она реально изменилась
MERGE INTO prices t
USING prices_stage s
ON (t.product_id = s.product_id)
WHEN MATCHED THEN
UPDATE SET t.price = s.price
WHERE NVL(t.price,-1) <> NVL(s.price,-1)
WHEN NOT MATCHED THEN
INSERT (product_id,price)
VALUES (s.product_id,s.price)15. Обновление справочника стран
MERGE INTO countries t
USING countries_stage s
ON (t.country_code = s.country_code)
WHEN MATCHED THEN
UPDATE SET t.country_name = s.country_name
WHEN NOT MATCHED THEN
INSERT (country_code,country_name)
VALUES (s.country_code,s.country_name)16. Перекладка значений с вычислением через CASE
MERGE INTO task_priority t
USING task_stage s
ON (t.task_id = s.task_id)
WHEN MATCHED THEN
UPDATE SET t.priority = CASE WHEN s.hours > 8 THEN 'HIGH' ELSE 'NORMAL' END
WHEN NOT MATCHED THEN
INSERT (task_id,priority)
VALUES (s.task_id,CASE WHEN s.hours > 8 THEN 'HIGH' ELSE 'NORMAL' END)17. Слияние по email вместо id
MERGE INTO newsletter_users t
USING newsletter_stage s
ON (LOWER(t.email) = LOWER(s.email))
WHEN MATCHED THEN
UPDATE SET t.opt_in = s.opt_in
WHEN NOT MATCHED THEN
INSERT (email,opt_in)
VALUES (s.email,s.opt_in)18. Обновление флага активности
MERGE INTO active_users t
USING active_users_stage s
ON (t.user_id = s.user_id)
WHEN MATCHED THEN
UPDATE SET t.is_active = s.is_active
WHEN NOT MATCHED THEN
INSERT (user_id,is_active)
VALUES (s.user_id,s.is_active)19. Синхронизация валютных курсов
MERGE INTO fx_rates t
USING fx_rates_stage s
ON (t.ccy = s.ccy AND t.rate_date = s.rate_date)
WHEN MATCHED THEN
UPDATE SET t.rate = s.rate
WHEN NOT MATCHED THEN
INSERT (ccy,rate_date,rate)
VALUES (s.ccy,s.rate_date,s.rate)20. Обновление лимитов с дефолтом через NVL
MERGE INTO limits t
USING limits_stage s
ON (t.limit_code = s.limit_code)
WHEN MATCHED THEN
UPDATE SET t.limit_value = NVL(s.limit_value,0)
WHEN NOT MATCHED THEN
INSERT (limit_code,limit_value)
VALUES (s.limit_code,NVL(s.limit_value,0))Еще 20 примеров
21. Добавление новых клиентов без обновления существующих
MERGE INTO clients t
USING clients_stage s
ON (t.client_id = s.client_id)
WHEN NOT MATCHED THEN
INSERT (client_id,client_name)
VALUES (s.client_id,s.client_name)22. Только обновление, без вставки новых строк
MERGE INTO invoices t
USING invoices_stage s
ON (t.invoice_id = s.invoice_id)
WHEN MATCHED THEN
UPDATE SET t.amount = s.amount,t.status = s.status23. Источник как подзапрос с GROUP BY
MERGE INTO sales_totals t
USING (
SELECT customer_id,SUM(amount) AS total
FROM orders_stage
GROUP BY customer_id
) s
ON (t.customer_id = s.customer_id)
WHEN MATCHED THEN
UPDATE SET t.total = s.total
WHEN NOT MATCHED THEN
INSERT (customer_id,total)
VALUES (s.customer_id,s.total)24. Источник как JOIN двух staging-таблиц
MERGE INTO customer_full t
USING (
SELECT a.customer_id,a.customer_name,b.city
FROM customer_stage a
JOIN address_stage b ON b.customer_id = a.customer_id
) s
ON (t.customer_id = s.customer_id)
WHEN MATCHED THEN
UPDATE SET t.customer_name = s.customer_name,t.city = s.city
WHEN NOT MATCHED THEN
INSERT (customer_id,customer_name,city)
VALUES (s.customer_id,s.customer_name,s.city)25. Обновление с фильтром в USING по новым данным
MERGE INTO tickets t
USING (
SELECT * FROM tickets_stage WHERE status = 'OPEN'
) s
ON (t.ticket_id = s.ticket_id)
WHEN MATCHED THEN
UPDATE SET t.status = s.status
WHEN NOT MATCHED THEN
INSERT (ticket_id,status)
VALUES (s.ticket_id,s.status)26. Синхронизация только для одного региона
MERGE INTO stores t
USING (
SELECT * FROM stores_stage WHERE region_code = 'EU'
) s
ON (t.store_id = s.store_id)
WHEN MATCHED THEN
UPDATE SET t.store_name = s.store_name
WHEN NOT MATCHED THEN
INSERT (store_id,store_name,region_code)
VALUES (s.store_id,s.store_name,s.region_code)27. Обновление цены с округлением через ROUND
MERGE INTO product_costs t
USING product_costs_stage s
ON (t.product_id = s.product_id)
WHEN MATCHED THEN
UPDATE SET t.cost = ROUND(s.cost,2)
WHEN NOT MATCHED THEN
INSERT (product_id,cost)
VALUES (s.product_id,ROUND(s.cost,2))28. Мягкое удаление внутри WHEN MATCHED THEN DELETE WHERE
MERGE INTO blacklist t
USING blacklist_stage s
ON (t.email = s.email)
WHEN MATCHED THEN
UPDATE SET t.is_deleted = s.is_deleted
DELETE WHERE s.is_deleted = 1
WHEN NOT MATCHED THEN
INSERT (email,is_deleted)
VALUES (s.email,s.is_deleted)29. Очистка нулевых остатков через DELETE WHERE
MERGE INTO stock t
USING stock_stage s
ON (t.sku = s.sku)
WHEN MATCHED THEN
UPDATE SET t.qty = s.qty
DELETE WHERE s.qty = 0
WHEN NOT MATCHED THEN
INSERT (sku,qty)
VALUES (s.sku,s.qty)30. Обновление и удаление просроченных записей
MERGE INTO sessions t
USING sessions_stage s
ON (t.session_id = s.session_id)
WHEN MATCHED THEN
UPDATE SET t.last_seen = s.last_seen
DELETE WHERE s.last_seen < SYSDATE - 30
WHEN NOT MATCHED THEN
INSERT (session_id,last_seen)
VALUES (s.session_id,s.last_seen)31. Синхронизация с преобразованием текста через UPPER
MERGE INTO sku_groups t
USING sku_groups_stage s
ON (t.group_code = s.group_code)
WHEN MATCHED THEN
UPDATE SET t.group_name = UPPER(s.group_name)
WHEN NOT MATCHED THEN
INSERT (group_code,group_name)
VALUES (s.group_code,UPPER(s.group_name))32. Обновление строк с датой загрузки
MERGE INTO uploads t
USING uploads_stage s
ON (t.file_name = s.file_name)
WHEN MATCHED THEN
UPDATE SET t.loaded_at = SYSDATE
WHEN NOT MATCHED THEN
INSERT (file_name,loaded_at)
VALUES (s.file_name,SYSDATE)33. Наполнение справочника городов
MERGE INTO cities t
USING cities_stage s
ON (t.city_code = s.city_code)
WHEN MATCHED THEN
UPDATE SET t.city_name = s.city_name
WHEN NOT MATCHED THEN
INSERT (city_code,city_name)
VALUES (s.city_code,s.city_name)34. Пересборка связки код-описание
MERGE INTO reason_codes t
USING reason_codes_stage s
ON (t.reason_code = s.reason_code)
WHEN MATCHED THEN
UPDATE SET t.reason_text = s.reason_text
WHEN NOT MATCHED THEN
INSERT (reason_code,reason_text)
VALUES (s.reason_code,s.reason_text)35. Дельта по рабочим сменам
MERGE INTO shifts t
USING shifts_stage s
ON (t.shift_id = s.shift_id)
WHEN MATCHED THEN
UPDATE SET t.start_at = s.start_at,t.end_at = s.end_at
WHEN NOT MATCHED THEN
INSERT (shift_id,start_at,end_at)
VALUES (s.shift_id,s.start_at,s.end_at)36. Обновление валютного символа
MERGE INTO currencies t
USING currencies_stage s
ON (t.ccy = s.ccy)
WHEN MATCHED THEN
UPDATE SET t.symbol = s.symbol
WHEN NOT MATCHED THEN
INSERT (ccy,symbol)
VALUES (s.ccy,s.symbol)37. Синхронизация категорий товара
MERGE INTO categories t
USING categories_stage s
ON (t.category_id = s.category_id)
WHEN MATCHED THEN
UPDATE SET t.category_name = s.category_name
WHEN NOT MATCHED THEN
INSERT (category_id,category_name)
VALUES (s.category_id,s.category_name)38. Перенос данных из history в current
MERGE INTO current_balances t
USING history_balances s
ON (t.account_id = s.account_id)
WHEN MATCHED THEN
UPDATE SET t.balance = s.balance
WHEN NOT MATCHED THEN
INSERT (account_id,balance)
VALUES (s.account_id,s.balance)39. Обновление с вычислением VAT через выражение
MERGE INTO invoice_tax t
USING invoice_stage s
ON (t.invoice_id = s.invoice_id)
WHEN MATCHED THEN
UPDATE SET t.tax_amt = ROUND(s.net_amt * 0.2,2)
WHEN NOT MATCHED THEN
INSERT (invoice_id,tax_amt)
VALUES (s.invoice_id,ROUND(s.net_amt * 0.2,2))40. Источник как WITH-подзапрос
MERGE INTO kpi_daily t
USING (
WITH x AS (
SELECT dept_id,COUNT(*) AS cnt
FROM tasks
GROUP BY dept_id
)
SELECT dept_id,cnt FROM x
) s
ON (t.dept_id = s.dept_id)
WHEN MATCHED THEN
UPDATE SET t.task_cnt = s.cnt
WHEN NOT MATCHED THEN
INSERT (dept_id,task_cnt)
VALUES (s.dept_id,s.cnt)Еще 20 примеров
41. Синхронизация витрины заказов по последнему статусу
MERGE INTO order_status_mart t
USING (
SELECT order_id,status
FROM order_status_stage
) s
ON (t.order_id = s.order_id)
WHEN MATCHED THEN
UPDATE SET t.status = s.status
WHEN NOT MATCHED THEN
INSERT (order_id,status)
VALUES (s.order_id,s.status)42. Загрузка отчётной таблицы из агрегированного источника
MERGE INTO dept_sales t
USING (
SELECT dept_id,SUM(amount) AS total_amt
FROM sales_stage
GROUP BY dept_id
) s
ON (t.dept_id = s.dept_id)
WHEN MATCHED THEN
UPDATE SET t.total_amt = s.total_amt
WHEN NOT MATCHED THEN
INSERT (dept_id,total_amt)
VALUES (s.dept_id,s.total_amt)43. Слияние по дате и коду продукта
MERGE INTO day_sales t
USING day_sales_stage s
ON (t.sale_dt = s.sale_dt AND t.sku = s.sku)
WHEN MATCHED THEN
UPDATE SET t.qty = s.qty,t.amount = s.amount
WHEN NOT MATCHED THEN
INSERT (sale_dt,sku,qty,amount)
VALUES (s.sale_dt,s.sku,s.qty,s.amount)44. Обновление только активных строк в цели
MERGE INTO subscriptions t
USING subscriptions_stage s
ON (t.sub_id = s.sub_id)
WHEN MATCHED THEN
UPDATE SET t.plan_code = s.plan_code
WHERE t.is_active = 1
WHEN NOT MATCHED THEN
INSERT (sub_id,plan_code,is_active)
VALUES (s.sub_id,s.plan_code,1)45. Источник как фильтрованный набор последних событий
MERGE INTO last_events t
USING (
SELECT event_id,user_id,event_ts
FROM events_stage
WHERE event_ts >= TRUNC(SYSDATE)
) s
ON (t.event_id = s.event_id)
WHEN MATCHED THEN
UPDATE SET t.event_ts = s.event_ts
WHEN NOT MATCHED THEN
INSERT (event_id,user_id,event_ts)
VALUES (s.event_id,s.user_id,s.event_ts)46. Перенос топ-N строк через USING c FETCH FIRST
MERGE INTO top_customers t
USING (
SELECT customer_id,total_amt
FROM customer_totals_stage
ORDER BY total_amt DESC
FETCH FIRST 100 ROWS ONLY
) s
ON (t.customer_id = s.customer_id)
WHEN MATCHED THEN
UPDATE SET t.total_amt = s.total_amt
WHEN NOT MATCHED THEN
INSERT (customer_id,total_amt)
VALUES (s.customer_id,s.total_amt)47. Обновление кода региона с COALESCE
MERGE INTO clients t
USING clients_region_stage s
ON (t.client_id = s.client_id)
WHEN MATCHED THEN
UPDATE SET t.region_code = COALESCE(s.region_code,'NA')
WHEN NOT MATCHED THEN
INSERT (client_id,region_code)
VALUES (s.client_id,COALESCE(s.region_code,'NA'))48. Синхронизация профилей с timestamp обновления
MERGE INTO profiles t
USING profiles_stage s
ON (t.user_id = s.user_id)
WHEN MATCHED THEN
UPDATE SET t.bio = s.bio,t.updated_at = SYSTIMESTAMP
WHEN NOT MATCHED THEN
INSERT (user_id,bio,updated_at)
VALUES (s.user_id,s.bio,SYSTIMESTAMP)49. Источник как разбор JSON-потока
MERGE INTO api_cache t
USING (
SELECT JSON_VALUE(payload,'$.id') AS cache_key,
JSON_VALUE(payload,'$.name') AS cache_val
FROM api_stage
) s
ON (t.cache_key = s.cache_key)
WHEN MATCHED THEN
UPDATE SET t.cache_val = s.cache_val
WHEN NOT MATCHED THEN
INSERT (cache_key,cache_val)
VALUES (s.cache_key,s.cache_val)50. Условная чистка отменённых заявок
MERGE INTO applications t
USING applications_stage s
ON (t.app_id = s.app_id)
WHEN MATCHED THEN
UPDATE SET t.status = s.status
DELETE WHERE s.status = 'CANCELLED'
WHEN NOT MATCHED THEN
INSERT (app_id,status)
VALUES (s.app_id,s.status)51. Синхронизация менеджеров клиентов
MERGE INTO client_managers t
USING client_managers_stage s
ON (t.client_id = s.client_id)
WHEN MATCHED THEN
UPDATE SET t.manager_id = s.manager_id
WHEN NOT MATCHED THEN
INSERT (client_id,manager_id)
VALUES (s.client_id,s.manager_id)52. Поддержка очереди отправки уведомлений
MERGE INTO notif_queue t
USING notif_stage s
ON (t.msg_id = s.msg_id)
WHEN MATCHED THEN
UPDATE SET t.payload = s.payload,t.send_at = s.send_at
WHEN NOT MATCHED THEN
INSERT (msg_id,payload,send_at)
VALUES (s.msg_id,s.payload,s.send_at)53. Обновление имени и очистка пробелов через TRIM
MERGE INTO vendors t
USING vendors_stage s
ON (t.vendor_id = s.vendor_id)
WHEN MATCHED THEN
UPDATE SET t.vendor_name = TRIM(s.vendor_name)
WHEN NOT MATCHED THEN
INSERT (vendor_id,vendor_name)
VALUES (s.vendor_id,TRIM(s.vendor_name))54. Источник с DISTINCT перед слиянием
MERGE INTO email_list t
USING (
SELECT DISTINCT email
FROM email_stage
) s
ON (t.email = s.email)
WHEN NOT MATCHED THEN
INSERT (email)
VALUES (s.email)55. Слияние заказов с преобразованием даты
MERGE INTO shipped_orders t
USING shipped_orders_stage s
ON (t.order_id = s.order_id)
WHEN MATCHED THEN
UPDATE SET t.ship_date = TO_DATE(s.ship_date_txt,'YYYY-MM-DD')
WHEN NOT MATCHED THEN
INSERT (order_id,ship_date)
VALUES (s.order_id,TO_DATE(s.ship_date_txt,'YYYY-MM-DD'))56. Синхронизация кодов и флагов доступности
MERGE INTO sku_availability t
USING sku_availability_stage s
ON (t.sku = s.sku)
WHEN MATCHED THEN
UPDATE SET t.available_flag = s.available_flag
WHEN NOT MATCHED THEN
INSERT (sku,available_flag)
VALUES (s.sku,s.available_flag)57. Перезапись отчётной суммы за день
MERGE INTO daily_totals t
USING daily_totals_stage s
ON (t.rep_date = s.rep_date)
WHEN MATCHED THEN
UPDATE SET t.total_amt = s.total_amt
WHEN NOT MATCHED THEN
INSERT (rep_date,total_amt)
VALUES (s.rep_date,s.total_amt)58. Дельта по телефонным номерам
MERGE INTO phones t
USING phones_stage s
ON (t.user_id = s.user_id)
WHEN MATCHED THEN
UPDATE SET t.phone = s.phone
WHEN NOT MATCHED THEN
INSERT (user_id,phone)
VALUES (s.user_id,s.phone)59. Обновление флага удаления через CASE
MERGE INTO docs t
USING docs_stage s
ON (t.doc_id = s.doc_id)
WHEN MATCHED THEN
UPDATE SET t.is_deleted = CASE WHEN s.status = 'ARCHIVE' THEN 1 ELSE 0 END
WHEN NOT MATCHED THEN
INSERT (doc_id,is_deleted)
VALUES (s.doc_id,CASE WHEN s.status = 'ARCHIVE' THEN 1 ELSE 0 END)60. Источник как набор констант из DUAL для системных параметров
MERGE INTO app_params t
USING (
SELECT 'PAGE_SIZE' AS param_code,'50' AS param_value FROM DUAL
UNION ALL
SELECT 'THEME','dark' FROM DUAL
) s
ON (t.param_code = s.param_code)
WHEN MATCHED THEN
UPDATE SET t.param_value = s.param_value
WHEN NOT MATCHED THEN
INSERT (param_code,param_value)
VALUES (s.param_code,s.param_value)Еще 20 примеров
61. MERGE из внешней таблицы во внутреннюю
MERGE INTO ext_load_target t
USING ext_orders s
ON (t.order_id = s.order_id)
WHEN MATCHED THEN
UPDATE SET t.amount = s.amount
WHEN NOT MATCHED THEN
INSERT (order_id,amount)
VALUES (s.order_id,s.amount)62. Слияние по нормализованному телефону через REGEXP_REPLACE
MERGE INTO contact_book t
USING contact_stage s
ON (REGEXP_REPLACE(t.phone,'\D') = REGEXP_REPLACE(s.phone,'\D'))
WHEN MATCHED THEN
UPDATE SET t.owner_name = s.owner_name
WHEN NOT MATCHED THEN
INSERT (phone,owner_name)
VALUES (s.phone,s.owner_name)63. Источник как последний статус по user_id через ROW_NUMBER
MERGE INTO user_status t
USING (
SELECT user_id,status
FROM (
SELECT user_id,status,
ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY changed_at DESC) rn
FROM user_status_stage
)
WHERE rn = 1
) s
ON (t.user_id = s.user_id)
WHEN MATCHED THEN
UPDATE SET t.status = s.status
WHEN NOT MATCHED THEN
INSERT (user_id,status)
VALUES (s.user_id,s.status)64. Обновление минимальной цены через LEAST
MERGE INTO best_price t
USING price_feed s
ON (t.sku = s.sku)
WHEN MATCHED THEN
UPDATE SET t.price = LEAST(t.price,s.price)
WHEN NOT MATCHED THEN
INSERT (sku,price)
VALUES (s.sku,s.price)65. Обновление максимального рейтинга через GREATEST
MERGE INTO movie_scores t
USING movie_scores_stage s
ON (t.movie_id = s.movie_id)
WHEN MATCHED THEN
UPDATE SET t.rating = GREATEST(t.rating,s.rating)
WHEN NOT MATCHED THEN
INSERT (movie_id,rating)
VALUES (s.movie_id,s.rating)66. Слияние по месяцу через TRUNC(date,’MM’)
MERGE INTO month_totals t
USING month_totals_stage s
ON (TRUNC(t.rep_date,'MM') = TRUNC(s.rep_date,'MM'))
WHEN MATCHED THEN
UPDATE SET t.total_amt = s.total_amt
WHEN NOT MATCHED THEN
INSERT (rep_date,total_amt)
VALUES (s.rep_date,s.total_amt)67. Перенос имени в нормализованном виде
MERGE INTO person_dim t
USING person_stage s
ON (t.person_id = s.person_id)
WHEN MATCHED THEN
UPDATE SET t.full_name = INITCAP(LOWER(s.full_name))
WHEN NOT MATCHED THEN
INSERT (person_id,full_name)
VALUES (s.person_id,INITCAP(LOWER(s.full_name)))68. Слияние и вычисление age из даты рождения
MERGE INTO person_age t
USING person_stage s
ON (t.person_id = s.person_id)
WHEN MATCHED THEN
UPDATE SET t.age_years = TRUNC(MONTHS_BETWEEN(SYSDATE,s.birth_date)/12)
WHEN NOT MATCHED THEN
INSERT (person_id,age_years)
VALUES (s.person_id,TRUNC(MONTHS_BETWEEN(SYSDATE,s.birth_date)/12))69. Слияние по hash-ключу
MERGE INTO dedup_keys t
USING dedup_keys_stage s
ON (t.hash_key = s.hash_key)
WHEN MATCHED THEN
UPDATE SET t.last_seen = s.last_seen
WHEN NOT MATCHED THEN
INSERT (hash_key,last_seen)
VALUES (s.hash_key,s.last_seen)70. Поддержка справочника причин возврата
MERGE INTO refund_reasons t
USING refund_reasons_stage s
ON (t.reason_code = s.reason_code)
WHEN MATCHED THEN
UPDATE SET t.reason_text = s.reason_text
WHEN NOT MATCHED THEN
INSERT (reason_code,reason_text)
VALUES (s.reason_code,s.reason_text)71. Слияние с UPDATE нескольких числовых полей
MERGE INTO kpi_table t
USING kpi_stage s
ON (t.kpi_code = s.kpi_code)
WHEN MATCHED THEN
UPDATE SET t.plan_val = s.plan_val,t.fact_val = s.fact_val
WHEN NOT MATCHED THEN
INSERT (kpi_code,plan_val,fact_val)
VALUES (s.kpi_code,s.plan_val,s.fact_val)72. Слияние по географической паре country/city
MERGE INTO city_dim t
USING city_stage s
ON (t.country_code = s.country_code AND t.city_code = s.city_code)
WHEN MATCHED THEN
UPDATE SET t.city_name = s.city_name
WHEN NOT MATCHED THEN
INSERT (country_code,city_code,city_name)
VALUES (s.country_code,s.city_code,s.city_name)73. Обновление отчёта по загрузке файлов
MERGE INTO file_report t
USING file_report_stage s
ON (t.file_name = s.file_name)
WHEN MATCHED THEN
UPDATE SET t.row_cnt = s.row_cnt,t.loaded_at = s.loaded_at
WHEN NOT MATCHED THEN
INSERT (file_name,row_cnt,loaded_at)
VALUES (s.file_name,s.row_cnt,s.loaded_at)74. Синхронизация промо-кампаний
MERGE INTO campaigns t
USING campaigns_stage s
ON (t.campaign_id = s.campaign_id)
WHEN MATCHED THEN
UPDATE SET t.start_at = s.start_at,t.end_at = s.end_at
WHEN NOT MATCHED THEN
INSERT (campaign_id,start_at,end_at)
VALUES (s.campaign_id,s.start_at,s.end_at)75. Поддержка истории цен по последней записи
MERGE INTO current_price t
USING (
SELECT product_id,price
FROM (
SELECT product_id,price,
ROW_NUMBER() OVER(PARTITION BY product_id ORDER BY price_date DESC) rn
FROM price_history_stage
)
WHERE rn = 1
) s
ON (t.product_id = s.product_id)
WHEN MATCHED THEN
UPDATE SET t.price = s.price
WHEN NOT MATCHED THEN
INSERT (product_id,price)
VALUES (s.product_id,s.price)76. Витрина активных клиентов по агрегату заказов
MERGE INTO active_clients t
USING (
SELECT user_id,COUNT(*) AS orders_cnt
FROM orders
GROUP BY user_id
) s
ON (t.user_id = s.user_id)
WHEN MATCHED THEN
UPDATE SET t.orders_cnt = s.orders_cnt
WHEN NOT MATCHED THEN
INSERT (user_id,orders_cnt)
VALUES (s.user_id,s.orders_cnt)77. Слияние и удаление устаревших уведомлений
MERGE INTO notif_state t
USING notif_state_stage s
ON (t.msg_id = s.msg_id)
WHEN MATCHED THEN
UPDATE SET t.status = s.status
DELETE WHERE s.status = 'EXPIRED'
WHEN NOT MATCHED THEN
INSERT (msg_id,status)
VALUES (s.msg_id,s.status)78. Обновление warehouse-location связки
MERGE INTO stock_locations t
USING stock_locations_stage s
ON (t.sku = s.sku AND t.wh_id = s.wh_id)
WHEN MATCHED THEN
UPDATE SET t.loc_code = s.loc_code
WHEN NOT MATCHED THEN
INSERT (sku,wh_id,loc_code)
VALUES (s.sku,s.wh_id,s.loc_code)79. Поддержка справочника tax rate
MERGE INTO tax_rates t
USING tax_rates_stage s
ON (t.country_code = s.country_code)
WHEN MATCHED THEN
UPDATE SET t.tax_rate = s.tax_rate
WHEN NOT MATCHED THEN
INSERT (country_code,tax_rate)
VALUES (s.country_code,s.tax_rate)80. Слияние со статусом на основе SIGN
MERGE INTO payment_sign t
USING payment_sign_stage s
ON (t.payment_id = s.payment_id)
WHEN MATCHED THEN
UPDATE SET t.sign_code = SIGN(s.amount)
WHEN NOT MATCHED THEN
INSERT (payment_id,sign_code)
VALUES (s.payment_id,SIGN(s.amount))Еще 20 примеров
81. MERGE и вычисление slug из заголовка
MERGE INTO article_slug t
USING article_stage s
ON (t.article_id = s.article_id)
WHEN MATCHED THEN
UPDATE SET t.slug = LOWER(REPLACE(TRIM(s.title),' ','-'))
WHEN NOT MATCHED THEN
INSERT (article_id,slug)
VALUES (s.article_id,LOWER(REPLACE(TRIM(s.title),' ','-')))82. Поддержка blacklist по email
MERGE INTO blacklist t
USING blacklist_feed s
ON (LOWER(t.email) = LOWER(s.email))
WHEN MATCHED THEN
UPDATE SET t.reason = s.reason
WHEN NOT MATCHED THEN
INSERT (email,reason)
VALUES (s.email,s.reason)83. Синхронизация планов подписки
MERGE INTO plans t
USING plans_stage s
ON (t.plan_code = s.plan_code)
WHEN MATCHED THEN
UPDATE SET t.plan_name = s.plan_name,t.month_fee = s.month_fee
WHEN NOT MATCHED THEN
INSERT (plan_code,plan_name,month_fee)
VALUES (s.plan_code,s.plan_name,s.month_fee)84. Перенос счетчиков событий с увеличением значения
MERGE INTO event_counters t
USING event_counters_stage s
ON (t.event_code = s.event_code)
WHEN MATCHED THEN
UPDATE SET t.cnt = t.cnt + s.cnt
WHEN NOT MATCHED THEN
INSERT (event_code,cnt)
VALUES (s.event_code,s.cnt)85. Слияние с trim и lower для нормализации логина
MERGE INTO user_login t
USING user_login_stage s
ON (LOWER(TRIM(t.login)) = LOWER(TRIM(s.login)))
WHEN MATCHED THEN
UPDATE SET t.last_seen = s.last_seen
WHEN NOT MATCHED THEN
INSERT (login,last_seen)
VALUES (LOWER(TRIM(s.login)),s.last_seen)86. Обновление флагов по результату regexp-проверки
MERGE INTO contacts_valid t
USING contacts_stage s
ON (t.contact_id = s.contact_id)
WHEN MATCHED THEN
UPDATE SET t.is_valid = CASE WHEN REGEXP_LIKE(s.email,'^[^@]+@[^@]+\.[^@]+$') THEN 1 ELSE 0 END
WHEN NOT MATCHED THEN
INSERT (contact_id,is_valid)
VALUES (s.contact_id,CASE WHEN REGEXP_LIKE(s.email,'^[^@]+@[^@]+\.[^@]+$') THEN 1 ELSE 0 END)87. Поддержка daily snapshot из DUAL-константы
MERGE INTO run_log t
USING (
SELECT TRUNC(SYSDATE) AS run_dt,'OK' AS run_status
FROM DUAL
) s
ON (t.run_dt = s.run_dt)
WHEN MATCHED THEN
UPDATE SET t.run_status = s.run_status
WHEN NOT MATCHED THEN
INSERT (run_dt,run_status)
VALUES (s.run_dt,s.run_status)88. Слияние каналов продаж
MERGE INTO sales_channels t
USING sales_channels_stage s
ON (t.channel_code = s.channel_code)
WHEN MATCHED THEN
UPDATE SET t.channel_name = s.channel_name
WHEN NOT MATCHED THEN
INSERT (channel_code,channel_name)
VALUES (s.channel_code,s.channel_name)89. Обновление last_seen и удаление заброшенных сессий
MERGE INTO user_sessions t
USING user_sessions_stage s
ON (t.session_id = s.session_id)
WHEN MATCHED THEN
UPDATE SET t.last_seen = s.last_seen
DELETE WHERE s.last_seen < SYSDATE - 60
WHEN NOT MATCHED THEN
INSERT (session_id,last_seen)
VALUES (s.session_id,s.last_seen)90. Витрина популярных запросов через агрегированный источник
MERGE INTO search_top t
USING (
SELECT query_text,COUNT(*) AS cnt
FROM search_log_stage
GROUP BY query_text
) s
ON (t.query_text = s.query_text)
WHEN MATCHED THEN
UPDATE SET t.cnt = s.cnt
WHEN NOT MATCHED THEN
INSERT (query_text,cnt)
VALUES (s.query_text,s.cnt)91. Синхронизация справочника ошибок
MERGE INTO error_dict t
USING error_dict_stage s
ON (t.err_code = s.err_code)
WHEN MATCHED THEN
UPDATE SET t.err_text = s.err_text
WHEN NOT MATCHED THEN
INSERT (err_code,err_text)
VALUES (s.err_code,s.err_text)92. Слияние с обрезкой длинной строки через SUBSTR
MERGE INTO notes t
USING notes_stage s
ON (t.note_id = s.note_id)
WHEN MATCHED THEN
UPDATE SET t.note_text = SUBSTR(s.note_text,1,200)
WHEN NOT MATCHED THEN
INSERT (note_id,note_text)
VALUES (s.note_id,SUBSTR(s.note_text,1,200))93. Слияние customer score с округлением до целого
MERGE INTO customer_score t
USING customer_score_stage s
ON (t.customer_id = s.customer_id)
WHEN MATCHED THEN
UPDATE SET t.score = ROUND(s.score)
WHEN NOT MATCHED THEN
INSERT (customer_id,score)
VALUES (s.customer_id,ROUND(s.score))94. Обновление времени доставки по маршруту
MERGE INTO route_eta t
USING route_eta_stage s
ON (t.route_id = s.route_id)
WHEN MATCHED THEN
UPDATE SET t.eta_min = s.eta_min
WHEN NOT MATCHED THEN
INSERT (route_id,eta_min)
VALUES (s.route_id,s.eta_min)95. Поддержка списка разрешённых IP
MERGE INTO allow_ip t
USING allow_ip_stage s
ON (t.ip_addr = s.ip_addr)
WHEN MATCHED THEN
UPDATE SET t.comment_text = s.comment_text
WHEN NOT MATCHED THEN
INSERT (ip_addr,comment_text)
VALUES (s.ip_addr,s.comment_text)96. Слияние начислений бонусов
MERGE INTO bonus_accrual t
USING bonus_accrual_stage s
ON (t.user_id = s.user_id AND t.accrual_dt = s.accrual_dt)
WHEN MATCHED THEN
UPDATE SET t.bonus_amt = s.bonus_amt
WHEN NOT MATCHED THEN
INSERT (user_id,accrual_dt,bonus_amt)
VALUES (s.user_id,s.accrual_dt,s.bonus_amt)97. Обновление текущего тарифа абонента
MERGE INTO current_tariff t
USING tariff_stage s
ON (t.sub_id = s.sub_id)
WHEN MATCHED THEN
UPDATE SET t.tariff_code = s.tariff_code
WHEN NOT MATCHED THEN
INSERT (sub_id,tariff_code)
VALUES (s.sub_id,s.tariff_code)98. Слияние и хранение хэша полезной нагрузки
MERGE INTO payload_hash t
USING payload_stage s
ON (t.row_id_src = s.row_id_src)
WHEN MATCHED THEN
UPDATE SET t.hash_val = STANDARD_HASH(s.payload,'SHA256')
WHEN NOT MATCHED THEN
INSERT (row_id_src,hash_val)
VALUES (s.row_id_src,STANDARD_HASH(s.payload,'SHA256'))99. Итоговая upsert-загрузка в мастер-таблицу
MERGE INTO master_table t
USING master_stage s
ON (t.id = s.id)
WHEN MATCHED THEN
UPDATE SET t.attr1 = s.attr1,t.attr2 = s.attr2,t.updated_at = SYSDATE
WHEN NOT MATCHED THEN
INSERT (id,attr1,attr2,updated_at)
VALUES (s.id,s.attr1,s.attr2,SYSDATE)100. Финальный пример: полный шаблон с UPDATE, INSERT и DELETE WHERE
MERGE INTO sync_target t
USING sync_stage s
ON (t.biz_key = s.biz_key)
WHEN MATCHED THEN
UPDATE SET t.attr_val = s.attr_val,t.sync_dt = SYSDATE
DELETE WHERE s.is_deleted = 1
WHEN NOT MATCHED THEN
INSERT (biz_key,attr_val,sync_dt)
VALUES (s.biz_key,s.attr_val,SYSDATE)Заключение
MERGE — это один из самых взрослых операторов Oracle SQL.
Он учит думать не отдельными действиями, а целым сценарием синхронизации.
Если тебе близка идея «один чистый оператор вместо мешанины из IF EXISTS, UPDATE и INSERT»,
то ты с ним точно подружишься.
Когда данные живут в движении, MERGE помогает держать систему в порядке без лишнего шума.
Официальная документация Oracle:
https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/MERGE.html
🔜 Следующая статья:
CONNECT_BY_ISLEAF в Oracle SQL — как определить конечные узлы в иерархических запросах