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

La funzione LAG e la funzione LEAD in SQL

LAG() e LEAD() sono funzioni posizionali. Una funzione posizionale è un tipo di window function o funzione finestra. Se non sapete quando e come usarle, qual è la sintassi di ciascuna funzione, perché usarle e quali sono le differenze, continuate a leggere!

LAG() e LEAD() sono funzioni posizionali. Sono window functions e sono molto utili nella creazione di report, perché possono fare riferimento a dati di righe superiori o inferiori a quella corrente. In questo articolo analizzeremo in dettaglio queste due funzioni.

Per imparare cosa fanno le funzioni finestra, quali sono e come si applicano ai problemi del mondo reale, è meglio seguire il nostro corso Window Functions. Potete trovare tutte le informazioni sul corso qui. È interattivo, ci sono 218 esercizi e sono sufficienti un browser web e alcune conoscenze di base di SQL.

Sintassi della funzione LAG

La funzione LAG() consente di accedere a un valore memorizzato in una riga diversa, più in alto rispetto a quella corrente. La riga può essere adiacente o distanziata di un certo numero di righe, ordinata in base a una colonna o a un set di colonne specifiche.

Vediamo la sua sintassi:

LAG(espressione [,offset[,valore_predefinito]]) OVER(ORDER BY colonne)

LAG() richiede tre argomenti: il nome della colonna o di un'espressione da cui si ottiene il valore, il numero di righe da saltare (offset) e il valore predefinito da restituire se il valore memorizzato ottenuto dalla riga precedente è vuoto. Solo il primo argomento è necessario. Il terzo argomento (valore predefinito) è ammesso solo se si specifica il secondo argomento, l'offset.

Come per le altre window functions, LAG() richiede la clausola OVER. Può accettare parametri opzionali, che verranno spiegati in seguito. Con LAG(), è necessario specificare un'istruzione ORDER BY nella clausola OVER, con una colonna o un elenco di colonne in base alle quali ordinare le righe.

Consideriamo la seguente tabella, vendite:

idnome_del_venditorevalore_di_vendita
3Stef7000
1Alice12000
2Mili25000

E la seguente query con una funzione LAG():

SELECT nome_del_venditore, valore_di_vendita,
  LAG(valore_di_vendita) OVER(ORDER BY valore_di_vendita) as valore_di_vendita_precedente
FROM vendita;

Ecco il risultato:

nome_del_venditorevalore_di_venditavalore_di_vendita_precedente
Stef7000NULL
Alice120007000
Mili2500012000

Questo uso molto semplice di LAG() visualizza il valore della riga adiacente in alto. Per esempio, il secondo record visualizza l'importo della vendita di Alice (12.000 dollari) e di quella di Stef (7.000 dollari) dalla riga precedente, rispettivamente nelle colonne valore_di_vendita e valore_di_vendita_precedente. Notate che la prima riga non ha una riga adiacente che la precede e di conseguenza il campo valore_di_vendita_precedente è vuoto (NULL), poiché la riga da cui si dovrebbe ottenere il valore di valore_di_vendita non esiste.

Se specifichiamo solo l'argomento richiesto (il nome della colonna o un'altra espressione), come in questo esempio, l'argomento offset è predefinito a 1 e il terzo argomento è predefinito come NULL. Nel nostro esempio, la prima riga del set di risultati ha NULL in valore_di_vendita_precedente e nelle altre righe sono presenti i valori delle rispettive righe immediatamente precedenti, poiché l'offset è 1.

Utilizzando LAG(), è possibile vedere il valore della riga corrente e quello della riga adiacente. È possibile utilizzare questa funzione, per esempio, per verificare l'importo delle vendite di una determinata riga rispetto a quello della riga precedente, con l'importo delle vendite ordinato dal più basso al più alto.

L'illustrazione seguente mostra come il valore della riga adiacente viene aggiunto a quella corrente.

Tabella

La funzione LAG() è inclusa nella nostra pratica "Scheda riassuntiva sulle Window Functions di SQL".

La sintassi della funzione LEAD

LEAD() è simile a LAG(). Mentre LAG() accede a un valore memorizzato in una riga superiore, LEAD() accede a un valore memorizzato in una riga inferiore.

La sintassi di LEAD() è simile a quella di LAG():

LEAD(espressione [,offset[,valore_predefinito]]) OVER(ORDER BY colonne)

Come LAG(), la funzione LEAD() accetta tre argomenti: il nome di una colonna o di un'espressione, l'offset da saltare in basso e il valore predefinito da restituire se il valore memorizzato ottenuto dalla riga sottostante è vuoto. Solo il primo argomento è necessario. Il terzo argomento, il valore predefinito, può essere specificato solo se si specifica il secondo argomento, l'offset.

Proprio come LAG(), LEAD() è una funzione finestra e richiede una clausola OVER. E come per LAG(), LEAD() deve essere accompagnata da un'istruzione ORDER BY nella clausola OVER.

Osserviamo nuovamente la tabella, vendite:

idnome_del_venditorevalore_di_vendita
3Stef7000
1Alice12000
2Mili25000

Ecco una query con una funzione LEAD():

SELECT nome_del_venditore, valore_di_vendita,
  LEAD(valore_di_vendita) OVER(ORDER BY valore_di_vendita) as valore_di_vendita_successivo
FROM vendita;

Ecco il set di risultati:

nome_del_venditorevalore_di_venditavalore_di_vendita_successivo
Stef700012000
Alice1200025000
Mili25000NULL

Le righe sono ordinate in base alla colonna specificata in ORDER BY (valore_di_vendita). La funzione LEAD() prende l'importo della vendita dalla riga sottostante. Per esempio, l'importo della vendita di Stef è di 7.000 dollari nella colonna valore_di_vendita, mentre la colonna valore_di_vendita_successivo nello stesso record contiene 12.000 dollari. Quest'ultima proviene dalla colonna valore_di_vendita di Alice, il venditore della riga successiva. Si noti che l'ultima riga non ha una riga successiva, quindi il campo valore_di_vendita_successivo è vuoto (NULL) per l'ultima riga.

Se si specifica solo l'argomento richiesto, vale a dire solo il nome della colonna o un'altra espressione, l'offset è predefinito a 1 e il terzo argomento è predefinito come NULL. Nel nostro esempio, il valore della colonna valore_di_vendita_successivo di Alice proviene dalla colonna valore_di_vendita della riga adiacente sottostante, poiché l'offset predefinito è 1.

Utilizzando LEAD(), è possibile confrontare i valori tra le righe. L'illustrazione seguente mostra come l'importo restituito da LEAD() viene aggiunto alla riga corrente.

Tabella

Uso di LAG() e LEAD() per confrontare i valori

Un uso importante di LAG() e LEAD() nei report è quello di confrontare i valori della riga corrente con quelli della stessa colonna, ma di una riga superiore o inferiore.

Consideriamo la seguente tabella, vendite_annuali, mostrata di seguito:

annovendita_totale
201523000
201625000
201734000
201832000
201933000

Come si può vedere, questa tabella contiene l'importo totale delle vendite per anno. Utilizzando LAG() e LEAD(), è possibile confrontare gli importi delle vendite annuali tra gli anni.

Esaminiamo questa query:

SELECT  anno, vendita_totale AS vendita_totale_corrente,
   LAG(vendita_totale) OVER(ORDER BY anno) AS vendita_totale_precedente,
   vendita_totale - LAG(vendita_totale) OVER(ORDER BY anno) AS differenza
FROM vendite_annuali;

Ecco il set di risultati:

annovendita_totale_correntevendita_totale_precedentedifferenza
201523000NULLNULL
201625000230002000
201734000250009000
20183200034000-2000
201933000320001000

Questa query prende l'importo delle vendite dell'anno precedente e lo inserisce nella colonna vendita_totale_precedente utilizzando la funzione LAG(). ORDER BY nella clausola OVER ordina i record per anno, assicurandosi che la riga adiacente rappresenti l'anno precedente. Quindi prende l'importo dalla colonna vendita_totale della riga precedente e lo riporta alla riga corrente.

Questa query calcola anche la differenza dell'importo delle vendite tra l'anno in corso e l'anno precedente. Questo ci aiuta a capire se c'è stato un aumento (differenza positiva) o una diminuzione (differenza negativa) delle vendite da un anno all'altro.

Per il 2015 non abbiamo informazioni sull'anno precedente. Pertanto, il valore restituito dalla funzione LAG() è NULL così come la differenza. La vendita totale nel 2018 è stata di 32.000 dollari, ma nel 2017 (l'anno precedente) era di 34.000 dollari, come mostrato nella colonna vendita_totale_precedente. La differenza è di -2.000 $, il che indica che nel 2018 c'è stata una diminuzione delle vendite di 2.000 $ rispetto all'anno 2017.

Utilizzo di LAG() e LEAD() con un offset specifico

È possibile utilizzare le funzioni LAG() e LEAD() con due argomenti: il nome della colonna e l'offset.

Consideriamo la seguente tabella, dipendenti:

id_dipendenteannotrimestrebonus
120171100
120172250
12017360
12017420
12018180
12018280
1201830
1201840
1201910
120192100
1201930
120194150

La query seguente seleziona il bonus per il dipendente con id=1 per ogni trimestre di ogni anno. Quindi identifica i bonus per il trimestre corrispondente dell'anno precedente e dell'anno successivo.

SELECT anno, trimestre,
  LAG(bonus,4) OVER(ORDER BY anno,trimestre) AS bonus_precedente,
 bonus AS bonus_corrente,
  LEAD(bonus,4) OVER(ORDER BY anno,trimestre) AS bonus_successivo 
FROM dipendenti
WHERE id_dipendente=1;

La query restituisce il seguente set di risultati:

annotrimestrebonus_precedentebonus_correntebonus_successivo
20171NULL10080
20172NULL25080
20173NULL600
20174NULL200
20181100800
2018225080100
201836000
20184200150
20191800NULL
2019280100NULL
2019300NULL
201940150NULL

Le righe evidenziate in verde sono i record del primo trimestre di ogni anno, quelle in bianco il secondo trimestre di ogni anno, ecc. In ogni riga, l'importo del bonus precedente e quello successivo sono presi dal trimestre corrispondente dell'anno precedente e dell'anno successivo e sono assegnati alle colonne bonus_precedente e bonus_successivo, rispettivamente.

Per esempio, il dipendente id=1 ha ricevuto un bonus di 80 dollari nel primo trimestre del 2018. Per lo stesso dipendente, il bonus del primo trimestre del 2017 era di 100 dollari e quello del 2019 di 0 dollari. ORDER BY specifica che le righe devono essere ordinate per anno e trimestre. Un offset di 4 indica a LEAD() e LAG() di saltare rispettivamente 4 righe prima e dopo quella corrente. Con questo offset, è possibile confrontare i valori dello stesso trimestre di anni diversi, poiché ci sono 4 trimestri in un anno. L'immagine seguente illustra questa idea.

Tabella

Per saperne di più sulle funzioni LAG() e LEAD(), consultate i nostri articoli "Window Functions comuni in SQL: funzioni posizionali " di Aldo Zelen e "Quando devo usare le Window Functions in SQL?" di Tihomir Babic.

Uso di LAG() e LEAD() con un valore predefinito

Nella sezione precedente abbiamo discusso come utilizzare l'argomento offset in LAG() e LEAD(). Ora consideriamo i casi con un terzo argomento: il valore predefinito da assegnare quando il valore ottenuto è NULL. Per specificare questo argomento, è necessario indicare anche il secondo, l'offset. L'offset predefinito è 1, quindi inserite 1 per mantenere l'offset predefinito o un altro valore appropriato per il vostro caso.

Vediamo un altro esempio. La tabella seguente, vendite_prodotti contiene gli id dei prodotti, il mese (1 = gennaio, 2 = febbraio, ecc.) e il conteggio delle vendite per mese.

Ecco i record per i quali l'id del prodotto è 1.

id_prodottomeseconteggio
11125
12135
13NULL
1490

La query:

SELECT id_prodotto, mese,
  LAG(conteggio,1,0) OVER(ORDER BY mese) AS conteggio_precedente,
  conteggio AS conteggio_corrente,
  conteggio - LAG(conteggio,1,0) OVER(ORDER BY mese) AS differenza
FROM vendite_prodotti
WHERE id_prodotto=1;

restituisce il risultato:

id_prodottomeseconteggio_precedenteconteggio_correntedifferenza
110125125
1212513510
13135NULLNULL
14NULL90NULL

Per il prodotto con id=1, selezioniamo il mese di vendita, il conteggio delle vendite per questo mese (conteggio_corrente) e il conteggio delle vendite del mese precedente (il valore della riga precedente restituito da LAG()).

Vorremmo mostrare zero al posto di NULL quando LAG() cerca di ottenere valori da righe diverse da quelle esistenti nel nostro set di dati. Sia per LAG() che per LEAD(), questo risultato si ottiene specificando un terzo argomento, il valore predefinito. Ricordate che l'argomento offset è necessario per specificare l'argomento valore predefinito; in questo caso, indichiamo un offset di 1 per osservare la riga precedente. Quindi specifichiamo 0 come terzo argomento. In questo modo, si azzera qualsiasi tentativo di ottenere valori da righe che non esistono, come nel caso della prima riga (non esiste una riga sopra la prima).

Notate che il valore predefinito zero viene assegnato solo alle righe inesistenti; le righe adiacenti, ma con valori NULL in conteggio_corrente, restano NULL invece di essere sostituite da 0. Un esempio di questo risultato è visibile nella riga relativa ad aprile (mese=4): sebbene conteggio_corrente per la riga precedente (mese=3) sia NULL, il valore non viene sostituito con uno zero, poiché la riga precedente esiste e contiene solo un NULL in conteggio_corrente.

Utilizzo di LAG() e LEAD() con le partizioni

Continuiamo con lo stesso esempio, ma ora esaminiamo il caso in cui abbiamo bisogno di PARTITION BY nella clausola OVER. Di seguito è riportata la parte successiva della tabella vendite_prodotti con un altro prodotto con id=2.

id_prodottomeseconteggio
11125
12135
13NULL
1490
21150
22100
23185
24190

La query:

SELECT id_prodotto, mese,
  LAG(conteggio,1,0) OVER(PARTITION BY id_prodotto ORDER BY mese) AS conteggio_precedente,
  conteggio AS conteggio_corrente,
  conteggio - LAG(conteggio,1,0) OVER(PARTITION BY id_prodotto ORDER BY mese) AS differenza
FROM vendite_prodotti;

restituisce il risultato:

id_prodottomeseconteggio_precedenteconteggio_correntedifferenza
110125125
1212513510
13135NULLNULL
14NULL90NULL
210150150
22150100-50
2310018585
241851905

Abbiamo più prodotti in questa tabella. Per calcolare le differenze tra le vendite attuali e le vendite precedenti separatamente per ciascun prodotto, è necessario specificare PARTITION BY prima di ORDER BY nella clausola OVER.

In PARTITION BY possiamo inserire il nome di una colonna o un elenco di colonne. In questo caso, abbiamo usato la colonna id_prodotto per dividere i record in partizioni e ordinare i risultati per mese all'interno di ogni partizione. L'esito è che ogni partizione inizia con il mese 1 e termina con il mese 4.

PARTITION BY è simile a GROUP BY in quanto raggruppa valori simili. A differenza di GROUP BY, tuttavia, PARTITION BY non comprime le righe originali in una sola; le righe originali restano accessibili. Per ulteriori informazioni sulla differenza tra PARTITION BY e GROUP BY, consultate l'articolo "Che differenza c'è tra GROUP BY e PARTITION BY?".

Le funzioni LAG e LEAD sono molto utili!

Le funzioni di posizionamento come LAG() e LEAD() sono utili in molte situazioni. Spesso vengono utilizzate nella creazione di report, perché possono fare riferimento alle righe superiori o inferiori, come abbiamo visto in questi esempi. Spero che questo articolo vi aiuti ad ampliare le vostre conoscenze di SQL sulle window functions. Per saperne di più sulle funzioni posizionali, leggete gli articoli "Window Functions comuni in SQL: funzioni posizionali " di Aldo Zelen e "Quando devo usare le Window Functions in SQL?" di Tihomir Babic. Se siete interessati a saperne di più sulle funzioni finestra, provate il nostro corso interattivo "Window Functions" sulla piattaforma LearnSQL.it.