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 conosciuti, 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 AdventureWorks;
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 AdventureWorks 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 AdventureWorks;
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 AdventureWorks;
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 AdventureWorks;
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;
Vedere anche