Torna all'elenco degli articoli Articoli
Tempo di lettura: 15 minuti

Come classificare le righe in SQL: Guida completa

Non dovrete più lottare con le funzioni di ranking di SQL! Questo articolo vi guiderà attraverso i casi d'uso più comuni delle funzioni di ranking.

Le classifiche sono utilizzate ogni giorno. Tornei sportivi, i migliori film e serie TV su Netflix, negozi con i prodotti più economici: questi sono solo alcuni esempi di classifiche che potreste aver visto di recente.

L'uso delle funzioni SQL RANK può essere talvolta difficile. La varietà di costruzioni diverse è enorme. È facile perdersi in tutto ciò che c'è da imparare sulle classifiche. La chiave è capire i concetti importanti e sapere dove cercare ulteriori informazioni.

In questa guida troverete le conoscenze che vi permetteranno di scrivere molti tipi di query di ranking SQL. Innanzitutto, spiegherò come funziona il ranking. Poi mostrerò una serie di esempi. Se cercate casi d'uso specifici, potete semplicemente immergervi nella sezione Casi d'uso del ranking SQL.

Le funzioni di classificazione SQL Window Functions

Iniziamo dalle basi. Cosa sono esattamente le funzioni di ranking in SQL? Fanno parte di una famiglia di funzioni chiamate funzioni finestra. Le funzioni finestra utilizzano un ambito (finestra) che esamina una parte dei dati per calcolare il risultato. Poi si spostano su un'altra parte dei dati e calcolano il risultato per quella parte. Un'idea di base del suo funzionamento si può ricavare da SQL Window Function Example With Explanations.

Sebbene esistano molte funzioni di finestra diverse, mi concentrerò solo sulla classificazione. Inoltre, non spiegherò a fondo il funzionamento interno delle funzioni finestra. Questo articolo si concentrerà sulle funzioni di classificazione di SQL e su come utilizzarle in diverse situazioni. Per saperne di più sulle funzioni finestra, consultate questo Window Functions corso.

Funzioni di classificazione di base

I tipi più comuni (e semplici) di funzioni di classificazione sono:

  • RANK()
  • DENSE_RANK()
  • ROW_NUMBER()

Che cosa fanno e come si differenziano l'una dall'altra? Spiegherò brevemente ciascuna di esse. Poi confronterò le classifiche di ciascuna funzione.

La funzione RANK() crea una classifica delle righe in base a una colonna fornita. Inizia con l'assegnare "1" alla prima riga dell'ordine e poi assegna numeri più alti alle righe inferiori. Se le righe hanno lo stesso valore, vengono classificate allo stesso modo. Tuttavia, il posto successivo viene spostato di conseguenza. Ad esempio, se due righe sono al 5° posto (hanno lo stesso rango), la riga successiva sarà la 7° (cioè la 6° non esiste).

La funzione DENSE_RANK() è piuttosto simile. L'unica differenza è che non lascia spazi vuoti nella classifica. Anche se più di una riga può avere lo stesso rango, la riga successiva otterrà il rango successivo. Ad esempio, se due righe sono al 5° posto, la riga successiva sarà al 6°.

La funzione ROW_NUMBER() è diversa. Se le righe hanno lo stesso valore, otterranno tutte una classifica consecutiva (non la stessa classifica come con le funzioni precedenti). Ad esempio, se due righe hanno lo stesso valore (sarebbero entrambe al 5° posto con le funzioni precedenti), ROW_NUMBER() le collocherà al 5° e al 6° posto.

Ora ci si potrebbe chiedere: Se alcune righe hanno lo stesso valore e si usa ROW_NUMBER(), come si fa a sapere quale riga sarà la prima, la seconda, ecc. La risposta è piuttosto cupa: non si può! Dipende da molti fattori e non è possibile prevedere quale riga otterrà un determinato punteggio.

Queste sono le funzioni di classificazione SQL di base e (probabilmente) più utilizzate. Se volete conoscere altre funzioni di classificazione, potete leggere questa panoramica delle funzioni di classificazione.

RANK() vs. DENSE_RANK() vs. ROW_NUMBER()

Ora che conoscete la teoria delle funzioni di classificazione, confrontiamo i risultati di queste funzioni. Per farlo, classifichiamo alcune persone in base ai loro punteggi nei test. Osserviamo i dati (tabella exam_result):

first_namelast_namepoints
EmyrDownes70
DinaMorin70
Evie-MayBoyer80
NoraParkinson60
TrystanOconnor40
ErykMyers90
MarleneDuncan90
MariusPowell90
JoanneGoddard50
RayhanWilliamson90

Creeremo una query che classifica le righe in base alla colonna dei punti, utilizzando le funzioni di classificazione descritte in precedenza:

SELECT
  RANK() OVER(ORDER BY points DESC) AS rank,
  DENSE_RANK() OVER(ORDER BY points DESC) AS dense_rank,
  ROW_NUMBER() OVER(ORDER BY points DESC) AS row_number,
  first_name,
  last_name,
  points
FROM exam_result;

Analizziamo il codice prima di eseguirlo per vedere i risultati. La prima colonna che vogliamo mostrare è rank. Sarà semplicemente la classifica creata con la funzione RANK(). Ma cosa significa OVER(ORDER BY points DESC)? Fa parte del concetto di funzione finestra. OVER() è una parte obbligatoria di tutte le funzioni di classificazione (in realtà, di tutte le funzioni finestra). Indica alla funzione l'ambito dei dati (la finestra) e l'ordine in cui saranno collocate le righe. In questo caso, abbiamo fornito solo l'ordine. Un esempio di definizione di una finestra si trova nella sezione RANK() OVER(PARTITION BY ...)-Single Column.

Poiché ora si è compresa la prima colonna, si dovrebbero comprendere anche le due successive. L'unica differenza è il nome delle funzioni di classificazione. Inoltre, le ultime tre colonne dovrebbero essere autoesplicative, dato che i dati sono già stati visti.

Bene, diamo un'occhiata ai risultati:

rankdense_rankrow_numberfirst_namelast_namepoints
111MarleneDuncan90
112RayhanWilliamson90
113MariusPowell90
114ErykMyers90
525Evie-MayBoyer80
636EmyrDownes70
637DinaMorin70
848NoraParkinson60
959JoanneGoddard50
10610TrystanOconnor40

Riuscite a individuare le differenze tra le varie funzioni? Per le prime righe, le colonne rank e dense_rank sembrano uguali. Tuttavia, row_number appare diversa fin dall'inizio. Anche se Marlene, Rayhan, Marius e Eryk hanno la stessa quantità di punti, la funzione ROW_NUMBER() ha assegnato loro valori diversi. Come detto in precedenza, l'ordine in cui queste persone hanno ottenuto il loro rango non è deterministico.

Vale la pena notare che è possibile rendere il risultato di ROW_NUMBER() più deterministico. È sufficiente aggiungere altre colonne all'ordine (per esempio, ROW_NUMBER() OVER (ORDER BY points DESC, last_name ASC)). Questo verrà spiegato ulteriormente nella sezione Classifica su più colonne.

È anche possibile rendere ROW_NUMBER() ancora meno deterministico! Anche se la parte ORDER BY è necessaria per RANK() e DENSE_RANK(), ROW_NUMBER() non la richiede affatto. In questo modo è possibile numerare le righe senza un ordine specifico.

Un'altra differenza si può notare più in basso nella tabella. Evie-May è classificata al 5° posto dalla funzione RANK() e al 2° posto dalla funzione DENSE_RANK(). La prima funzione prende nota di quante righe avevano la stessa posizione e la riga successiva viene classificata di conseguenza. La seconda funzione prende semplicemente in considerazione il valore precedente e alla riga successiva viene assegnata la classifica successiva.

Guardate le classifiche qui sopra e assicuratevi di capire la differenza tra queste tre funzioni di classificazione SQL. È importante sapere quale funzione utilizzare in quali casi. Se avete bisogno di ulteriori spiegazioni, date un'occhiata a Come usare le funzioni di classificazione in SQL.

Casi d'uso della classificazione SQL

È ora di immergersi in alcune query SQL RANK. Inizieremo con alcune query semplici e aumenteremo gradualmente la complessità. Lo schema è il seguente:

Anche se utilizzeremo principalmente la funzione SQL RANK(), gli esempi possono essere applicati a DENSE_RANK() e ROW_NUMBER(). Dipende dalle vostre esigenze. È quindi importante capire le differenze tra queste funzioni.

Negli esempi, utilizzeremo i dati modificati dell'esempio precedente. La colonna exam_date mostra quando la persona ha sostenuto l'esame e la colonna città mostra in quale city ha sostenuto l'esame. Date un'occhiata:

first_namelast_nameexam_datecitypoints
EmyrDownes2018-12-18San Francisco70
DinaMorin2019-01-17Los Angeles70
Evie-MayBoyer2019-01-23San Francisco80
NoraParkinson2019-02-16San Diego60
TrystanOconnor2019-02-28Los Angeles40
ErykMyers2019-06-07San Francisco90
MarleneDuncan2019-06-13San Diego90
MariusPowell2019-11-13San Diego90
JoanneGoddard2019-12-18San Diego50
MariusWilliamson2020-01-02San Diego90

Classifica su una singola colonna

Inizieremo con il caso d'uso più semplice: creare una classifica basata su una sola colonna. Baseremo la nostra classifica sui punti:

SELECT
  RANK() OVER(ORDER BY points DESC) AS ranking,
  first_name,
  last_name,
  points
FROM exam_result;

La query è piuttosto semplice, come l'esempio precedente. Crea una classifica basata sul numero di punti in ordine decrescente. Utilizziamo RANK() OVER(ORDER BY ...) per indicare quale colonna deve essere utilizzata per l'ordinamento. I risultati sono questi:

rankingfirst_namelast_namepoints
1MarleneDuncan90
1MariusWilliamson90
1MariusPowell90
1ErykMyers90
5Evie-MayBoyer80
6EmyrDownes70
6DinaMorin70
8NoraParkinson60
9JoanneGoddard50
10TrystanOconnor40

Se si desidera creare una classifica basata su una colonna diversa o su un ordine diverso (ad esempio, ascendente anziché discendente), è sufficiente cambiare il nome della colonna o cambiare la parola chiave DESC con la parola chiave ASC.

Classifica su più colonne

Anche l'uso di più colonne per l'ordinamento è semplice. Basta aggiungere il nome della colonna successiva dopo la virgola. Se i valori della prima colonna sono uguali, viene presa in considerazione la seconda colonna e così via. Guardate:

SELECT
  RANK() OVER(ORDER BY points DESC, first_name ASC) AS ranking,
  first_name,
  last_name,
  points
FROM exam_result;

Questa query è simile alla precedente. Abbiamo aggiunto first_name ASC alla clausola di ordinamento. In questo modo, se alcune righe hanno lo stesso valore nella prima colonna, viene presa in considerazione la seconda. Si noti che se la seconda colonna è uguale, la classificazione viene risolta in base alla funzione utilizzata (nel nostro caso RANK()). Osservate il risultato:

rankingfirst_namelast_namepoints
1ErykMyers90
2MariusWilliamson90
2MariusPowell90
4MarleneDuncan90
5Evie-MayBoyer80
6DinaMorin70
7EmyrDownes70
8NoraParkinson60
9JoanneGoddard50
10TrystanOconnor40

Come si può vedere, le persone con lo stesso numero di punti hanno classifiche diverse perché hanno nomi diversi. Le uniche eccezioni sono le due persone di nome Marius. Poiché hanno lo stesso nome e lo stesso numero di punti, hanno la stessa posizione.

Vale la pena ricordare che l'ordinamento per alcune colonne può essere utile se si vuole rendere deterministico il risultato della funzione ROW_NUMBER(). Osservate questa query:

SELECT
  ROW_NUMBER() OVER(ORDER BY points DESC, last_name ASC) AS ranking,
  first_name,
  last_name,
  points
FROM exam_result;

Poiché tutti i nostri dati hanno un cognome diverso, possiamo aggiungerlo alla clausola di ordinamento in modo da poter prevedere chi ottiene quale rango. Guardate:

rankingfirst_namelast_namepoints
1MarleneDuncan90
2ErykMyers90
3MariusPowell90
4MariusWilliamson90
5Evie-MayBoyer80
6EmyrDownes70
7DinaMorin70
8NoraParkinson60
9JoanneGoddard50
10TrystanOconnor40

In questo caso, la funzione ROW_NUMBER() funziona come RANK() e DENSE_RANK() perché ogni riga può essere posizionata in modo deterministico. Tuttavia, si noti che se ci fossero persone con lo stesso cognome, la funzione ROW_NUMBER() non sarebbe deterministica, anche se stiamo ordinando per due colonne.

RANK() con i primi 10 risultati

Spesso si creano classifiche per mostrare i risultati più importanti (ad esempio, i primi 10, i primi 100, ecc.). Come ci si potrebbe aspettare, è possibile mostrare i primi risultati utilizzando le funzioni di classificazione SQL. Una query di questo tipo, però, è un po' più complicata. Guardate un po':

SELECT
  *
FROM (
  SELECT
    RANK() OVER(ORDER BY points DESC) AS ranking,
    first_name,
    last_name,
    points
  FROM exam_result
) AS a
WHERE ranking <= 3;

In questa query, utilizziamo una sottoquery per calcolare la classifica e poi, nella query principale, filtriamo i risultati in modo da mostrare solo i primi tre. I risultati potrebbero sorprendervi:

rankingfirst_namelast_namepoints
1MarleneDuncan90
1MariusWilliamson90
1MariusPowell90
1ErykMyers90

Come si può vedere, i risultati mostrano quattro righe e ognuna di esse ha lo stesso valore nella colonna della classifica. Questo è dovuto ai nostri dati. Quattro righe hanno lo stesso numero di punti e quindi ottengono la stessa posizione nella sottoquery.

Per scrivere una query di questo tipo, è sufficiente inserire la query di ranking all'interno di una subquery e scrivere un filtro intorno ad essa. Ad esempio, per mostrare i 10, cambiare WHERE ranking <= 3 in WHERE ranking <= 10.

Vale anche la pena di notare cosa succede quando si usa DENSE_RANK() con questi dati. Osservate la query seguente e i suoi risultati:

SELECT
  *
FROM (
  SELECT
    DENSE_RANK() OVER(ORDER BY points DESC) AS ranking,
    first_name,
    last_name,
    points
  FROM exam_result
) AS a
WHERE ranking <= 3;
rankingfirst_namelast_namepoints
1MarleneDuncan90
1MariusWilliamson90
1MariusPowell90
1ErykMyers90
2Evie-MayBoyer80
3EmyrDownes70
3DinaMorin70

Come si può vedere, ci sono molte righe nel risultato! Ancora una volta, ciò è dovuto al modo in cui DENSE_RANK() classifica ogni riga. Questo esempio mostra chiaramente che è importante capire la differenza tra le varie funzioni e sapere quale utilizzare in una determinata situazione.

Classificazione per data

SELECT
  RANK() OVER(ORDER BY exam_date ASC) AS ranking,
  first_name,
  last_name,
  exam_date
FROM exam_result;

In questo caso, stiamo semplicemente usando la colonna exam_date per ordinare. La differenza tra l'ordinamento per data e quello per qualsiasi altra colonna è minima. Osservate i risultati:

rankingfirst_namelast_nameexam_date
1EmyrDownes2018-12-18
2DinaMorin2019-01-17
3Evie-MayBoyer2019-01-23
4NoraParkinson2019-02-16
5TrystanOconnor2019-02-28
6ErykMyers2019-06-07
7MarleneDuncan2019-06-13
8MariusPowell2019-11-13
9JoanneGoddard2019-12-18
10MariusWilliamson2020-01-02

La cosa importante da ricordare è che ASC (ascendente) nel caso delle date significa che il più vecchio sarà posizionato per primo. Nell'ordine DESC (discendente), la data più recente sarà la prima.

Ordinamento per mese

È possibile ordinare le righe utilizzando solo una parte della data (ad esempio, il mese). Non è difficile se si conoscono le funzioni della data. Guardate la seguente query:

SELECT
  RANK() OVER(ORDER BY EXTRACT('year' FROM exam_date) ASC,
    EXTRACT('month' FROM exam_date) ASC) AS ranking,
  first_name,
  last_name,
  exam_date
FROM exam_result;

Utilizziamo la funzione EXTRACT() per estrarre una determinata parte della data. Per prima cosa, vogliamo ordinare le righe per anno e poi per mese. Quindi, utilizziamo prima EXTRACT('year' FROM exam_date) e poi EXTRACT('month' FROM exam_date). Osservate i risultati:

rankingfirst_namelast_nameexam_date
1EmyrDownes2018-12-18
2Evie-MayBoyer2019-01-23
2DinaMorin2019-01-17
4NoraParkinson2019-02-16
4TrystanOconnor2019-02-28
6MarleneDuncan2019-06-13
6ErykMyers2019-06-07
8MariusPowell2019-11-13
9JoanneGoddard2019-12-18
10MariusWilliamson2020-01-02

EXTRACT() è una funzione piuttosto semplice. Si dice quale parte di data si vuole estrarre dalla data. Ci sono molte parti di data diverse che si possono usare. È meglio consultare la documentazione del proprio sistema di gestione dei database (DBMS). Le parti più comuni sono:

  • 'year'
  • 'month'
  • 'day'
  • 'hour'
  • 'minute'
  • 'second'

È bene ricordare che non tutti i DBMS supportano questa funzione. Quelli che la supportano sono PostgreSQL e MySQL. In SQL Server, questa funzione si chiama DATEPART().

Le operazioni con la data e l'ora sono un argomento piuttosto complesso, che esula dallo scopo di questo articolo. Se volete saperne di più sulla funzione EXTRACT() e su molte altre, date un'occhiata al corso Standard SQL Functions . C'è un'intera sezione dedicata alle operazioni su data e ora.

Classificazione con GROUP BY

Ora vedremo come utilizzare le classifiche con le funzioni aggregate. Anche se sembra spaventoso, con la giusta comprensione, è un concetto piuttosto logico. Il database calcola prima le funzioni aggregate e poi crea una classifica basata sui valori calcolati. Guardate questo esempio con AVG():

SELECT
  RANK() OVER(ORDER BY AVG(points) DESC) AS ranking,
  city,
  AVG(points) AS average_points
FROM exam_result
GROUP BY city;

Come si può vedere, questa query non è molto diversa dalle altre query che abbiamo visto finora. Si possono semplicemente usare funzioni aggregate all'interno di funzioni di classificazione. La cosa importante da ricordare è l'uso della clausola GROUP BY. Come già detto, le funzioni aggregate vengono calcolate per prime. Ciò significa che con GROUP BY si possono usare solo le funzioni aggregate o le espressioni che si stanno raggruppando all'interno della funzione di classificazione.

Ad esempio, se si volesse utilizzare un'altra colonna per l'ordinamento, in modo che le righe siano ordinate in base a quest'altra colonna se il numero medio di punti è lo stesso, si dovrebbe includere quest'altra colonna nella clausola GROUP BY.

La query precedente restituisce il numero medio di punti ottenuti dalle persone di ogni città.

rankingcityaverage_points
1San Francisco80
2San Diego76
3Los Angeles55

Classifiche con COUNT()

Ora daremo un'occhiata a una funzione aggregata spesso utilizzata:COUNT():

SELECT
  RANK() OVER(ORDER BY COUNT(*) DESC) AS ranking,
  city,
  COUNT(*) AS exam_takers
FROM exam_result
GROUP BY city;

Come si può vedere, questa query non è molto diversa da quella precedente. Tutte le funzioni aggregate sono utilizzate allo stesso modo con le funzioni di classificazione.

I risultati sono i seguenti:

rankingcityexam_takers
1San Diego5
2San Francisco3
3Los Angeles2

La query calcola il numero di persone che hanno sostenuto l'esame in ogni città e crea una classifica basata su questo valore.

Classifica con SUM()

Un'altra funzione aggregata molto diffusa è SUM(). Anch'essa è piuttosto semplice da usare:

SELECT
  RANK() OVER(ORDER BY SUM(points) DESC) AS ranking,
  city,
  SUM(points) AS total_points
FROM exam_result
GROUP BY city;

Ancora una volta, utilizziamo semplicemente la funzione SUM() con la funzione SQL RANK().

rankingcitytotal_points
1San Diego380
2San Francisco240
3Los Angeles110

Questa volta, abbiamo calcolato il numero totale di punti acquisiti dalle persone di ogni città.

RANK() OVER(PARTITION BY ...)-Colonna singola

Spero che siate pronti ad affrontare un concetto di classifica più avanzato: PARTITION BY. Permette di creare classifiche in gruppi separati. Date un'occhiata a questa query:

SELECT
  RANK() OVER(PARTITION BY city ORDER BY points DESC) AS ranking,
  city,
  first_name,
  last_name,
  points
FROM exam_result;

In questa query, utilizziamo PARTITION BY con una singola colonna per creare una classifica delle persone in ogni città. In questo modo, possiamo vedere le persone con i punteggi più alti in ogni città. Guardate i risultati:

rankingcityfirst_namelast_namepoints
1San FranciscoErykMyers90
2San FranciscoEvie-MayBoyer80
3San FranciscoEmyrDowes70
1Los AngelesDinaMorin70
2Los AngelesTrystanOconnor40
1San DiegoMarleneDuncan90
1San DiegoMariusPowell90
1San DiegoMariusWilliamson90
4San DiegoNoraParkinson60
5San DiegoJoanneGoddard50

Come si può notare, le classifiche sono calcolate separatamente per ogni città. In questo modo, Eryk e Dina ottengono la stessa classifica, anche se hanno ottenuto punteggi diversi. Inoltre, Dina ed Emyr hanno gli stessi punteggi, ma Dina è classificata più in alto perché ha sostenuto l'esame in una città diversa.

In alcuni casi, PARTITION BY è un concetto importante e quindi vale la pena ricordarlo. L'aspetto positivo è che è anche piuttosto semplice da usare. Tuttavia, se volete maggiori informazioni, potete leggere Common SQL Window Functions: Using Partitions With Ranking Functions sul blog LearnSQL.it .

RANK() OVER(PARTITION BY ...)-Colonne multiple

Infine, analizziamo la clausola PARTITION BY con più colonne. Non è molto diverso dall'uso di PARTITION BY con una sola colonna. Guardate:

SELECT
  RANK() OVER(PARTITION BY city, first_name
    ORDER BY exam_date ASC) AS ranking,
  city,
  first_name,
  last_name,
  exam_date
FROM exam_result;

Nella query precedente, stiamo usando PARTITION BY con due colonne: city e first_name. Ciò significa che per ogni coppia distinta di città e nome, avremo classifiche separate. Osservate i risultati:

rankingcityfirst_namelast_nameexam_date
1San FranciscoErykMyers2019-06-07
1San FranciscoEvie-MayBoyer2019-01-23
1San FranciscoEmyrDowes2018-12-18
1Los AngelesDinaMorin2019-01-17
1Los AngelesTrystanOconnor2019-02-28
1San DiegoMarleneDuncan2019-06-13
1San DiegoMariusPowell2019-11-13
2San DiegoMariusWilliamson2020-01-02
1San DiegoNoraParkinson2019-02-16
1San DiegoJoanneGoddard2019-12-18

Come si può vedere, la maggior parte delle persone è classificata al primo posto. Questo perché la maggior parte delle coppie (città e nome) sono uniche. Tuttavia, c'è una coppia che non è unica. Ci sono due persone di San Diego che si chiamano Marius. Marius Powell è il primo perché ha sostenuto l'esame prima di Marius Williamson.

Ricordate: la pratica rende perfetti

Come si può notare, i casi di utilizzo delle funzioni di classificazione in SQL sono numerosi. È quindi importante conoscerle bene: probabilmente prima o poi dovrete scrivere una query di classificazione in SQL.

Il modo migliore per imparare le funzioni di classificazione (e le funzioni delle finestre in generale) è la pratica. Consiglio questo Window Functions corso. Ha 218 esercizi interattivi, che equivalgono a circa 20 ore di codifica. È un bel po', soprattutto se si decide di fare tutto d'un fiato. Noi non lo consigliamo. È meglio distribuire lo studio su più giorni. Qui troverete altri consigli su come mantenersi in salute durante l'apprendimento di SQL. Prendetevi cura del vostro corpo mentre sviluppate la vostra carriera e iniziate a imparare SQL oggi stesso.