MERGE в Oracle SQL — как объединить INSERT и UPDATE

🟢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.status

23. Источник как подзапрос с 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 — как определить конечные узлы в иерархических запросах


 

Понравилась статья? Поделиться с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии