23rd Jun 2024 Tempo di lettura: 12 minuti Imparare la clausola OVER() in SQL con 7 esempi Ignacio L. Bisso sql OVER Indice La clausola OVER() in SQL Introduzione ai dati Query di esempio #1: un semplice uso di OVER() Query di esempio n. 2: Uso della sottoclausola PARTITION BY Esempio di query #3: Uso della sottoclausola ORDER BY Query di esempio #4: Calcolo del tempo libero della tabella con OVER() e LAG() Query di esempio #5: Calcolo delle classifiche con la clausola OVER() Query di esempio n. 6: calcolo delle differenze rispetto a un periodo precedente Esempio di query #7: media mobile Pronti a fare pratica con la clausola SQL OVER()? Imparate a utilizzare la clausola OVER() nelle vostre query SQL e farete un passo avanti nella vostra conoscenza dell'SQL. La maggior parte degli analisti di dati e degli sviluppatori SQL conosce bene le clausole SELECT, FROM, WHERE, GROUP BY e ORDER BY. Tuttavia, queste clausole sono solo un sottoinsieme limitato del linguaggio SQL. Esistono altre clausole, come OVER(), che ci permettono di creare report SQL flessibili e aggiungono un'enorme potenza espressiva al linguaggio SQL. In questo articolo spiegheremo la clausola OVER() mostrando alcuni esempi del suo utilizzo. Iniziamo! La clausola OVER() in SQL L'SQL ha una curva di apprendimento abbastanza facile; chiunque può imparare a creare semplici query in SQL. Infatti, il professionista informatico medio o lo sviluppatore SQL gestisce con facilità le clausole di base SELECT, FROM, WHERE, GROUP BY e ORDER BY. Queste clausole erano disponibili in SQL-92, che ha 30 anni! Tuttavia, il linguaggio SQL non si ferma qui: dal 1992 sono stati aggiunti molti nuovi elementi. Uno di questi è la clausola OVER(), che ci permette di utilizzare le funzioni finestra nelle query SQL. In SQL, le funzioni finestra sono simili a GROUP BY in quanto operano su un gruppo di righe. Tuttavia, le funzioni finestra si basano su una finestra di dati, ovvero un insieme di righe correlate alla riga corrente. A differenza di GROUP BY, le funzioni finestra non fanno collassare le righe, ma mantengono intatti i dettagli delle singole righe. Per coloro che desiderano approfondire OVER() e le funzioni finestra in SQL, suggerisco il nostro corso onlineWindow Functions (Funzioni Finestra) . Contiene numerosi esempi di utilizzo di diverse funzioni finestra. Ok, ora torniamo alla funzione OVER(). Per prima cosa, diamo un'occhiata ai nostri dati. Introduzione ai dati Le nostre query di esempio si baseranno sui seguenti dati. Supponiamo di avere un ristorante di pesce sulla costa mediterranea. Abbiamo anche una tabella di database in cui memorizziamo lo storico delle attività del ristorante. Il nostro database ha una tabella chiamata restaurant_activity con queste colonne: table_number waiter_name start_date start_time served_time end_time total_diners amount_payment total_tips. Di seguito è riportato un esempio di dati: Restaurant_activity table_numberwaiter_namestart_datestart_ timeserved_timeend_timetotal_dinersamount_paymenttotal_tips 1John5/5/202211:0311:1711:453350.0037 2Peter5/5/202211:1011:3213:104430.5050 3Mary5/5/202211:3012:0512:402260.3520 1John5/5/202212:0012:3813:104670.1230 3Mary5/5/202212:4313:1213:503320.5020 2Peter6/5/202211:1011:2111:405560.7560 3Mary6/5/202211:4011:5312:403240.1025 1John6/5/202211:3011:5312:301150.0010 3Mary6/5/202214:1014:2014:401240.1025 1Mary6/5/202214:3014:3514:502150.0030 Tutto ciò dovrebbe essere evidente, ma esaminiamo rapidamente alcune colonne. start_date e start_time sono la data e l'ora in cui i clienti hanno ordinato un pasto a table_number; served_time è il momento in cui il pasto è stato servito e end_time è il momento in cui i clienti hanno chiesto il conto. amount_payment è il costo del pasto, esclusa la mancia (che viene memorizzata come total_tips). Query di esempio #1: un semplice uso di OVER() Cominciamo a usare la clausola OVER() per calcolare la percentuale di ogni ordine sul totale delle entrate giornaliere su 5/5/2022. Possiamo calcolare l'incasso totale del ristorante in questo giorno (escluse le mance) usando OVER() combinato con la funzione finestra SUM(). La clausola OVER() è sempre associata a una funzione finestra; le funzioni finestra calcolano un valore sulla base di un insieme di record definiti dalla clausola OVER(). La query è: SELECT start_date AS date, SUM(amount_payment) OVER () AS daily_revenue, amount_payment AS total_order, (amount_payment / SUM(amount_payment) OVER ())*100 AS order_percentage FROM restaurant_activity WHERE start_date = ’5/5/2022’ La query precedente calcola il valore daily_revenue per 5/5/2022 sommando ogni importo di pagamento per questo giorno. La clausola OVER() è vuota, il che significa che la finestra di record utilizzata per calcolare la funzione SUM() è l'insieme completo dei record restituiti dalla query. In altre parole, la finestra è composta da tutti i record per questa data. Nel calcolo della colonna order_percentage, dividiamo l'importo del singolo ordine per il ricavo totale giornaliero per ottenere la percentuale; questo è un punto centrale della query, perché stiamo combinando le colonne a livello di riga con i risultati della funzione finestra in un'unica espressione. Capire quali record fanno parte della finestra è fondamentale per comprendere il funzionamento delle funzioni finestra; torneremo su questo punto più avanti nell'articolo. Per ora guardiamo i risultati: datedaily_revenuetotal_orderorder_percentage 2022-05-052031.47350.0017.23 2022-05-052031.47430.5021.19 2022-05-052031.47260.3512.82 2022-05-052031.47670.1232.99 2022-05-052031.47320.5015.78 Prima di addentrarci in esempi più complessi di OVER(), vorrei suggerire il nostro set di eserciziWindow Functions (Funzioni Finestra) . Se volete imparare davvero a usare OVER(), questi 100 esercizi interattivi faranno al caso vostro. Query di esempio n. 2: Uso della sottoclausola PARTITION BY In questo esempio, utilizzeremo la sottoclausola PARTITION BY, che lavora con OVER() per definire le finestre di dati. Supponiamo di volere un report simile a quello precedente, ma esteso a qualsiasi giorno del maggio 2022. Per ogni giorno, vogliamo vedere il ricavo totale giornaliero, l'importo del singolo ordine per tutti gli ordini e la percentuale del ricavo giornaliero attribuita a ciascun ordine. Ecco la query: SELECT start_date AS date, SUM(amount_payment) OVER (PARTITION BY start_date) AS daily_revenue, amount_payment AS total_order, (amount_payment / SUM(amount_payment) OVER (PARTITION BY start_date)) * 100 AS order_percentage FROM restaurant_activity WHERE start_date BETWEEN ’5/1/2022’ AND ’5/31/2022’ Abbiamo detto che le funzioni finestra funzionano in base a una finestra di record (o a un insieme di record) relativi al record corrente. La query precedente utilizza la sottoclausola PARTITION BY start_date per definire quali record appartengono a ciascuna finestra. Nel nostro esempio, tutti i record con lo stesso valore start_date della riga corrente faranno parte della finestra. Poiché abbiamo solo due valori unici per start_date, abbiamo solo due finestre diverse. I risultati sono visualizzati in verde e in rosso: datedaily_revenuetotal_orderorder_percentage 5/5/20222031.47350.0017.23 5/5/20222031.47430.5021.19 5/5/20222031.47260.3512.82 5/5/20222031.47670.1232.99 5/5/20222031.47320.5015.78 5/6/20221340.95560.7541.82 5/6/20221340.95240.1017.91 5/6/20221340.95150.0011.19 5/6/20221340.95240.1017.91 5/6/20221340.95150.0011.19 Esempio di query #3: Uso della sottoclausola ORDER BY Introduciamo ora la sottoclausola ORDER BY, che consente di inserire le righe in un ordine specifico all'interno della finestra. Supponiamo di voler creare un semplice report con le cinque ordinazioni più costose insieme al cameriere, alla data e al numero del tavolo. La query è: WITH ranking AS ( SELECT table_number, amount_payment, waiter_name, start_date, RANK() OVER (ORDER BY amount_payment DESC) AS position FROM restaurant_activity ) SELECT amount_payment, waiter_name, start_date, table_number, position FROM ranking WHERE position <= 5 ORDER BY position In questo caso, abbiamo usato una CTE (o Common Table Expression) per costruire la classifica. Nella CTE, abbiamo calcolato la posizione di ogni ordine utilizzando la funzione finestra RANK() combinata con la seguente clausola OVER(): RANK() OVER (ORDER BY amount_payment DESC) La clausola precedente definisce una finestra di record formata da tutti i record della tabella, quindi tutti gli ordini del ristorante sono inclusi. Questa finestra viene ordinata da amount_payment in ordine decrescente: l'importo maggiore è il primo record della finestra e così via. La funzione RANK() restituisce la posizione della riga corrente nella finestra ordinata, memorizzando questo valore nella colonna position della CTE. Dopo aver creato la CTE di classificazione, il resto della query utilizza ranking come qualsiasi altra tabella. Filtriamo i record per ottenere solo le prime 5 posizioni, quindi ordiniamo i risultati in base a position. Ecco cosa otteniamo: amount_paymentwaiter_namestart_datetable_numberposition 670.12John2022-05-0511 560.75Peter2022-05-0622 430.50Peter2022-05-0523 350.00John2022-05-0514 320.50Mary2022-05-0535 A questo punto, vorrei suggerire l'articolo SQL Window Functions (Funzioni Finestra) vs. GROUP BY: Qual è la differenza? Spiega le differenze tra la clausola GROUP BY e le funzioni finestra attraverso diversi esempi di query SQL. Un altro articolo interessante con ulteriori dettagli è Cos'è la clausola OVER() in SQL. Query di esempio #4: Calcolo del tempo libero della tabella con OVER() e LAG() Le funzioni di Window includono molti modi per semplificare calcoli complessi. Una di queste è la funzione LAG(), che restituisce un valore da qualsiasi colonna della riga precedente relativa alla riga corrente della finestra. Il proprietario di un ristorante vuole sapere quanto tempo i tavoli sono liberi, ad esempio il tempo che intercorre tra un cliente e l'altro. Per farlo, si può creare un report con le colonne table_number, date, free_start, free_end e free_time_duration. Per calcolare il tempo libero, dobbiamo accedere a due righe. Abbiamo bisogno di end_time dell'occupazione precedente e di start_time dell'occupazione successiva; poi possiamo calcolare il tempo trascorso tra le due. Qui entra in gioco la funzione LAG(), perché LAG() consente di accedere a qualsiasi colonna del record precedente. Ecco la query da utilizzare: SELECT start_date AS date, table_number, -- ending time of the previous occupation COALESCE(LAG(end_time) OVER (PARTITION BY start_date, table_number ORDER BY start_time),'11:00') AS start_free_time, -- starting time of current occupation start_time AS end_free_time, -- calculating the free time when the table was unoccupied start_time - COALESCE(LAG(end_time) OVER (PARTITION BY start_date, table_number ORDER BY start_time),'11:00') AS free_time_duration FROM restaurant_activity La chiave della query precedente è la funzione LAG() window. L'abbiamo usata per ottenere la end_time dell'occupazione precedente. La clausola ... OVER (PARTITION BY start_date, table_number ORDER BY start_time) ... definisce una finestra (o insieme di righe) per ogni coppia distinta di <start_date, table_number>, e ciascuna di queste finestre è ordinata per start_time. Quindi LAG(end_time) restituisce l'ora di fine della precedente occupazione della tabella. Avrete notato che abbiamo usato LAG() due volte. La prima è usata per ottenere l'ora di inizio del periodo libero e la seconda per calcolare la durata del tempo libero usando la seguente espressione: start_time - coalesce(LAG(end_time) OVER (PARTITION BY start_date, table_number ORDER BY start_time),'11:00') I risultati della query sono: datetable_numberstart_free_timeend_free_timefree_time_duration 5/5/2022111:00:0011:03:0000:03:00 5/5/2022111:45:0012:00:0000:15:00 5/5/2022211:00:0011:10:0000:10:00 5/5/2022311:00:0011:30:0000:30:00 5/5/2022312:40:0012:43:0000:03:00 6/5/2022111:00:0011:30:0000:30:00 6/5/2022112:30:0014:30:0002:00:00 6/5/2022211:00:0011:10:0000:10:00 6/5/2022311:00:0011:40:0000:40:00 6/5/2022312:40:0014:10:0001:30:00 Prima di passare alla sezione successiva, vi suggerisco i seguenti articoli per maggiori dettagli: Esempio di funzione SQL Window con spiegazioni Come classificare le righe in SQL: Guida completa La funzione LAG e la funzione LEAD in SQL Come calcolare la lunghezza di una serie con SQL Query di esempio #5: Calcolo delle classifiche con la clausola OVER() In questa sezione, tratteremo una situazione aziendale in cui la clausola SQL OVER() può essere applicata per creare una classifica, come quella dei 10 venditori migliori o dei 5 prodotti più venduti. È possibile utilizzare la clausola OVER() combinata con la funzione RANK() per ottenere questo tipo di report. Vediamo un esempio di query che restituisce le due maggiori mance del giorno e il cameriere che le ha ricevute: SELECT * FROM ( SELECT waiter_name, start_date AS date, total_tips AS tip_amount, RANK() OVER(PARTITION BY start_date ORDER BY total_tips DESC) AS ranking FROM restaurant_activity ) AS ranking_table WHERE ranking <= 2; Abbiamo usato una sottoquery nella clausola FROM per creare una "tabella" temporanea chiamata ranking_table. Utilizza la colonna ranking per memorizzare la posizione della mancia nella classifica giornaliera delle mance. La posizione nella classifica viene calcolata con la funzione RANK(). Le altre colonne della tabella temporanea sono waiter_name, date e tip_amount. Nella query esterna si filtrano solo i suggerimenti classificati 1 e 2. Il risultato della query è mostrato di seguito: waiter_namedatetip_amountranking John5/5/2022501 John5/5/2022372 Peter6/5/2022601 Mary6/5/2022302 Query di esempio n. 6: calcolo delle differenze rispetto a un periodo precedente Un'altra possibilità interessante è quella di mostrare la differenza di valore tra il periodo precedente e quello attuale. La clausola OVER() combinata con le funzioni finestra come LEAD() e LAG() sono utilizzate per creare questo tipo di report. Per ulteriori informazioni, vedere Come calcolare la differenza tra due righe in SQL. Supponiamo di voler vedere le entrate giornaliere di ogni cameriere insieme alla differenza rispetto al giorno precedente. Nella stessa riga, vogliamo anche vedere la differenza espressa in percentuale. Ecco la query: SELECT waiter_name, date, today_revenue, -- revenue variation ---------------------------------------------- LAG(today_revenue) OVER ( PARTITION BY waiter_name ORDER BY date) yesterday_revenue, today_revenue - LAG(today_revenue) OVER ( PARTITION BY waiter_name ORDER BY date) AS revenue_variation, -- ----------------------------------------------------------------- -- revenue variation percentage ------------------------------------------------ round((today_revenue - LAG(today_revenue) OVER (PARTITION BY waiter_name ORDER BY date)) / LAG(today_revenue) OVER (PARTITION BY waiter_name ORDER BY date),2) * 100 AS revenue_variation_percentage -- ----------------------------------------------------------------------------- FROM ( SELECT DISTINCT start_date::date AS "date", waiter_name, SUM(total_tips) OVER ( PARTITION BY waiter_name, start_date::date ORDER BY start_date) AS today_revenue FROM restaurant_activity ) AS daily_revenue_per_waiter; Abbiamo creato una sottoquery nella clausola FROM chiamata daily_revenue_per_waiter che contiene i dati waiter_name, date e il ricavo totale realizzato da questo cameriere in questo giorno. Nella query esterna, utilizziamo la funzione finestra LAG() per ottenere le entrate del giorno precedente, quindi otteniamo la variazione delle entrate tra ieri e oggi e la variazione percentuale. Il risultato è: waiter_namedatetoday_revenueyesterday_revenuerevenue_variationrevenue_variation_percentage John2022-05-0567nullnullnull John2022-05-061067-57-85.00 Mary2022-05-0540nullnullnull Mary2022-05-06804040100.00 Peter2022-05-0550nullnullnull Peter2022-05-0660501020.00 Esempio di query #7: media mobile Le medie mobili sono una metrica spesso utilizzata per attenuare le fluttuazioni a breve termine. Per saperne di più, consultare l'articolo Cos'è una media mobile e come calcolarla in SQL; vediamo come costruirne una utilizzando la clausola OVER(). A titolo di esempio, supponiamo che il proprietario di un ristorante voglia conoscere l'importo medio pagato a persona sull'ultimo tavolo servito in un determinato momento. Utilizza questa media come metrica per sapere quanto pagano i clienti e per attivare alcune promozioni o sconti. Possiamo calcolare facilmente questa media con l'espressione amount_payment/total_diners; tuttavia, il proprietario si è reso conto che questa metrica ha forti fluttuazioni, quindi ha deciso di utilizzare l'importo medio pagato a persona negli ultimi 3 e 6 tavoli serviti. La query per calcolare questo report è la seguente: SELECT start_date AS "date", start_time AS "time", table_number, amount_payment AS total_amount, total_diners, ROUND(amount_payment/total_diners,2) AS diner_avg, ROUND(AVG(amount_payment/total_diners) OVER (ORDER BY start_date, start_time ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),2) AS diner_moving_avg_last_3_tables_served, ROUND(AVG(amount_payment/total_diners) OVER (ORDER BY start_date, start_time ROWS BETWEEN 5 PRECEDING AND CURRENT ROW),2) AS diner_moving_avg_last_6_tables_served FROM restaurant_activity La query precedente calcola 3 diverse medie. La prima è una media semplice basata sull'espressione: ROUND(amount_payment/total_diners,2) La seconda media è la media mobile per gli ultimi 3 tavoli serviti; la terza media è la stessa, ma per gli ultimi 6 tavoli serviti: ROUND(AVG(amount_payment/total_diners) OVER (ORDER BY start_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),2) … OVER (ORDER BY start_date, start_time ROWS BETWEEN 5 PRECEDING AND CURRENT ROW),2) Il termine "ROWS BETWEEN 2 PRECEDING AND CURRENT ROW" specifica che la media è calcolata sulla base di 3 righe: la riga corrente e le 2 righe immediatamente precedenti. La finestra è ordinata in base all'ora di inizio del tavolo. Ecco il risultato: datetimetable_numbertotal_amountdinersdiner_avgdiner_moving_avg_last_3_tables_serveddiner_moving_avg_last_6_tables_served 2022-05-0511:031350.003116.67116.67116.67 2022-05-0511:102430.504107.63112.15112.15 2022-05-0511:303260.352130.18118.16118.16 2022-05-0512:001670.124167.53135.11130.50 2022-05-0512:433320.503106.83134.85125.77 2022-05-0611:102560.755112.15128.84123.50 2022-05-0611:403240.10380.0399.67117.39 2022-05-0611:301150.001150.00114.06124.45 2022-05-0614:103240.101240.10156.71142.77 2022-05-0614:301150.00275.00155.03127.35 Altri usi della clausola OVER() includono il calcolo dei totali progressivi (utile in tutti i tipi di analisi finanziaria) e il calcolo della lunghezza di una serie di dati. Pronti a fare pratica con la clausola SQL OVER()? Abbiamo mostrato diverse query con la clausola SQL OVER(). Poiché OVER() deve essere utilizzata in combinazione con una funzione finestra, abbiamo trattato anche alcune di queste: SUM(), AVG(), LAG() e RANK(). Se volete mettere in pratica le vostre nuove conoscenze sulla clausola SQL OVER(), vi consiglio il nostro corso interattivo Window Functions (Funzioni Finestra) seguito dal nostro set di esercitazioniWindow Functions (Funzioni Finestra) . Potete leggere il corso nell'articolo Corso SQL del mese - Window Functions (Funzioni Finestra). Potete anche ottenere una copia del nostro foglio informativo gratuito su SQL Window Functions (Funzioni Finestra) per aiutarvi nel vostro percorso di apprendimento. Sviluppate le vostre competenze e aumentate il vostro patrimonio! Tags: sql OVER