Un'operazione molto frequente nei processi
Extract Transform Load (ETL) tipici del popolamento dei
data warehouse è quella di
MERGE, anche detta
UPSERT: essa
consiste nell'eseguire una funzione diversa, di INSERT piuttosto che di UPDATE, a seconda che il record da caricare sia già presente o meno all'interno della tabella di destinazione.
Consideriamo ad esempio il popolamento periodico di un
data warehouse relativo a dei contratti telefonici: in ogni caricamento successivo al primo occorrerà verificare, per ogni contratto in esame, se lo stesso è già presente in tabella; in tal caso
occorrerà aggiornarne i dati eventualmente variati dall'ultimo caricamento, quali ad esempio il piano tariffario prescelto, piuttosto che l'indirizzo di fatturazione, mentre in caso contrario bisognerà inserire i dati del nuovo contratto;
in alcuni casi sarà necessario inoltre provvedere anche alla cancellazione di quei contratti non più in vigore, e quindi non più presenti nella base dati di origine, ma ancora memorizzati in quella di destinazione.
Fino ad oggi tale compito era affidato agli sviluppatori, che spesso incapsulavano una logica di questo tipo all'interno di più o meno complesse
stored procedure Transact SQL; con l'avvento di
SQL Server 2008,
Microsoft ha introdotto l'istruzione MERGE, che consente di
attivare in un unico comando l'insieme delle operazioni dell'esempio precedente, con evidenti
vantaggi in termini di tempo ed una minore possibilità di errori da parte dello sviluppatore.
Per utilizzare il nuovo comando è necessario specificare le tabelle/query di origine e di destinazione da relazionare con una
join e il tipo di azione da mettere in atto per i record, che sono presenti in entrambe e per quelli presenti solo nell'una o nell'altra. Grazie all'utilizzo della
clausola di OUTPUT,
introdotta in SQL Server 2005, è inoltre possibile ottenere le informazioni che si desiderano sui record inseriti, modificati o cancellati dall'esecuzione del
MERGE.
La
sintassi completa del comando è consultabile su
MSDN; vediamo subito un esempio, tratto da
un articolo introduttivo sul nuovo
statement opportunamente commentato:
<span style="font-size:1.0em">
MERGE
-- tabella DESTINAZIONE
dbo.CricketTeams AS TARGET
-- tabella SORGENTE
USING dbo.CricketTeams_UpdatedList AS SOURCE
-- criterio di join
ON (TARGET.CricketTeamID = SOURCE.CricketTeamID)
-- cosa fare quando il record è presente in entrambe le tabelle
WHEN MATCHED
-- ...con eventuali condizioni aggiuntive
AND TARGET.CricketTeamContinent SOURCE.CricketTeamContinent
OR TARGET.CricketTeamCountry SOURCE.CricketTeamCountry THEN
UPDATE SET TARGET.CricketTeamContinent = SOURCE.CricketTeamContinent,
TARGET.CricketTeamCountry = SOURCE.CricketTeamCountry
-- cosa fare quando il record NON è presente nella tabella DESTINAZIONE
WHEN NOT MATCHED THEN
INSERT (CricketTeamID, CricketTeamCountry, CricketTeamContinent)
VALUES (SOURCE.CricketTeamID, SOURCE.CricketTeamCountry, SOURCE.CricketTeamContinent)
-- cosa fare quando il record NON è presente nella tabella SORGENTE
WHEN NOT MATCHED BY SOURCE THEN
DELETE
-- clausola di OUTPUT, restituisce informazioni sulle operazioni eseguite
OUTPUT $action,
INSERTED.CricketTeamID AS SourceCricketTeamID,
INSERTED.CricketTeamCountry AS SourceCricketTeamCountry,
INSERTED.CricketTeamContinent AS SourceCricketTeamContinent,
DELETED.CricketTeamID AS TargetCricketTeamID,
DELETED.CricketTeamCountry AS TargetCricketTeamCountry,
DELETED.CricketTeamContinent AS TargetCricketTeamContinent;
</span>
Notiamo la presenza delle tre possibili operazioni da eseguire a seconda dell'esistenza o meno nella tabella
CricketTeams (destinazione) degli elementi della tabella
CricketTeams_UpdatedList (sorgente); non è necessario specificare sempre tutte e tre le opzioni, ma è ovviamente obbligatorio definirne almeno una.
Due dettagli importanti da notare sono il carattere di punto e virgola con cui occorre sempre terminare l'istruzione
MERGE, e la colonna
$action, definita come
varchar(10), che conterrà i valori
INSERT, UPDATE o DELETE per ogni riga della tabella di destinazione rispettivamente aggiunta, modificata o cancellata. L'utilizzo del comando
MERGE mantiene inoltre un comportamento coerente con quanto avviene per le operazioni base in cui lo stesso viene scomposto, per cui
verranno scatenati gli eventuali trigger definiti sulla tabella destinazione e saranno rispettati i vincoli impostati su tale tabella.
Una nota sull'utilizzo della
istruzione @@ROWCOUNT che,
dopo un'operazione di MERGE, restituirà il numero totale di righe inserite, modificate o cancellate nella tabella destinazione; dal punto di vista della sicurezza, inoltre, un utente deve avere i diritti di lettura sulla tabella sorgente e quelli di INSERT, UPDATE e DELETE su quella di destinazione per poter eseguire uno statement di MERGE.
Per finire, è d'obbligo dare un'occhiata a
come ottimizzare le performance del
nuovo nato, prima di iniziare ad utilizzare il comando
MERGE per tutte le nostre necessità di
UPSERT.