Iterare su un insieme di righe, magari attraverso l'uso di cursori o di cicli, una soluzione nella stragrande maggioranza dei casi comporta gravi problemi correlati alle performance, rende il codice prolisso e di difficile debug, difficilmente scalabile e soggetto a bug.
Al contrario, pensare una soluzione set based, rende il codice molto più leggibile, compatto e scalabile. Per riuscire a ragionare Set based dobbiamo però conoscere gli strumenti che la piattaforma SQL Server ci mette a disposizione.
Uno degli strumenti, introdotto dalla versione SQL Server 2005 ma che ancora non è molto utilizzato, è la Common Table Expression o CTE.
Qui trovate la documentazione ufficiale.
Vediamo un piccolo esempio di utilizzo delle CTE per creare query ricorsive.
Immaginate di avere una tabella contenente l'anagrafica dipendenti della vostra azienda. Ogni riga dipendente ha un proprio Id, il nome e l'id del diretto responsabile e avete la necessità di calcolare il livello d'appartenenza di ogni persona censita e di visualizzare il diretto responsabile.
Grazie alle CTE possiamo, con un'unica query, raggiungere l'obiettivo prefissato.
Vediamo come Preparo una variabile di tipo tabella contenente la lista dei dipendenti
Use TempDb Go --Preparo la tabella anagrafica Declare @Dipendenti Table ( idDipendente tinyint, Nome varchar(50), idResponsabile tinyint ) --la popolo Insert into @Dipendenti values(1,'Amministratore Delegato',NULL) ,(2,'Responsabile di Unità operativa 1',1) ,(3,'Responsabile di Unità operativa 2',1) ,(4,'Capo progetto 1',2) ,(5,'Capo progetto 2',3) ,(6,'Impiegato1',4) ,(7,'Impiegato2',4) ,(8,'Impiegato3',4) ,(9,'Impiegato4',4) ,(10,'Impiegato5',5) ,(11,'Impiegato6',5) ,(12,'Impiegato7',5) ,(13,'Impiegato8',5) --Ecco la CTE ;With CTELivello as ( --Reperisco i membri di anchor, quelli che nel nostro caso --non hanno censito il resposabile diretto, quindi 'Amministratore Delegato'. Select idDipendente, Nome as NomeDipendente, Cast('' as varchar(50))as NomeResponsabile , idResponsabile, 0 as Livello From @Dipendenti Where idResponsabile is Null --Grazie alla union all reperiamo tutti i membri ricorsivi Union all Select Dip.idDipendente, Dip.Nome as NomeDipendente, Resp.NomeDipendente as NomeResponsabile, Dip.idResponsabile, Livello + 1 as Livello From @Dipendenti Dip Inner Join CTELivello Resp on Dip.idResponsabile = Resp.idDipendente ) Select Replicate('>',Livello) + ' ' + NomeDipendente as [Nome dipendente] ,NomeResponsabile as [Responsabile diretto] ,Livello from CTELivelloVediamo nel dettaglio come funziona. Il primo passo è reperire il/i membri di anchor, vale a dire le righe danno il via alla ricorsione.
;With CTELivello as ( --Reperisco i membri di anchor, quelli che nel nostro caso --non hanno censito il resposabile diretto, quindi 'Amministratore Delegato'. Select idDipendente, Nome as NomeDipendente, Cast('' as varchar(50))as NomeResponsabile , idResponsabile, 0 as Livello From @Dipendenti Where idResponsabile is Null
Come potete vedere Reperisco l'id del dipendente, il suo nome, stringa vuota come nome reponsabile diretto, l'id del responsabile e 0 come livello.
Perchè stringa vuota come responsabile e 0 come livello ?
Perchè sono i membri di anchor, non hanno responsabile diretto ed appartengono al livello 0.
Queste colonne mi servono in quanto la union all formatta le colonne della seconda parte della query utilizzando quelle della prima select.
La seconda parte della CTE è quella che implementa la ricorsione su tutti i membri secondari
--Grazie alla union all reperiamo tutti i membri ricorsivi Union all Select Dip.idDipendente, Dip.Nome as NomeDipendente, Resp.NomeDipendente as NomeResponsabile, Dip.idResponsabile, Livello + 1 as Livello From @Dipendenti Dip Inner Join CTELivello Resp on Dip.idResponsabile = Resp.idDipendente ) Select Replicate('>',Livello) + ' ' + NomeDipendente as [Nome dipendente] ,NomeResponsabile as [Responsabile diretto] ,Livello from CTELivelloIn pratica per ogni membro di anchor vengono ricercati tutti i membri ricorsivi mettendo in join la tabella dipendenti co la CTE stessa tra il campo idresponsabile della tabella stessa ed il campo id dipendente della CTE che mano a mano si va a popolare.
Inoltre effettuo per ogni membro ricorsivo il ricalcolo del livello (Livello + 1) basandomi sul livello del membro di anchor in iterazione.
Ed ecco il risultato :
Ciao
Luca
Nessun commento:
Posta un commento