Una funzione finestra aggiunge una colonna senza comprimere le righe
GROUP BY riduce molte righe a una. Una funzione finestra fa qualcosa di diverso: calcola un valore su un insieme di righe collegate ma tiene ogni riga di input nell'output. Ottieni il dettaglio riga per riga e l'aggregato, uno accanto all'altro.
La forma è sempre la stessa: una funzione, poi OVER (...).
La colonna total_all mostra il totale complessivo di tutte le righe, ripetuto su ogni riga. Le righe originali restano intatte. Confrontalo con SELECT SUM(amount) FROM sales: stesso numero, ma torna una sola riga. Le funzioni finestra ti danno entrambe le viste insieme.
PARTITION BY: aggregare dentro i gruppi
Un OVER () vuoto aggrega su tutta la tabella. Aggiungi PARTITION BY per aggregare dentro dei gruppi, un po' come GROUP BY, ma di nuovo senza comprimere le righe.
Ogni riga riceve il totale della propria regione e la propria quota di quel totale. Con un semplice GROUP BY perderesti il dettaglio per dipendente. È questo il grande vantaggio delle funzioni finestra: dettaglio e aggregato in una sola query.
Ranking: ROW_NUMBER, RANK, DENSE_RANK
La famiglia del ranking numera le righe secondo un ORDER BY dentro OVER. Le tre varianti si distinguono per come gestiscono i pareggi.
Come leggere l'output:
ROW_NUMBER()è sempre unico: i pareggi vengono risolti in modo arbitrario. Usalo quando ti serve un numero stabile e distinto per ogni riga.RANK()dà la stessa posizione alle righe a pari merito, poi salta i numeri successivi. Due giocatori a pari merito in posizione 1 sono seguiti dalla posizione 3.DENSE_RANK()gestisce anch'esso i pareggi, ma non salta. La posizione successiva è 2.
Per i "primi N per gruppo", combina il ranking con PARTITION BY e filtra in una query esterna: WHERE non può fare riferimento direttamente alle funzioni finestra:
I due che hanno guadagnato di più per ogni regione.
LAG e LEAD: guardare le righe vicine
LAG(col) restituisce il valore di col dalla riga precedente nella finestra. LEAD(col) guarda in avanti. Entrambe sono perfette per le domande sui cambiamenti nel tempo.
Lo yesterday della prima riga è NULL: prima non c'è niente. Puoi fornire un valore predefinito: LAG(celsius, 1, celsius) OVER (ORDER BY day) userebbe il valore di oggi quando non esiste una riga precedente.
LEAD è l'immagine speculare. Combinale con PARTITION BY per ottenere sequenze per gruppo, per esempio confrontare le vendite di questo mese con quelle del mese precedente dentro ogni regione.
Totali progressivi con i frame della finestra
Aggiungi ORDER BY dentro OVER e le funzioni di aggregazione come SUM, AVG, COUNT iniziano a calcolare in modo cumulativo:
Due cose da notare:
SUM(amount) OVER (ORDER BY day)è un totale progressivo. Il frame predefinito quando scriviORDER BYsenza un frame esplicito è "dall'inizio della finestra fino alla riga corrente".- La seconda colonna usa un frame esplicito:
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW. È una finestra scorrevole di 3 righe: una media mobile.
Il modello mentale per i frame: ogni funzione finestra viene valutata su un frame di righe, definito rispetto alla riga corrente. I frame più comuni:
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: totale progressivo (il predefinito implicito).ROWS BETWEEN N PRECEDING AND CURRENT ROW: finestra che segue la riga corrente.ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING: l'intera partizione.
ROWS conta le righe fisiche. Esiste anche RANGE, che raggruppa per valore: comodo quando ci sono pareggi nella colonna dell'ORDER BY e vuoi trattarli come un unico passo.
FIRST_VALUE, LAST_VALUE, NTILE
Qualche altra funzione finestra che vale la pena conoscere:
FIRST_VALUEeLAST_VALUErestituiscono il primo o l'ultimo valore dentro il frame. ConLAST_VALUEfai attenzione al frame: quello predefinito finisce aCURRENT ROW, quindi di solito ti serveROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWINGper ottenere davvero l'ultimo valore della partizione.NTILE(n)divide le righe inngruppi di dimensioni circa uguali: utile per quartili, percentili e suddivisioni in stile A/B.
Dare un nome a una finestra con WINDOW
Quando più colonne condividono la stessa clausola OVER (...), la ripetizione diventa noiosa. SQLite ti permette di dare un nome a una finestra una volta e riutilizzarla:
Stessa query, meno rumore. La clausola WINDOW va dopo WHERE/GROUP BY/HAVING e prima di ORDER BY.
Funzioni finestra vs GROUP BY
Entrambe riguardano l'aggregazione, ma rispondono a domande diverse:
GROUP BYriduce. Una riga per gruppo. Usalo quando vuoi solo il riepilogo.- Le funzioni finestra conservano. Ogni riga di input sopravvive, con colonne calcolate in più accanto.
Se ti capita di fare un GROUP BY e poi riunire gli aggregati alla tabella originale con un join, è un segnale forte che una funzione finestra farebbe il lavoro in una sola query.
Un paio di insidie
WHEREnon può fare riferimento alle funzioni finestra. I filtri vengono applicati prima che le finestre siano calcolate. Racchiudi la query in una subquery o in una CTE e filtra al livello esterno.- I frame impliciti fanno brutti scherzi.
SUM(x) OVER (ORDER BY y)è un totale progressivo perché il frame predefinito èRANGE UNBOUNDED PRECEDING. Se volevi la somma dell'intera partizione, scriviOVER (PARTITION BY ...)senza unORDER BY, oppure specifica il frame in modo esplicito. LAST_VALUEsorprende tutti la prima volta. Con il frame predefinito che finisce alla riga corrente, restituisce il valore corrente, non l'ultimo della partizione. Cambia il frame.- Le funzioni finestra richiedono SQLite 3.25+ (uscito nel 2018). Qualsiasi installazione ragionevolmente moderna le ha, ma alcuni ambienti embedded restano indietro.
Prossimo passo: colonne generate
Le funzioni finestra sono un calcolo al momento della query. La prossima pagina parla del calcolo al momento della memorizzazione: le colonne generate, in cui il valore della colonna è definito da un'espressione e si aggiorna automaticamente quando cambiano i dati sottostanti.
Domande frequenti
Cosa sono le funzioni finestra in SQLite?
Le funzioni finestra (window function) calcolano un valore su un insieme di righe collegate alla riga corrente, senza comprimerle come fa GROUP BY. Aggiungi una clausola OVER (...) a funzioni come ROW_NUMBER(), RANK(), SUM() o LAG() per definire la finestra. Ogni riga di input resta nel risultato: ottieni solo una colonna calcolata in più.
Che differenza c'è tra RANK e DENSE_RANK in SQLite?
Entrambe assegnano una posizione in base a ORDER BY, ma gestiscono i pareggi in modo diverso. RANK() lascia dei buchi dopo i pareggi: due righe a pari merito in posizione 1 sono seguite dalla posizione 3. DENSE_RANK() no: la riga successiva riceve la posizione 2. Usa DENSE_RANK() quando vuoi posizioni consecutive, RANK() quando il buco ha un significato.
Come calcolo un totale progressivo in SQLite?
Usa SUM(column) OVER (ORDER BY ...) con un frame della finestra. Per impostazione predefinita, un ORDER BY dentro OVER usa il frame RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, che ti dà un totale progressivo. Aggiungi PARTITION BY per far ripartire il totale progressivo per ogni gruppo.