Instrukcja Merge w T-SQL

Instrukcja MERGE to sposób, aby jednym zapytaniem wykonać 3 operacje naraz: INSERT, UPDATE, DELETE.

Najczęściej używa się jej do synchronizacji danych między dwiema tabelami:
- źródłową (np. dane z importu)
- docelową (np. tabela produkcyjna)

1. Przykład z życia – aktualizacja klientów po imporcie

Załóżmy, że codziennie ładujesz dane klientów do tabeli tymczasowej #Klienci_Import, a Twoja tabela docelowa to Klienci. Chcesz:
- zaktualizować dane, które się zmieniły,
- dodać nowych klientów,
- usunąć tych, których już nie ma w CRM.

Kod SQL:
--dane

drop table if exists Klienci;
drop table if exists #Klienci_Import;

create table Klienci (KlientID int IDENTITY(1,1) primary key not null, Imie varchar(20),Nazwisko varchar(20));
create table #Klienci_Import (KlientID int IDENTITY(1,1) primary key not null, Imie varchar(20),Nazwisko varchar(20));

--tabela bazowa i co sie bedzie dziac
insert into Klienci (Imie,Nazwisko) select 'Jarek','Kowalski' --bez zmian
insert into Klienci (Imie,Nazwisko) select 'Mirek','Maliniak' --to update
insert into Klienci (Imie,Nazwisko) select 'Wacek','Placek' --to del

--aktualna lista
insert into #Klienci_Import (Imie,Nazwisko) select 'Jarek','Kowalski'
insert into #Klienci_Import (Imie,Nazwisko) select 'Mirosław','Malinowski'

--merge na tabeli #Klienci na podstawie #Klienci_Import
MERGE Klienci WITH (HOLDLOCK) AS Target
USING #Klienci_Import AS Source
ON Target.KlientID = Source.KlientID
--upd
WHEN MATCHED AND (Target.Imie <> Source.Imie OR Target.Nazwisko <> Source.Nazwisko)
THEN UPDATE SET Target.Imie = Source.Imie, Target.Nazwisko = Source.Nazwisko
--ins
WHEN NOT MATCHED BY TARGET THEN
    INSERT (Imie, Nazwisko)
    VALUES (Source.Imie, Source.Nazwisko)
--del
WHEN NOT MATCHED BY SOURCE THEN
    DELETE;

 --check
 select * from Klienci

Results:
KlientID    Imie                 Nazwisko
----------- -------------------- --------------------
1           Jarek                Kowalski
2           Mirosław             Malinowski

2. Co tu się dzieje

  1. Klienci_Import – to źródło danych (np. nowy import z CRM).
  2. Klienci – to tabela docelowa z aktualnymi danymi.
  3. SQL Server porównuje rekordy po KlientID:
    • jeśli rekord istnieje w obu tabelach, ale dane się różnią → robi UPDATE,
    • jeśli rekord istnieje tylko w źródle → robi INSERT,
    • jeśli rekord istnieje tylko w docelowej → robi DELETE.  

3. Dlaczego WITH (HOLDLOCK) jest ważne

HOLDLOCK mówi SQL Serverowi, żeby potraktował całą tabelę docelową jakby działała w trybie SERIALIZABLE – czyli żadna inna transakcja nie może jej zmienić w trakcie wykonywania MERGE.
Dzięki temu masz gwarancję, że:

  • dane nie zostaną przypadkowo nadpisane przez równoległe procesy,
  • MERGE wykona się w sposób spójny i przewidywalny.

Bez tej blokady, przy dużym ruchu w bazie, możesz złapać tzw. race condition, czyli błędną aktualizację spowodowaną zmianą danych w trakcie operacji.

4. Po co ten warunek w WHEN MATCHED

Domyślnie MERGE wykona UPDATE nawet wtedy, gdy wartości kolumn są identyczne.
To powoduje zbędne zapisy do loga, blokady, a czasem uruchamianie triggerów.
Dlatego warto dopisać warunek:

WHEN MATCHED AND (Target.Kolumna <> Source.Kolumna)

Dzięki temu aktualizujesz tylko faktyczne zmiany.

5. Podsumowanie

MERGE to potężne narzędzie, które pozwala w prosty sposób utrzymać dane zsynchronizowane między dwiema tabelami.
Z HOLDLOCK masz bezpieczeństwo, a z warunkiem w WHEN MATCHED – wydajność i czystość loga transakcyjnego.

To rozwiązanie sprawdza się idealnie przy:

  • codziennych importach danych,
  • procesach ETL,
  • integracjach z systemami zewnętrznymi.

Jedno polecenie, pełna kontrola nad danymi.