Condividi tramite


Inserimento, aggiornamento ed eliminazione dei dati con MERGE

In SQL Server 2008 è possibile eseguire operazioni di inserimento, aggiornamento o eliminazione in un'unica istruzione utilizzando l'istruzione MERGE che consente di unire in join un'origine dati con una tabella o vista di destinazione e successivamente di eseguire più azioni sulla destinazione in base ai risultati del join. È possibile ad esempio utilizzare l'istruzione MERGE per effettuare le operazioni seguenti:

  • Inserire o aggiornare righe di una tabella di destinazione in modo condizionale.

    Se la riga esiste nella tabella di destinazione, aggiornare una o più colonne. In caso contrario, inserire i dati in una nuova riga.

  • Sincronizzare due tabelle.

    Inserire, aggiornare o eliminare le righe di una tabella di destinazione in base alle differenze con i dati di origine.

La sintassi MERGE è costituita da cinque clausole primarie:

  • Clausola MERGE che specifica la tabella o la vista di destinazione delle operazioni di inserimento, aggiornamento o eliminazione.

  • Clausola USING che specifica l'origine dati da unire in join con la destinazione.

  • Clausola ON che specifica le condizioni di join che determinano il punto in cui origine e destinazione corrispondono.

  • Clausole WHEN (WHEN MATCHED, WHEN NOT MATCHED BY TARGET e WHEN NOT MATCHED BY SOURCE) che specificano le azioni da eseguire in base ai risultati della clausola ON e a qualsiasi criterio di ricerca aggiuntivo specificato nelle clausole WHEN.

  • Clausola OUTPUT che restituisce una riga per ogni riga della destinazione aggiornata, inserita o eliminata.

Per informazioni dettagliate sulla sintassi e sulle regole, vedere MERGE (Transact-SQL).

Specifica delle condizioni di ricerca per l'origine e la destinazione

È importante comprendere il modo in cui i dati di origine e di destinazione vengono uniti in un unico flusso di input e il modo in cui è possibile utilizzare criteri di ricerca aggiuntivi per filtrare correttamente le righe non necessarie. In caso contrario, i criteri di ricerca aggiuntivi potrebbero essere specificati in modo da produrre risultati non corretti.

Le righe presenti nell'origine vengono confrontate con quelle della destinazione in base al predicato di join specificato nella clausola ON. Il risultato è un flusso di input combinato, in base al quale un'operazione di inserimento, aggiornamento o eliminazione viene eseguita per ogni riga di input. In base alle clausole WHEN specificate nell'istruzione, la riga di input potrebbe essere uno degli elementi seguenti:

  • Coppia corrispondente costituita da una riga della destinazione e da una riga dell'origine. Questo risultato viene restituito dalla clausola WHEN MATCHED.

  • Riga dell'origine per cui non esiste alcuna riga corrispondente nella destinazione. Questo risultato viene restituito dalla clausola WHEN NOT MATCHED BY TARGET.

  • Riga della destinazione per cui non esiste alcuna riga corrispondente nell'origine. Questo risultato viene restituito dalla clausola WHEN NOT MATCHED BY SOURCE.

La combinazione di clausole WHEN specificate nell'istruzione MERGE determina il tipo di join implementato da Query Processor e influisce sul flusso di input risultante. Questa situazione viene illustrata dalle tabelle e dai dati di origine e di destinazione dell'esempio seguente.

USE tempdb;
GO
CREATE TABLE dbo.Target(EmployeeID int, EmployeeName varchar(10), 
     CONSTRAINT Target_PK PRIMARY KEY(EmployeeID));
CREATE TABLE dbo.Source(EmployeeID int, EmployeeName varchar(10), 
     CONSTRAINT Source_PK PRIMARY KEY(EmployeeID));
GO
INSERT dbo.Target(EmployeeID, EmployeeName) VALUES(100, 'Mary');
INSERT dbo.Target(EmployeeID, EmployeeName) VALUES(101, 'Sara');
INSERT dbo.Target(EmployeeID, EmployeeName) VALUES(102, 'Stefano');

GO
INSERT dbo.Source(EmployeeID, EmployeeName) Values(103, 'Bob');
INSERT dbo.Source(EmployeeID, EmployeeName) Values(104, 'Steve');
GO

Nella tabella seguente vengono elencati i possibili tipi di join e viene indicato il momento in cui ogni tipo viene implementato da Query Optimizer. Nella tabella viene illustrato inoltre il flusso di input risultante per le tabelle di origine e di destinazione dell'esempio quando i criteri di ricerca per la corrispondenza tra i dati di origine e di destinazione corrispondono a Source.EmployeeID = Target.EmployeeID.

Tipo di join

Implementazione

Risultati del flusso di input di esempio

INNER JOIN

La clausola WHEN MATCHED è l'unica clausola WHEN specificata.

SrcEmpID SrcName TrgEmpID TrgName

-------- ------- -------- -------

NULL     NULL    NULL     NULL

LEFT OUTER JOIN

La clausola WHEN NOT MATCHED BY TARGET è specificata, ma la clausola WHEN NOT MATCHED BY SOURCE non è specificata. La clausola WHEN MATCHED può essere specificata o meno.

SrcEmpID SrcName TrgEmpID TrgName

-------- ------- -------- -------100      Mary    NULL     NULL

101      Sara    NULL     NULL

102      Stefano NULL     NULL

RIGHT OUTER JOIN

Le clausole WHEN MATCHED e WHEN NOT MATCHED BY SOURCE sono specificate, ma la clausola WHEN NOT MATCHED BY TARGET non è specificata.

SrcEmpID SrcName TrgEmpID TrgName

-------- ------- -------- -------NULL     NULL    103      Bob

NULL     NULL    104      Steve

FULL OUTER JOIN

Le clausole WHEN NOT MATCHED BY TARGET e WHEN NOT MATCHED BY SOURCE sono specificate. La clausola WHEN MATCHED può essere specificata o meno.

SrcEmpID SrcName TrgEmpID TrgName

-------- ------- -------- -------100      Mary    NULL     NULL

101      Sara    NULL     NULL

102      Stefano NULL     NULL

NULL     NULL    103      Bob

NULL     NULL    104      Steve

ANTI SEMI JOIN

La clausola WHEN NOT MATCHED BY SOURCE è l'unica clausola WHEN specificata.

TrgEmpID TrgName

-------- -------

100      Mary

101      Sara

102      Stefano

I risultati del flusso di input di esempio illustrano la relativa dipendenza dalla combinazione di clausole WHEN. Si supponga ora che si desideri eseguire le azioni riportate di seguito nella tabella di destinazione in base a tale flusso di input:

  • Inserire le righe della tabella di origine quando l'ID dipendente non esiste nella tabella di destinazione e il nome del dipendente di origine inizia con "S".

  • Eliminare le righe della tabella di destinazione quando il nome del dipendente di destinazione inizia con "S" e l'ID dipendente non esiste nella tabella di origine.

Per eseguire queste azioni, è necessario utilizzare le clausole WHEN seguenti:

  • WHEN NOT MATCHED BY TARGET THEN INSERT

  • WHEN NOT MATCHED BY SOURCE THEN DELETE

Come descritto nella tabella precedente, quando sono specificate entrambe le clausole WHEN NOT MATCHED il flusso di input risultante è rappresentato da un join di tipo full outer join alle tabelle di origine e di destinazione. Una volta che i risultati del flusso di input sono noti, considerare il modo in cui le azioni di inserimento, aggiornamento ed eliminazione verranno applicate al flusso di input.

Come indicato in precedenza, le clausole WHEN specificano le azioni da eseguire in base ai risultati della clausola ON e a qualsiasi criterio di ricerca aggiuntivo specificato nelle clausole WHEN. In molti casi le condizioni di ricerca specificate nella clausola ON producono il flusso di input richiesto. Nello scenario di esempio, tuttavia, per eseguire le azioni di inserimento ed eliminazione è necessario utilizzare filtri aggiuntivi per limitare le righe interessate a quelle in cui è presente un nome di dipendente che inizia con "S". Nell'esempio seguente le condizioni relative ai filtri vengono applicate a WHEN NOT MATCHED BY TARGET e WHEN NOT MATCHED BY SOURCE. L'output dall'istruzione illustra che le righe previste rispetto al flusso di input vengono corrette, inserite o eliminate.

-- MERGE statement with the join conditions specified correctly.
USE tempdb;
GO
BEGIN TRAN;
MERGE Target AS T
USING Source AS S
ON (T.EmployeeID = S.EmployeeID) 
WHEN NOT MATCHED BY TARGET AND S.EmployeeName LIKE 'S%' 
    THEN INSERT(EmployeeID, EmployeeName) VALUES(S.EmployeeID, S.EmployeeName)
WHEN MATCHED 
    THEN UPDATE SET T.EmployeeName = S.EmployeeName
WHEN NOT MATCHED BY SOURCE AND T.EmployeeName LIKE 'S%'
    THEN DELETE 
OUTPUT $action, inserted.*, deleted.*;
ROLLBACK TRAN;
GO 

Di seguito sono riportati i risultati della clausola OUTPUT.

$action   EmployeeID  EmployeeName EmployeeID  EmployeeName

--------- ----------- ------------ ----------- ------------

DELETE    NULL        NULL         101         Sara

DELETE    NULL        NULL         102         Stefano

INSERT    104         Steve        NULL        NULL

(3 row(s) affected)

Se si riduce il numero di righe nel flusso di input in un punto precedente del processo specificando la condizione di ricerca aggiuntiva nella clausola ON, ad esempio ON Source.EmployeeID = Target.EmployeeID AND EmployeeName LIKE 'S%', potrebbe sembrare che le prestazioni delle query aumentino. Questa operazione tuttavia può provocare risultati imprevisti e non corretti. Poiché le condizioni di ricerca aggiuntive specificate nella clausola ON non vengono utilizzate per la corrispondenza tra i dati di origine e di destinazione, tali condizioni potrebbero essere applicate in modo non corretto.

Nell'esempio seguente viene illustrato il modo in cui possono verificarsi risultati non corretti. La condizione di ricerca per la corrispondenza tra le tabelle di origine e di destinazione e quella aggiuntiva utilizzata per filtrare le righe vengono entrambe specificate nella clausola ON. Poiché la condizione di ricerca aggiuntiva non è necessaria per determinare la corrispondenza tra l'origine e la destinazione, le azioni di inserimento ed eliminazione vengono applicate a tutte le righe di input. La condizione di filtro EmployeeName LIKE 'S%' viene ignorata. Quando l'istruzione viene eseguita, l'output delle tabelle inserted e deleted illustra che due righe sono state modificate in modo non corretto, poiché Mary è stata eliminata dalla tabella di destinazione e Bob è stato inserito erroneamente.

-- MERGE statement with join conditions that produce unexpected results.
USE tempdb;
GO
BEGIN TRAN;
MERGE Target AS T
USING Source AS S
ON (T.EmployeeID = S.EmployeeID AND T.EmployeeName LIKE 'S%' 
    AND S.EmployeeName LIKE 'S%' )
WHEN NOT MATCHED BY TARGET
    THEN INSERT(EmployeeID, EmployeeName) VALUES(S.EmployeeID, S.EmployeeName)
WHEN MATCHED 
    THEN UPDATE SET T.EmployeeName = S.EmployeeName
WHEN NOT MATCHED BY SOURCE
    THEN DELETE
OUTPUT $action, Inserted.*, Deleted.*;
ROLLBACK TRAN;
GO

Di seguito sono riportati i risultati della clausola OUTPUT.

$action   EmployeeID  EmployeeName EmployeeID  EmployeeName

--------- ----------- ------------ ----------- ------------

DELETE    NULL        NULL         100         Mary

DELETE    NULL        NULL         101         Sara

DELETE    NULL        NULL         102         Stefano

INSERT    103         Bob          NULL        NULL

INSERT    104         Steve        NULL        NULL

(5 row(s) affected)

Linee guida per le condizioni di ricerca

Le condizioni di ricerca utilizzate per confrontare le righe di origine e di destinazione e le condizioni di ricerca aggiuntive utilizzate per filtrare le righe dell'origine o della destinazione devono essere specificate in modo corretto per garantire la restituzione di risultati corretti. Si consiglia di attenersi alle linee guida seguenti:

  • Specificare nella clausola ON <merge_search_condition> solo condizioni di ricerca che determinano i criteri per la corrispondenza dei dati nelle tabelle di origine e di destinazione, ovvero specificare solo colonne della tabella di destinazione confrontate con quelle corrispondenti della tabella di origine.

  • Non includere confronti con altri valori, ad esempio una costante.

Per filtrare le righe delle tabelle di origine o di destinazione, effettuare una delle operazioni seguenti:

  • Specificare la condizione di ricerca per applicare il filtro alla riga nella clausola WHEN appropriata, ad esempio WHEN NOT MATCHED AND S.EmployeeName LIKE 'S%' THEN INSERT....

  • Definire una vista sull'origine o sulla destinazione che restituisca le righe filtrate e faccia riferimento alla vista come la tabella di origine o di destinazione. Se la vista viene definita sulla tabella di destinazione, qualsiasi azione eseguita su tale vista deve soddisfare le condizioni per l'aggiornamento delle viste. Per ulteriori informazioni sull'aggiornamento di dati tramite una vista, vedere Modifica di dati tramite una vista.

  • Utilizzare la clausola WITH <common table expression> per filtrare le righe delle tabelle di origine o di destinazione. Questo metodo è analogo alla specifica di criteri di ricerca aggiuntivi nella clausola ON e può produrre risultati non corretti. Si consiglia di evitare l'utilizzo di questo metodo o di eseguirne un test accurato prima di implementarlo.

Esempi

A. Utilizzo di un'istruzione MERGE semplice per eseguire operazioni INSERT e UPDATE

Si supponga di disporre di una tabella FactBuyingHabits in un database del data warehouse che tenga traccia della data più recente in cui ogni cliente ha acquistato un prodotto specifico. In una seconda tabella, Purchases, in un database OLTP vengono registrati gli acquisti effettuati durante una determinata settimana. Ogni settimana si desidera aggiungere righe di prodotti che clienti specifici non hanno mai acquistato in precedenza dalla tabella Purchases alla tabella FactBuyingHabits. Per righe di clienti che acquistano prodotti che hanno già acquistato in precedenza, si desidera semplicemente aggiornare la data di acquisto nella tabella FactBuyingHabits. È possibile eseguire queste operazioni INSERT e UPDATE in un'unica istruzione utilizzando MERGE.

Nell'esempio seguente vengono innanzitutto create le tabelle Purchases e FactBuyingHabits in cui vengono inseriti successivamente alcuni dati di esempio. Poiché le prestazioni delle istruzioni MERGE migliorano quando gli indici UNIQUE vengono creati nella chiave di join, gli indici cluster vengono creati specificando un vincolo PRIMARY KEY nella colonna ProductID di entrambe le tabelle.

In questo esempio Purchases contiene gli acquisti della settimana del 21 agosto 2006. FactBuyingHabits contiene gli acquisti della settimana precedente. Normalmente, questa tabella verrebbe popolata con righe risalenti a molto tempo prima.

USE AdventureWorks2008R2;
GO
IF OBJECT_ID (N'dbo.Purchases', N'U') IS NOT NULL 
    DROP TABLE dbo.Purchases;
GO
CREATE TABLE dbo.Purchases (
    ProductID int, CustomerID int, PurchaseDate datetime, 
    CONSTRAINT PK_PurchProdID PRIMARY KEY(ProductID,CustomerID));
GO
INSERT INTO dbo.Purchases VALUES(707, 11794, '20060821'),
(707, 15160, '20060825'),(708, 18529, '20060821'),
(711, 11794, '20060821'),(711, 19585, '20060822'),
(712, 14680, '20060825'),(712, 21524, '20060825'),
(712, 19072, '20060821'),(870, 15160, '20060823'),
(870, 11927, '20060824'),(870, 18749, '20060825');
GO
IF OBJECT_ID (N'dbo.FactBuyingHabits', N'U') IS NOT NULL 
    DROP TABLE dbo.FactBuyingHabits;
GO
CREATE TABLE dbo.FactBuyingHabits (
    ProductID int, CustomerID int, LastPurchaseDate datetime, 
    CONSTRAINT PK_FactProdID PRIMARY KEY(ProductID,CustomerID));
GO
INSERT INTO dbo.FactBuyingHabits VALUES(707, 11794, '20060814'),
(707, 18178, '20060818'),(864, 14114, '20060818'),
(866, 13350, '20060818'),(866, 20201, '20060815'),
(867, 20201, '20060814'),(869, 19893, '20060815'),
(870, 17151, '20060818'),(870, 15160, '20060817'),
(871, 21717, '20060817'),(871, 21163, '20060815'),
(871, 13350, '20060815'),(873, 23381, '20060815');
GO

Le tabelle vengono quindi popolate con i dati seguenti:

dbo.Purchases

ProductID   CustomerID  PurchaseDate

----------- ----------- -----------------------

707         11794       2006-08-20 00:00:00.000

707         15160       2006-08-25 00:00:00.000

708         18529       2006-08-21 00:00:00.000

711         11794       2006-08-20 00:00:00.000

711         19585       2006-08-22 00:00:00.000

712         14680       2006-08-26 00:00:00.000

712         21524       2006-08-26 00:00:00.000

712         19072       2006-08-20 00:00:00.000

870         15160       2006-08-23 00:00:00.000

870         11927       2006-08-24 00:00:00.000

870         18749       2006-08-25 00:00:00.000

dbo.FactBuyingHabits

ProductID   CustomerID  LastPurchaseDate

----------- ----------- -----------------------

707         11794       2006-08-14 00:00:00.000

707         18178       2006-08-18 00:00:00.000

864         14114       2006-08-18 00:00:00.000

866         13350       2006-08-18 00:00:00.000

866         20201       2006-08-15 00:00:00.000

867         20201       2006-08-14 00:00:00.000

869         19893       2006-08-15 00:00:00.000

870         17151       2006-08-18 00:00:00.000

870         15160       2006-08-17 00:00:00.000

871         21717       2006-08-17 00:00:00.000

871         21163       2006-08-15 00:00:00.000

871         13350       2006-08-15 00:00:00.000

873         23381       2006-08-15 00:00:00.000

Sono presenti due righe relative ai prodotti e ai clienti comuni a entrambe le tabelle. Il cliente 11794 ha acquistato il prodotto 707 durante la settimana corrente e in quella precedente e, in modo analogo, il cliente 15160 ha acquistato il prodotto 870. Per queste righe, viene aggiornata la tabella FactBuyingHabits con la data registrata per tali acquisti in Purchases utilizzando la clausola WHEN MATCHED THEN. Tutte le altre righe vengono inserite in FactBuyingHabits utilizzando la clausola WHEN NOT MATCHED THEN.

MERGE dbo.FactBuyingHabits AS Target
USING (SELECT CustomerID, ProductID, PurchaseDate FROM dbo.Purchases) AS Source
ON (Target.ProductID = Source.ProductID AND Target.CustomerID = Source.CustomerID)
WHEN MATCHED THEN
    UPDATE SET Target.LastPurchaseDate = Source.PurchaseDate
WHEN NOT MATCHED BY TARGET THEN
    INSERT (CustomerID, ProductID, LastPurchaseDate)
    VALUES (Source.CustomerID, Source.ProductID, Source.PurchaseDate)
OUTPUT $action, Inserted.*, Deleted.*; 

B. Esecuzione delle operazioni UPDATE e DELETE

Nell'esempio seguente viene utilizzato MERGE per aggiornare la tabella ProductInventory nel database di esempio AdventureWorks2008R2 su base giornaliera, in base agli ordini elaborati nella tabella SalesOrderDetail. Utilizzando l'istruzione MERGE seguente, la colonna Quantity della tabella ProductInventory viene aggiornata sottraendo il numero di ordini effettuati ogni giorno per ogni prodotto. Se il numero di ordini per un prodotto provoca l'azzeramento del livello delle scorte del prodotto, la riga relativa a tale prodotto viene eliminata dalla tabella ProductInventory. La tabella di origine viene aggregata nella colonna ProductID. Se ciò non si verificasse, più di un ProductID nella tabella di origine potrebbe corrispondere alla tabella di destinazione e l'istruzione MERGE restituirebbe un errore.

USE AdventureWorks2008R2;
GO
IF OBJECT_ID (N'Production.usp_UpdateInventory', N'P') IS NOT NULL DROP PROCEDURE Production.usp_UpdateInventory;
GO
CREATE PROCEDURE Production.usp_UpdateInventory
    @OrderDate datetime
AS
MERGE Production.ProductInventory AS target
USING (SELECT ProductID, SUM(OrderQty) FROM Sales.SalesOrderDetail AS sod
    JOIN Sales.SalesOrderHeader AS soh
    ON sod.SalesOrderID = soh.SalesOrderID
    AND soh.OrderDate = @OrderDate
    GROUP BY ProductID) AS source (ProductID, OrderQty)
ON (target.ProductID = source.ProductID)
WHEN MATCHED AND target.Quantity - source.OrderQty <= 0
    THEN DELETE
WHEN MATCHED 
    THEN UPDATE SET target.Quantity = target.Quantity - source.OrderQty, 
                    target.ModifiedDate = GETDATE()
OUTPUT $action, Inserted.ProductID, Inserted.Quantity, Inserted.ModifiedDate, Deleted.ProductID,
    Deleted.Quantity, Deleted.ModifiedDate;
GO

EXECUTE Production.usp_UpdateInventory '20030501'

C. Esecuzione delle operazioni INSERT, UPDATE e DELETE

Nell'esempio seguente viene utilizzata l'istruzione MERGE per inserire, aggiornare o eliminare righe in una tabella di destinazione in base alle differenze con i dati di origine. Si consideri una piccola società con cinque reparti, ognuno con un responsabile del reparto. La società decide di riorganizzare i reparti. Per implementare i risultati della riorganizzazione nella tabella di destinazione dbo.Departments, l'istruzione MERGE deve implementare le modifiche seguenti:

  • Alcuni reparti esistenti non vengono modificati.

  • Alcuni reparti esistenti avranno nuovi responsabili.

  • Verranno creati nuovi reparti.

  • Alcuni reparti non esisteranno più dopo la riorganizzazione.

Nel codice seguente viene creata la tabella di destinazione dbo.Departments successivamente popolata con i responsabili.

USE AdventureWorks2008R2;
GO
IF OBJECT_ID (N'dbo.Departments', N'U') IS NOT NULL 
    DROP TABLE dbo.Departments;
GO
CREATE TABLE dbo.Departments (DeptID tinyint NOT NULL PRIMARY KEY, DeptName nvarchar(30), 
    Manager nvarchar(50));
GO
INSERT INTO dbo.Departments 
    VALUES (1, 'Human Resources', 'Margheim'),(2, 'Sales', 'Byham'), 
           (3, 'Finance', 'Gill'),(4, 'Purchasing', 'Barber'),
           (5, 'Manufacturing', 'Brewer');

Le modifiche organizzative da apportare ai reparti vengono archiviate nella tabella di origine dbo.Departments_delta. Il codice seguente crea e popola questa tabella:

USE AdventureWorks2008R2;
GO
IF OBJECT_ID (N'dbo.Departments_delta', N'U') IS NOT NULL 
    DROP TABLE dbo.Departments_delta;
GO
CREATE TABLE dbo.Departments_delta (DeptID tinyint NOT NULL PRIMARY KEY, DeptName nvarchar(30), 
    Manager nvarchar(50));
GO
INSERT INTO dbo.Departments_delta VALUES 
    (1, 'Human Resources', 'Margheim'), (2, 'Sales', 'Erickson'),
    (3 , 'Accounting', 'Varkey'),(4, 'Purchasing', 'Barber'), 
    (6, 'Production', 'Jones'), (7, 'Customer Relations', 'Smith');
GO

Infine, per riflettere la riorganizzazione dell'azienda nella tabella di destinazione, nel codice seguente viene utilizzata l'istruzione MERGE per confrontare la tabella di origine, dbo.Departments_delta, con la tabella di destinazione dbo.Departments. La condizione di ricerca per questo confronto è definita nella clausola ON dell'istruzione. In base ai risultati del confronto, vengono eseguite le azioni riportate di seguito.

  • I reparti che esistono in entrambe le tabelle vengono aggiornati nella tabella di destinazione con nuovi nomi o nuovi responsabili o con entrambi nella tabella Departments. Se non sono presenti modifiche, non viene eseguito alcun aggiornamento. Questa operazione viene eseguita nella clausola WHEN MATCHED THEN.

  • I reparti in Departments_delta che non esistono in Departments vengono inseriti in Departments. Questa operazione viene eseguita nella clausola WHEN NOT MATCHED THEN.

  • I reparti in Departments che non esistono nella tabella di origine Departments_delta vengono eliminati da Departments. Questa operazione viene eseguita nella clausola WHEN NOT MATCHED By SOURCE THEN.

MERGE dbo.Departments AS d
USING dbo.Departments_delta AS dd
ON (d.DeptID = dd.DeptID)
WHEN MATCHED AND d.Manager <> dd.Manager OR d.DeptName <> dd.DeptName
    THEN UPDATE SET d.Manager = dd.Manager, d.DeptName = dd.DeptName
WHEN NOT MATCHED THEN
    INSERT (DeptID, DeptName, Manager)
        VALUES (dd.DeptID, dd.DeptName, dd.Manager)
WHEN NOT MATCHED BY SOURCE THEN
    DELETE
OUTPUT $action, 
       inserted.DeptID AS SourceDeptID, inserted.DeptName AS SourceDeptName, 
       inserted.Manager AS SourceManager, 
       deleted.DeptID AS TargetDeptID, deleted.DeptName AS TargetDeptName, 
       deleted.Manager AS TargetManager;