18th Jul 2022 Tempo di lettura: 9 minuti Come trovare l'ennesimo stipendio più alto per reparto con SQL Tihomir Babic sql imparare sql Window Functions Indice I dati utilizzati Qual è il compito? Utilizzo di NTH_VALUE() Utilizzo di ROW_NUMBER() Utilizzo di RANK() Utilizzo di DENSE_RANK() Panoramica dei concetti e delle loro differenze Diversi modi per ottenere il valore N di SQL Imparate a trovare l'ennesimo stipendio più alto in SQL e imparerete come ottenere l'ennesimo valore in qualsiasi dato. In questo articolo mi concentrerò su un problema e vi fornirò diverse soluzioni. Il problema è già indicato nel titolo: trovare l'ennesimo stipendio più alto per reparto utilizzando SQL. Può sembrare troppo specifico. Ma imparando a risolverlo, sarete in grado di trovare l'ennesimo valore in qualsiasi dato, non solo negli stipendi. Avrete un'idea di come risolvere altri problemi simili in cui vi imbatterete. Come ho detto, esamineremo un solo problema, ma esploreremo diverse soluzioni. In effetti, vi mostrerò quattro soluzioni utilizzando queste funzioni della finestra: NTH_VALUE() ROW_NUMBER() RANK() DENSE_RANK() Se avete bisogno di aiuto con la sintassi o con qualsiasi altro dettaglio relativo a queste funzioni, potete consultare la scheda informativa di SQL Window Functions . Potete anche dare un'occhiata a questo esempio per vedere come funzionano le funzioni finestra. I dati utilizzati Utilizzeremo i dati memorizzati nelle due tabelle seguenti: employee department Ecco come appaiono i dati nella tabella employee come appaiono: idfirst_namelast_namesalarydepartment_id 1GoraudTomankiewicz7,231.061 2HarwellWinny8,139.511 3BastienGoosnell4,574.201 4ZachariahRapi6,657.111 5GiustinoCruikshank5,555.631 6AbraClemon5,564.253 7HurleeDrance9,790.162 8RozannaMcIlvoray3,201.182 9IveStrathdee9,300.252 10LoisSkain5,371.022 11DeborHolby2,804.293 10HadrianRobatham2,615.783 13DixSowter6,378.123 14LeslieSandle8,805.703 15DagnyRosier2,041.263 La tabella department ha i seguenti dati: iddepartment_name 1Research and Development 2Accounting 3Human Resources Qual è il compito? Troviamo il terzo stipendio più alto per reparto. Ciò significa trovare il terzo valore più alto, non in generale ma all'interno di ogni sottoinsieme, dove un sottoinsieme contiene gli stipendi di un determinato reparto. Lo strumento più utile per farlo sono le funzioni finestra. Ecco quindi la prima soluzione che utilizza una funzione finestra. Utilizzo di NTH_VALUE() Lo scopo della funzione NTH_VALUE() è quello di ottenere il valore dell'ennesima riga del dataset. Ecco come utilizzarla per ottenere il terzo stipendio più alto per reparto: SELECT e.first_name, e.last_name, d.department_name, salary, NTH_VALUE (salary, 3) OVER ( PARTITION BY department_name ORDER BY salary DESC RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS third_highest_salary FROM department d JOIN employee e ON d.id = e.department_id; Questo codice seleziona innanzitutto alcune colonne dalle tabelle employee e department. Per utilizzare NTH_VALUE(), dobbiamo specificare la colonna e il valore di N. Poiché vogliamo ottenere il terzo stipendio più alto salary, la colonna è lo stipendio e N = 3; quindi, abbiamo NTH_VALUE(salary, 3). In questo modo otterremo il terzo stipendio più alto. Affinché una funzione finestra funzioni, è necessario utilizzare una clausola OVER(). Ricordate che stiamo cercando il terzo stipendio più alto del dipartimento. ogni reparto. Per farlo, utilizziamo PARTITION BY per dividere i dati in sottoinsiemi in base alla colonna nome_dipartimento dalla tabella department. Poiché stiamo cercando il terzopiù alto stipendio, utilizziamo ORDER BY per far sì che le funzioni della finestra eseguano i calcoli dallo stipendio più alto a quello più basso. Questo è il motivo per cui ORDER BY stipendio DESC nel codice. Inoltre, abbiamo bisogno della clausola RANGE, in questo caso RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING. Ciò significa che la funzione finestra cercherà tra la prima e l'ultima riga della partizione. Naturalmente, per ottenere tutto questo, dobbiamo unire le due tabelle. Ecco il risultato: first_namelast_namedepartment_namesalarythird_highest_salary HurleeDranceAccounting9,790.165,371.02 IveStrathdeeAccounting9,300.255,371.02 LoisSkainAccounting5,371.025,371.02 RozannaMcIlvorayAccounting3,201.185,371.02 LeslieSandleHuman Resources8,805.705,564.25 DixSowterHuman Resources6,378.125,564.25 AbraClemonHuman Resources5,564.255,564.25 DeborHolbyHuman Resources2,804.295,564.25 HadrianRobathamHuman Resources2,615.785,564.25 DagnyRosierHuman Resources2,041.265,564.25 HarwellWinnyResearch and Development8,139.516,657.11 GoraudTomankiewiczResearch and Development7,231.066,657.11 ZachariahRapiResearch and Development6,657.116,657.11 GiustinoCruikshankResearch and Development5,555.636,657.11 BastienGoosnellResearch and Development4,574.206,657.11 Il risultato ci dice che il terzo stipendio più alto in Contabilità è 5.371,02, ovvero lo stipendio di Lois Skain. Lo stipendio di Abra Clemon (5.564,25) è il terzo più alto in Risorse umane. In Ricerca e sviluppo, lo stipendio di Zachariah Rapi è il terzo più alto (6.657,11). Utilizzo di ROW_NUMBER() La seconda opzione per ottenere il terzo stipendio più alto per reparto è utilizzare ROW_NUMBER(). Questa funzione della finestra restituisce i numeri di sequenza delle righe di un set di dati. Se ordiniamo gli stipendi all'interno di ciascun reparto, sarà facile scegliere il terzo più alto. Ecco come appare la soluzione: SELECT e.first_name, e.last_name, d.department_name, salary, ROW_NUMBER() OVER ( PARTITION BY d.id ORDER BY salary DESC ) AS salary_rank FROM department d JOIN employee e ON d.id = e.department_id ORDER BY department_name; Questa soluzione seleziona tutte le stesse colonne della soluzione precedente. La differenza è che qui usiamo ROW_NUMBER() invece di NTH_VALUE(). Tutti i criteri nella clausola OVER() sono gli stessi della soluzione precedente. Il risultato appare così: first_namelast_namedepartment_namesalarysalary_rank HurleeDranceAccounting9,790.161 IveStrathdeeAccounting9,300.252 LoisSkainAccounting5,371.023 RozannaMcIlvorayAccounting3,201.184 LeslieSandleHuman Resources8,805.701 DixSowterHuman Resources6,378.122 AbraClemonHuman Resources5,564.253 DeborHolbyHuman Resources2,804.294 HadrianRobathamHuman Resources2,615.785 DagnyRosierHuman Resources2,041.266 HarwellWinnyResearch and Development8,139.511 GoraudTomankiewiczResearch and Development7,231.062 ZachariahRapiResearch and Development6,657.113 GiustinoCruikshankResearch and Development5,555.634 BastienGoosnellResearch and Development4,574.205 Il risultato è leggermente diverso da quello della soluzione precedente. L'ultima colonna non contiene il valore del terzo stipendio più alto. Contiene invece una classifica e si può facilmente notare che l'effetto è lo stesso. I valori del terzo stipendio più alto per reparto sono 5.371,02 (Contabilità), 5.564,25 (Risorse umane) e 6.657,11 (Ricerca e sviluppo), come nella prima soluzione. Possiamo anche utilizzare ROW_NUMBER() in un CTE. Se non avete familiarità con le CTE, questo articolo spiega di cosa si tratta. Ad esempio, possiamo scrivere il codice seguente: WITH salaries_ranks AS ( SELECT e.first_name, e.last_name, d.department_name, salary, ROW_NUMBER() OVER (PARTITION BY d.id ORDER BY salary DESC ) AS salary_rank FROM department d JOIN employee e ON d.id = e.department_id ) SELECT * FROM salaries_ranks WHERE salary_rank = 3; Si inizia la CTE con un WITH, seguito dal nome della CTE, salaries_ranks. Quindi, scriviamo un'istruzione SELECT, che è esattamente la stessa del codice precedente. Ora che abbiamo scritto una CTE, possiamo usarla come qualsiasi altra tabella. Lo facciamo con un'istruzione separata SELECT che seleziona tutte le colonne dalla CTE salaries_ranksmostrando solo le righe in cui salary_rank = 3. Il risultato di questo codice è: first_namelast_namedepartment_namesalarysalary_rank ZachariahRapiResearch and Development6,657.113 LoisSkainAccounting5,371.023 AbraClemonHuman Resources5,564.253 Si tratta di una soluzione piuttosto elegante. Otteniamo solo i dati necessari: solo tre righe che mostrano il terzo stipendio più alto per ogni reparto. Utilizzo di RANK() La terza opzione consiste nell'utilizzare la funzione RANK(). È simile a ROW_NUMBER(), ma classifica anche le righe all'interno di una partizione. Simile, ma non uguale. Le differenze verranno spiegate più avanti, oppure si possono leggere qui. Scriviamo il codice usando RANK(): SELECT e.first_name, e.last_name, d.department_name, salary, RANK() OVER ( PARTITION BY d.department_name ORDER BY salary DESC ) AS salary_rank FROM department d JOIN employee e ON d.id = e.department_id; Anche in questo caso, il codice non è molto diverso dai precedenti, tranne che per la funzione finestra specifica utilizzata. In questo caso, si tratta di RANK(), con tutti gli stessi criteri nella clausola OVER(). Il risultato sarà esattamente lo stesso di quando abbiamo usato ROW_NUMBER(), quindi non credo sia necessario mostrare di nuovo la stessa tabella dei risultati. Utilizzo di DENSE_RANK() L'ultima soluzione che vi mostrerò è la funzione della finestra DENSE_RANK(). Proprio come ROW_NUMBER() e RANK(), classifica i valori all'interno di un set di dati. Anche il codice non è molto diverso. È lo stesso di prima, solo che utilizza una funzione finestra diversa: SELECT e.first_name, e.last_name, d.department_name, salary, DENSE_RANK() OVER ( PARTITION BY d.department_name ORDER BY salary DESC ) AS salary_rank FROM department d JOIN employee e ON d.id = e.department_id; Quando eseguiamo questo codice, otteniamo esattamente lo stesso risultato delle due soluzioni precedenti. Panoramica dei concetti e delle loro differenze Come si è visto, è possibile utilizzare una qualsiasi delle quattro funzioni finestra per ottenere il terzo stipendio più alto per reparto. La funzione NTH_VALUE() mostra esplicitamente il valore del terzo stipendio più alto per reparto. Le funzioni ROW_NUMBER(), RANK() e DENSE_RANK() classificano gli stipendi all'interno di ciascun reparto. Quindi, è sufficiente trovare il valore dello stipendio associato al terzo posto. Queste tre funzioni sono simili ma non uguali. In questo esempio sembrano identiche, ma altri dati potrebbero evidenziarne le differenze. Ecco una spiegazione di come sono diverse per evitare spiacevoli sorprese. Le differenze emergono quando nei dati sono presenti legami (più righe con lo stesso valore). Siccome sapevo di non avere legami nei miei dati, tutte e tre le funzioni mi avrebbero dato la stessa soluzione. Ma cosa succede se ci sono dei legami? In questo caso, ROW_NUMBER() assegnerebbe i ranghi in modo sequenziale; cioè, i legami avrebbero numeri di rango diversi, assegnati arbitrariamente. Al contrario, RANK() assegna lo stesso numero di rango agli stessi valori e salta un numero di rango per compensarlo quando arriva a una riga con un valore diverso. Infine, DENSE_RANK() assegna lo stesso numero di rango ai legami, senza saltare un numero di rango al valore successivo. Ecco un esempio per illustrare la situazione: employeesalaryrow_number()rank()dense_rank() 12,000111 24,000333 33,000222 48,000554 54,000433 Se si cerca il valore del terzo stipendio più alto in questi dati, ROW_NUMBER() fornisce la soluzione corretta: lo stipendio di 4.000 è il terzo più alto. Tuttavia, se si cercano tutti i dipendenti con il terzo stipendio più alto, la soluzione non è corretta. Mostrerebbe solo l'impiegato 2, mentre dovrebbe essere mostrato anche l'impiegato 5. In questo caso, RANK() o DENSE_RANK() sarebbero una scelta migliore. E se si cerca il valore del quarto stipendio più alto? La funzione ROW_NUMBER() fornirebbe una soluzione totalmente errata, poiché il terzo e il quarto valore più alto sono gli stessi. L'uso di RANK() non dà alcun risultato, perché salta il numero di rango quattro: come spiegato in precedenza, assegna lo stesso numero di rango ai legami e salta il numero di rango successivo per compensarlo. In questo caso, solo DENSE_RANK() fornisce una soluzione corretta. Prima di decidere quale funzione utilizzare, assicuratevi di aver compreso i dati e cosa state cercando di ottenere come soluzione. Il modo più sicuro è usare NTH_VALUE(). Se si vuole usare una funzione di classificazione, in genere è meglio usare DENSE_RANK() quando non si sa se ci sono legami nei dati. Se invece si usa ROW_NUMBER() o RANK(), assicurarsi di sapere come sarà il risultato. Forse una o entrambe queste due funzioni vi daranno ciò di cui avete bisogno. Scegliete la funzione in base alle vostre esigenze. Queste quattro funzioni possono essere utilizzate anche quando si vuole trovare il salario massimo o minimo per reparto. Oppure, ad esempio, il prodotto più o meno costoso per categoria di prodotto. O qualsiasi prodotto con l'ennesimo prezzo più alto. Si possono cercare le vendite più alte o più basse, i ricavi, le ore lavorate, i costi, il numero di "mi piace", i login, gli impegni, gli stream, i commenti, ecc. Se utilizzate MySQL, questa introduzione completa vi mostrerà queste quattro e tutte le altre funzioni della finestra di MySQL. Diversi modi per ottenere il valore N di SQL La funzione NTH_VALUE() è perfetta per trovare l'ennesimo stipendio o l'ennesimo valore di qualsiasi altra colonna. Dopotutto, è stata progettata proprio per questo scopo. Vi ho mostrato come ottenere lo stesso effetto con altre tre funzioni della finestra: ROW_NUMBER(), RANK(), e DENSE_RANK(). Utilizzate quella più adatta alle vostre esigenze e ai vostri dati. Ma queste sono solo quattro funzioni di finestra. Sono solo la punta dell'iceberg e c'è molto di più! Queste e altre funzioni finestra sono spiegate in questo corso sulle funzioni finestra. Tags: sql imparare sql Window Functions