In questa lezione si apprende come codificare le query che riassumono i dati. Ad esempio, è possibile utilizzare query di riepilogo per riportare i totali delle vendite per fornitore o stato, oppure per ottenere un conteggio del numero di fatture elaborate ogni giorno del mese. Imparerete anche a usare un tipo speciale di funzione, chiamata funzione aggregate. Le funzioni aggregate consentono di eseguire facilmente operazioni come calcolare medie o totali o trovare il valore più alto per una determinata colonna. Le utilizzerete spesso nelle vostre query di riepilogo.
In questo capitolo imparerete a usare le funzioni aggregate, che operano su una serie di valori e restituiscono un singolo valore di riepilogo. Poiché le funzioni aggregate operano tipicamente sui valori delle colonne, vengono talvolta chiamate funzioni di colonna. Una query che contiene una o più funzioni aggregate viene generalmente chiamata query di riepilogo.
L'esempio qui sotto presenta la sintassi delle funzioni aggregate più comuni. Poiché lo scopo di queste funzioni si spiega da sé, mi concentrerò principalmente sul loro utilizzo. Tutte le funzioni, tranne una, operano su un'espressione. Nella query di questo esempio, l'espressione codificata per la funzione SUM calcola il saldo di una fattura utilizzando le colonne lnvoiceTotal, PaymentTotal e CreditTotal. Il risultato è un singolo valore che rappresenta l'importo totale di tutte le fatture selezionate. Se si esamina la clausola WHERE di questo esempio, si può notare che include solo le fatture con un saldo da pagare. Oltre a un'espressione, è possibile codificare la parola chiave ALL o DISTINCT in queste funzioni. ALL è l'opzione predefinita, che significa che tutti i valori sono inclusi nel calcolo. L'eccezione è rappresentata dai valori nulli, che sono sempre esclusi da queste funzioni. Se non si desidera includere i valori duplicati, è possibile utilizzare la parola chiave DISTINCT. Nella maggior parte dei casi, si usa DISTINCT solo con la funzione COUNT. Non si usa con MIN o MAX perché non ha alcun effetto su queste funzioni. E di solito non ha senso usarla con le funzioni AVG e SUM. A differenza delle altre funzioni di aggregazione, non è possibile utilizzare le parole chiave ALL o DISTINCT o un'espressione con COUNT(*). Questa funzione deve essere codificata esattamente come mostrato nella sintassi. Il valore restituito da questa funzione è il numero di righe nella tabella di base che soddisfano la condizione di ricerca della query, incluse le righe con valori nulli. La funzione COUNT(*) nella query di questa figura, ad esempio, indica che la tabella Fatture contiene 11 fatture con un saldo da pagare.
Sintassi delle funzioni | Risultato |
---|---|
AVG ([ALL | DISTINCT] expression) | La media dei valori non nulli dell'espressione. |
SUM ([ALL | DISTINCT] expression) | Il totale dei valori non nulli nell'espressione. |
MIN ([ALL | DISTINCT] expression) | Il valore non nullo più basso dell'espressione. |
MAX ([ALL | DISTINCT] expression) | Il valore non nullo più alto dell'espressione. |
COUNT ( [ALL | DISTINCT] expression) | Il numero di valori non nulli nell'espressione. |
COUNT (*) | Il numero di righe selezionate dalla query. |
USE AP
GO
SELECT COUNT (*) AS NumberOfinvoices,
SUM (InvoiceTotal - PaymentTotal - CreditTotal ) AS TotalDue
FROM Invoices
WHERE (InvoiceTotal - PaymentTotal - CreditTotal) > 0
L'esempio qui sotto presenta altre quattro query che utilizzano funzioni aggregate. Prima di descrivere queste query, è necessario sapere che, con tre eccezioni, una clausola SELECT che contiene una funzione aggregata può contenere solo funzioni aggregate. La prima eccezione è se la specifica della colonna risulta in un valore letterale. Questo è illustrato dalla prima colonna nelle prime due query di questa figura. La seconda eccezione è se la query include una clausola GROUP BY. In questo caso, la clausola SELECT può includere tutte le colonne specificate nella clausola GROUP BY. La terza eccezione è se le funzioni aggregate includono la clausola OVER. In questo caso, la clausola SELECT può includere qualsiasi colonna delle tabelle di base. Si vedrà come si usano le clausole GROUP BY e OVER più avanti in questa lezione. Le prime due query di questa figura utilizzano la funzione COUNT(*) per contare il numero di righe della tabella Fatture che soddisfano la condizione di ricerca. In entrambi i casi, solo le fatture con data di fatturazione successiva al 15/12/2022 sono incluse nel conteggio. Inoltre, la prima query utilizza la funzione AVG per calcolare l'importo medio delle fatture e la funzione SUM per calcolare l'importo totale delle fatture. La seconda query, invece, utilizza le funzioni MIN e MAX per calcolare l'importo minimo e massimo delle fatture. Sebbene le funzioni MIN, MAX e COUNT siano tipicamente utilizzate per le colonne che contengono dati numerici, possono essere utilizzate anche su colonne contenenti dati di carattere o di data. Nella terza query, ad esempio, vengono usate sulla colonna VendorName della tabella Vendors. In questo caso, la funzione MIN restituisce il nome del fornitore che si trova più in basso nella sequenza di ordinamento, la funzione MAX restituisce il nome del fornitore che si trova più in alto nella sequenza di ordinamento e la funzione COUNT restituisce il numero totale di fornitori. Si noti che, poiché la colonna VendorName non può contenere valori nulli, la funzione COUNT(*) avrebbe restituito lo stesso risultato. La quarta query illustra come l'uso della parola chiave DISTINCT possa influenzare il risultato di una funzione COUNT. Qui, la prima funzione COUNT utilizza la parola chiave DISTINCT per contare il numero di fornitori che hanno fatture datate 2015-09-01 o successive nella tabella Fatture. Per farlo, cerca valori distinti nella colonna VendorID. la colonna VendorID. Al contrario, poiché la seconda funzione COUNT non include la parola chiave DISTINCT, conta tutte le fatture successive al 2015-09-01. Naturalmente è possibile ottenere lo stesso risultato utilizzando la funzione COUNT(*). Ho usato COUNT(VendorlD) solo per illustrare la differenza tra la codifica e la non codifica della parola chiave DISTINCT.
USE AP
GO
SELECT 'After 15/12/2022' AS SelectionDate,
COUNT (*) AS NumberOfinvoices,
AVG (InvoiceTotal) AS AverageinvoiceAmount,
SUM (InvoiceTotal) AS TotalinvoiceAmount
FROM Invoices
WHERE InvoiceDate > '2022-12-15'
USE AP
GO
SELECT 'After 15/12/2022' AS SelectionDate,
COUNT (*) AS NumberOfinvoices,
MAX (InvoiceTotal) AS HighestinvoiceTotal,
MIN (InvoiceTotal) AS LowestinvoiceTotal
FROM Invoices
WHERE InvoiceDate > '2022-12-15'
USE AP
GO
SELECT MIN (VendorName) AS Firstvendor,
MAX (VendorName) AS Lastvendor,
COUNT (VendorName) AS NumberOfVendors
FROM Vendors
USE AP
GO
SELECT COUNT (DISTINCT VendorID) AS NumberOfVendors,
COUNT (VendorID) AS NumberOfinvoices,
AVG (InvoiceTotal) AS AverageinvoiceAmount,
SUM (InvoiceTotal) AS TotalinvoiceAmount
FROM Invoices
WHERE InvoiceDate > '2022-12-15'
Ora che avete capito come funzionano le funzioni aggregate, siete pronti per imparare a raggruppare i dati e a usare le funzioni aggregate per riassumere i dati di ciascun gruppo. Per farlo, è necessario conoscere altre due clausole dell'istruzione SELECT: GROUP BY e HAVING.
L'esempio qui sotto presenta la sintassi dell'istruzione SELECT con le clausole GROUP BY e HAVING. La clausola GROUP BY determina come vengono raggruppate le righe selezionate e la clausola HAVING determina quali gruppi sono inclusi nei risultati finali. Come si può notare, queste clausole sono codificate dopo la clausola WHERE ma prima della clausola ORDER BY. Questo ha senso perché la condizione di ricerca nella clausola WHERE viene applicata prima che le righe vengano raggruppate. La sequenza di ordinamento nella clausola ORDER BY viene applicata dopo il raggruppamento delle righe. Nella clausola GROUP BY, si elencano una o più colonne o espressioni separate da virgole. Quindi, le righe che soddisfano la condizione di ricerca nella clausola WHERE vengono raggruppate in base a queste colonne o espressioni in ordine crescente. Ciò significa che viene restituita una singola riga per ogni serie unica di valori nelle colonne GROUP BY. Ciò avrà più senso quando si vedranno gli esempi nella prossima figura che raggruppano per due colonne. Per ora, guardate l'esempio di questa figura che raggruppa per una sola colonna. Questo esempio calcola l'importo medio delle fatture per ogni fornitore che ha fatture nella tabella Fatture di importo medio superiore a 2.000 dollari. Per farlo, raggruppa le le fatture per VendorID. Quindi, la funzione AVG calcola la media della colonna lnvoiceTotal. Poiché la query include una clausola GROUP BY, questa funzione calcola il totale medio delle fatture per ogni gruppo e non per l'intero set di risultati. In questo caso, la funzione aggregata è chiamata aggregato vettoriale. Al contrario, le funzioni di aggregazione come quelle viste in precedenza in questo capitolo che restituiscono un singolo valore per tutte le righe di un insieme di risultati sono chiamate aggregati scalari. L'esempio di questa figura include anche una clausola HAVING. La condizione di ricerca questa clausola specifica che solo i fornitori con fatture che superano in media i 2.000 dollari. Si noti che questa condizione deve essere applicata dopo che le righe sono state raggruppate e la media per ogni gruppo è stata calcolata. Oltre alla funzione AVG, la clausola SELECT include la colonna VendorID. Questo ha senso dato che le righe sono raggruppate in base a questa colonna. Tuttavia, le colonne utilizzate nella clausola GROUP BY non devono essere incluse nella clausola SELECT.
SELECT select_list
FROM table_source
[WHERE search_condition]
[GROUP BY group_by_list]
[HAVING search_condition]
[ORDER BY order_by_list]
USE AP
GO
SELECT VendorID, AVG(InvoiceTotal) AS AverageinvoiceAmount
FROM Invoices
GROUP BY VendorID
HAVING AVG(InvoiceTotal) > 2000
ORDER BY AverageinvoiceAmount DESC
L'esempio qui sotto presenta altre tre query che raggruppano i dati. Se si è compresa la query della figura precedente, non si dovrebbero avere problemi a capire come funziona la prima query di questa figura. Raggruppa le righe della tabella Fatture per VendorID e restituisce un conteggio del numero di fatture per ogni fornitore. La seconda query di questa figura illustra come sia possibile raggruppare per più di una colonna. In questo caso, si utilizza un join per combinare le colonne VendorState e VendorCity della tabella Vendors con il conteggio e la media delle fatture della tabella Invoices. Poiché le righe sono raggruppate per stato e città, viene restituita una riga per ogni combinazione di stato e città. Quindi, la clausola ORDER BY ordina le righe per città all'interno dello stato. Senza questa clausola, le righe verrebbero restituite in nessuna sequenza particolare. La terza query è identica alla seconda, ma include la clausola HAVING. Questa clausola utilizza la funzione COUNT per limitare i gruppi di stati e città inclusi nell'insieme dei risultati a quelli che hanno due o più fatture. In altre parole, esclude i gruppi che hanno una sola fattura.
USE AP
GO
SELECT VendorID, COUNT(*) AS InvoiceQty
FROM Invoices
GROUP BY VendorID
USE AP
GO
SELECT VendorState, VendorCity, COUNT(*) AS InvoiceQty,
AVG(InvoiceTotal) AS InvoiceAvg
FROM Invoices JOIN Vendors
ON Invoices.VendorID = Vendors.VendorID
GROUP BY VendorState, VendorCity
ORDER BY VendorState, VendorCity
USE AP
GO
SELECT VendorState, VendorCity, COUNT(* ) AS InvoiceQty,
AVG(InvoiceTotal) AS InvoiceAvg
FROM Invoices JOIN Vendors
ON Invoices.VendorID = Vendors.VendorID
GROUP BY VendorState, VendorCity
HAVING COUNT(*) >= 2
ORDER BY VendorState, VendorCity
Come si è visto, è possibile limitare i gruppi inclusi in un insieme di risultati codificando una condizione di ricerca nella clausola HAVING. Inoltre, è possibile applicare una condizione di ricerca a ogni riga prima che venga inclusa in un gruppo. A tale scopo, si codifica la condizione condizione di ricerca nella clausola WHERE come per qualsiasi istruzione SELECT. Per essere sicuri di comprendere le differenze tra le condizioni di ricerca codificate nelle clausole HAVING e WHERE, la figura successiva presenta due esempi. Nel primo esempio, le fatture della tabella Invoices sono raggruppate in base al nome del fornitore e per ogni gruppo vengono calcolati il conteggio e l'importo medio della fattura. Poi, la clausola HAVING limita i gruppi nel set di risultati a quelli che hanno un totale medio delle fatture superiore a 500 dollari. Al contrario, il secondo esempio include una condizione di ricerca nella clausola WHERE che limita le fatture incluse nei gruppi a quelle che hanno un totale della fattura superiore a 500 dollari. In altre parole, la condizione di ricerca in questo esempio viene applicata a ogni riga. Nell'esempio precedente, invece, è stata applicata a ogni gruppo di righe. Oltre a questo, esistono anche due differenze nelle espressioni che si possono includere nelle clausole WHERE e HAVING. Innanzitutto, la clausola HAVING può includere funzioni di aggregazione, come si è visto nel primo esempio di questa figura, ma la clausola WHERE non può. Questo perché la condizione di ricerca in una clausola WHERE viene applicata prima che le righe vengano raggruppate. In secondo luogo, sebbene la clausola WHERE può fare riferimento a qualsiasi colonna delle tabelle di base, la clausola HAVING può fare riferimento solo a colonne incluse nella clausola SELECT o GROUP BY. Questo perché filtra l'insieme dei risultati riassunti definito dalle clausole SELECT, FROM, WHERE e GROUP BY. In altre parole, non filtra le tabelle di base.
USE AP
GO
SELECT VendorName, COUNT(*) AS InvoiceQty,
AVG(InvoiceTotal) AS InvoiceAvg
FROM Vendors JOIN Invoices
ON Vendors.VendorID = Invoices.VendorID
GROUP BY VendorName
HAVING AVG(InvoiceTotal) > 500
ORDER BY InvoiceQty DESC
USE AP
GO
SELECT VendorName, COUNT(*) AS InvoiceQty,
AVG(InvoiceTotal) AS InvoiceAvg
FROM Vendors JOIN Invoices
ON Vendors.VendorID = Invoices.VendorID
WHERE InvoiceTotal > 500
GROUP BY VendorName
ORDER BY InvoiceQty DESC
È possibile codificare condizioni di ricerca composte in una clausola HAVING proprio come in una clausola WHERE. Questo è illustrato dalla prima query degli esempi che seguono. Questa raggruppa le fatture per data di fatturazione e calcola il conteggio delle fatture e la somma dei totali delle fatture per ogni data. Inoltre, la clausola HAVING specifica tre condizioni. In primo luogo, la data della fattura deve essere compresa tra 2022-12-15 e 2023-03-15. In secondo luogo, il conteggio delle fatture deve essere maggiore di 1. Infine, la somma dei totali delle fatture deve essere superiore a 100 dollari. Dato che la seconda e la terza condizione nella clausola HAVING di questa query includono funzioni aggregate, devono essere codificate nella clausola HAVING. La prima condizione, invece, non include una funzione aggregata, quindi può essere codificata sia nella clausola HAVING che in quella WHERE. La seconda istruzione in questa figura, ad esempio, mostra questa condizione codificata nella clausola WHERE. Si noti che restituisce lo stesso insieme di risultati indipendentemente dalla codifica di questa condizione. Come si fa a sapere dove codificare una condizione di ricerca? In generale, credo che il codice sia più facile da leggere se si includono tutte le condizioni di ricerca nella clausola HAVING. Se, invece, si preferisce codificare le condizioni di ricerca non aggregate nella clausola WHERE, va bene lo stesso. Poiché una condizione di ricerca nella clausola WHERE viene applicata prima del raggruppamento delle righe raggruppate, mentre una condizione di ricerca nella clausola HAVING viene applicata solo dopo il raggruppamento. Ci si potrebbe aspettare un vantaggio in termini di prestazioni codificando tutte le condizioni di ricerca nella clausola HAVING. Tuttavia, SQL Server si occupa di questo problema di prestazioni quando ottimizza la query. A tal fine, sposta automaticamente le condizioni di ricerca nella clausola che garantisce le migliori prestazioni, purché ciò non modifichi la logica della query. Di conseguenza, è possibile codificare le condizioni di ricerca dove il codice risulta più leggibile senza preoccuparsi delle prestazioni del sistema.
USE AP
GO
SELECT InvoiceDate, COUNT (*) AS InvoiceQty, SUM (InvoiceTotal) AS InvoiceSum
FROM Invoices
GROUP BY InvoiceDate
HAVING InvoiceDate BETWEEN '2022-12-15' AND '2023-03-15'
AND COUNT (*) > 1
AND SUM (InvoiceTotal) > 100
ORDER BY InvoiceDate DESC
USE AP
GO
SELECT InvoiceDate, COUNT (*) AS InvoiceQty, SUM (InvoiceTotal ) AS InvoiceSum
FROM Invoices
WHERE InvoiceDate BETWEEN '2022-12-15' AND '2023-03-15'
GROUP BY InvoiceDate
HAVING COUNT (*) > 1
AND SUM (InvoiceTotal ) > 100
ORDER BY InvoiceDate DESC
Finora in questa lezione sono state trattate le parole chiave e le funzioni standard di SQL. Tuttavia, è necessario conoscere anche le quattro estensioni che SQL Server mette a disposizione per il riepilogo dei dati: gli operatori ROLLUP, CUBE e GROUPING SETS e la clausola OVER.
È possibile utilizzare l'operatore ROLLUP per aggiungere una o più righe di riepilogo a un insieme di risultati che utilizza raggruppamenti e aggregati. I due esempi che seguono illustrano come funziona. Il primo esempio mostra come funziona l'operatore ROLLUP quando si raggruppa per una singola colonna. In questo caso, le fatture nella tabella Fatture sono raggruppate per VendorID e per ogni fornitore vengono calcolati il conteggio e il totale delle fatture. Si noti che, poiché la frase WITH ROLLUP è inclusa nella clausola GROUP BY, viene aggiunta un'ulteriore riga alla fine dell'insieme dei risultati. Questa riga riepiloga tutte le colonne aggregate nel set di risultati. In questo caso, riassume le colonne lnvoiceCount e lnvoiceTotal. Poiché la colonna VendorID non può essere riassunta, le viene assegnato un valore nullo. La seconda query in questa figura mostra come funziona l'operatore ROLLUP quando si raggruppa per due colonne. Questa query raggruppa i venditori nella tabella Vendors per stato e città e conta il numero di venditori in ciascun gruppo. Si noti che, oltre a una riga di riepilogo alla fine del set di risultati, sono incluse righe di riepilogo per ogni stato. Si noti anche la clausola ORDER BY di questa query. Essa fa sì che le righe del set di risultati siano ordinate per città in sequenza decrescente e per stato in sequenza decrescente. Il motivo per cui queste colonne sono ordinate in sequenza decrescente è che l'ordinamento viene eseguito dopo che le righe di riepilogo sono state aggiunte all'insieme dei risultati e queste righe hanno valori nulli nella colonna VendorCity. Inoltre, la riga di riepilogo finale ha un valore nullo nella colonna VendorState. Quindi, se si ordinassero queste colonne in ordine crescente, le righe con valori nulli apparirebbero prima delle righe che riassumono, il che non è quello che si vuole. È inoltre possibile utilizzare un'altra funzione, la funzione GROUPING (raggruppamento), per lavorare con le colonne nulle in una riga di riepilogo. Tuttavia, questa funzione viene tipicamente utilizzata assieme alla funzione CASE, di cui si parlerà nella lezione 9. Quindi la funzione GROUPING verrà presentata in quella lezione. SQL Server 2008 ha introdotto una sintassi alternativa che può essere utilizzata per lavorare con l'operatore ROLLUP, ed è mostrata sotto entrambi gli esempi. Per utilizzare questa sintassi, si codifica l'operatore ROLLUP subito dopo le parole chiave GROUP BY. Quindi, si codificano le colonne all'interno di una serie di parentesi. Il vantaggio di questa sintassi è che è più coerente con le altre estensioni di SQL Server. Lo svantaggio è che questa sintassi non è supportata dalle versioni precedenti di SQL Server come SQL Server 2005.
USE AP
GO
SELECT VendorID, COUNT (*) AS InvoiceCount,
SUM (InvoiceTotal) AS InvoiceTotal
FROM Invoices
WHERE InvoiceDate BETWEEN '2022-12-15' AND '2023-03-15'
GROUP BY VendorID WITH ROLLUP
GROUP BY ROLLUP (VendorID)
USE AP
GO
SELECT VendorState, VendorCity, COUNT (*) AS QtyVendors
FROM Vendors
WHERE VendorState IN ( 'IA', 'NJ' )
GROUP BY VendorState, VendorCity WITH ROLLUP
ORDER BY VendorState DESC, VendorCity DESC
GROUP BY ROLLUP (VendorState, VendorCity)
L'esempio che segue mostra come utilizzare l'operatore CUBE. Questo operatore è simile all'operatore ROLLUP, ma aggiunge righe di riepilogo per ogni combinazione di gruppi. Questo è illustrato dai due esempi della figura. Come si può notare, questi esempi sono identici a quelli precedenti, tranne che per l'uso dell'operatore CUBE al posto dell'operatore ROLLUP. Nel primo esempio, il set di risultati è raggruppato per una singola colonna. In questo caso, alla fine del set di risultati viene aggiunta una riga di riepilogo che riassume tutti i gruppi. In altre parole, il funzionamento è identico a quello dell'operatore ROLLUP. Nel secondo esempio, invece, si può notare come CUBE differisca da ROLLUP quando si raggruppa per due o più colonne. In questo caso, il set di risultati include una riga di riepilogo per ogni stato, proprio come quando è stato utilizzato l'operatore ROLLUP. Inoltre, include una riga di riepilogo per ogni città. L'ottava riga in questa figura, ad esempio, indica che ci sono due venditori nelle città di nome Washington. Se si osservano la prima e la quinta riga dell'insieme di risultati, si vedrà che uno di questi venditori si trova a Washington, New Jersey e uno a Washington, Iowa. Lo stesso vale per la città di Fairfield. Ci sono anche due fornitori nella città di East Brunswick, ma entrambi si trovano nel New Jersey. Come per l'operatore ROLLUP, SQL Server 2008 ha introdotto una sintassi alternativa che è possibile utilizzare per lavorare con l'operatore CUBE. Questa sintassi è mostrata di seguito a entrambi gli esempi. Se avete capito come funziona la sintassi per l'operatore ROLLUP, non ci dovrebbero avere problemi a capire come funziona per l'operatore CUBE. Anche se questa sintassi non funziona per le versioni di SQL Server precedenti alla 2008, è possibile utilizzarla per i nuovi sviluppi. Ora che avete visto come funziona l'operatore CUBE, vi starete chiedendo quando utilizzarlo. Il fatto è che probabilmente non lo userete se non per aggiungere una riga di riepilogo a un insieme di risultati raggruppati per una singola colonna. E in quel caso, si potrebbe usare l'operatore ROLLUP. In alcuni casi particolari, tuttavia, l'operatore CUBE può fornire informazioni utili che non si possono ottenere in altro modo.
USE AP
GO
SELECT VendorID, COUNT (*) AS InvoiceCount,
SUM (InvoiceTotal) AS InvoiceTotal
FROM Invoices
WHERE InvoiceDate BETWEEN '2022-12-15' AND '2023-03-15'
GROUP BY VendorID WITH CUBE
GROUP BY CUBE (VendorID)
USE AP
GO
SELECT VendorState, VendorCity, COUNT ( * ) AS QtyVendors
FROM Vendors
WHERE VendorState IN ( 'IA', 'NJ' )
GROUP BY VendorState, VendorCity WITH CUBE
ORDER BY VendorState DESC, VendorCity DESC
GROUP BY CUBE (VendorState, VendorCity)
L'esempio che segue mostra come utilizzare l'operatore GROUPING SETS, introdotto con SQL Server 2008. Questo operatore è simile agli operatori ROLLUP e CUBE. Tuttavia, l'operatore GROUPING SETS include solo le righe di riepilogo per ogni gruppo specificato e utilizza una sintassi simile a quella del 2008 per gli operatori ROLLUP e CUBE. Il primo esempio di questa figura è simile al secondo esempio presentato nelle due figure precedenti. Tuttavia, questo esempio utilizza l'operatore GROUPING SETS invece dell'operatore CUBE o ROLLUP. Qui, il set di risultati include solo righe di riepilogo per i due elementi di raggruppamento: stato e città. Per iniziare, vengono visualizzate le righe di riepilogo per gli stati. Quindi vengono visualizzate le righe di riepilogo per le città. Il secondo esempio in questa figura mostra alcune funzioni aggiuntive che sono disponibili quando si utilizza l'operatore GROUPING SETS. Per cominciare, all'interno delle parentesi dopo l'operatore GROUPING SETS, è possibile aggiungere altri gruppi di parentesi per creare gruppi composti da più colonne. Inoltre, è possibile aggiungere un insieme vuoto di parentesi per aggiungere un gruppo per una riga di riepilogo che riassume l'intero risultato. In questo esempio, il primo gruppo è costituito dallo Stato e città del fornitore, il secondo gruppo è il codice postale del fornitore e il terzo gruppo è un insieme vuoto di parentesi, che aggiunge una riga di riepilogo per l'intero set di risultati. Quando si utilizzano i gruppi compositi, si deve sapere che è possibile aggiungere altre righe di riepilogo aggiuntive applicando gli operatori ROLLUP e CUBE a un gruppo composito. A tale scopo, si codifica l'operatore ROLLUP o CUBE prima del gruppo composito. In questa figura, ad esempio, il terzo esempio mostra una clausola GROUPING SETS che applica l'operatore ROLLUP al gruppo composito stato/città. Questo aggiunge una riga di riepilogo per ogni stato e una riga di riepilogo per l'intero set di risultati. Anche se all'inizio può sembrare confuso, con un po' di sperimentazione si può ottenere l'insieme dei risultati desiderato.
USE AP
GO
SELECT VendorState, VendorCity, COUNT(*) AS QtyVendors
FROM Vendors
WHERE VendorState IN ('IA', 'NJ')
GROUP BY GROUPING SETS(VendorState, VendorCity)
ORDER BY VendorState DESC, VendorCity DESC
USE AP
GO
SELECT VendorState, VendorCity, VendorZipCode,
COUNT(*) AS QtyVendors
FROM Vendors
WHERE VendorState IN ('IA', 'NJ')
GROUP BY GROUPING SETS((VendorState, VendorCity), VendorZipCode, ())
ORDER BY VendorState DESC, VendorCity DESC
GROUP BY GROUPING SETS(ROLLUP(VendorState, VendorCity), VendorZipCode)
Finora, in questa lezione, si è appreso come codificare le query di riepilogo che restituiscono solo i dati riassunti. Ma cosa succede se si vogliono restituire le singole righe che sono state utilizzate per calcolare i riepiloghi insieme ai dati del riepilogo? Per farlo, è possibile utilizzare la clausola OVER, come mostrato nell'esempio che segue. Nella sintassi in cima a questa figura, si può notare che la clausola OVER viene codificata dopo la funzione aggregata, seguita da una serie di parentesi. All'interno delle parentesi, è possibile codificare una clausola PARTITION BY, una clausola ORDER BY o entrambe. Questo è illustrato dai tre esempi di questa figura. Il primo esempio calcola il totale, il conteggio e la media delle fatture della tabella Fatture. In questo caso, si è utilizzata la clausola PARTITION BY per indicare che le fatture devono essere raggruppate per data della fattura. Se si osservano i risultati di questa query, si noterà che la tabella Fatture contiene una sola fattura per le prime tre date. Per questo motivo, le colonne Totale, Conteggio e Media per queste date sono calcolate sulla base di un'unica fattura. Al contrario, le tre fatture successive si riferiscono alla stessa data. In questo caso, le colonne totale, conteggio e media sono calcolate in base a tutte e tre le fatture. Il secondo esempio è simile, ma utilizza la clausola ORDER BY invece della clausola PARTITION BY. Per questo motivo, i calcoli non sono raggruppati in base alla data della fattura come nel primo esempio. Invece, i riepiloghi si accumulano da una data all'altra. Ad esempio, le colonne totale e media per la prima fattura sono uguali al totale della fattura, perché sono calcolati solo su quel totale. Le colonne totale e media per la seconda fattura, invece, sono calcolate in base sia alla prima che alla seconda fattura. Le colonne totale e la media della terza fattura sono calcolate in base alla prima, alla seconda e alla terza fattura. E così via. Inoltre, la colonna del conteggio indica la sequenza delle date delle fatture all'interno del set di risultati. Un totale che si accumula in questo modo si chiama totale cumulativo e una media calcolata sulla base di un totale cumulativo si chiama media mobile. Se il set di risultati contiene più di una fattura per la stessa data, i valori di riepilogo di tutte le fatture vengono accumulati contemporaneamente. Questo è illustrato dalla quarta, quinta e sesta fattura di questo esempio. In questo caso, la colonna totale cumulativo per ogni fattura include i totali delle fatture per tutte e tre le fatture della stessa data. Poiché il totale cumulativo ora include sei righe, la colonna del conteggio è impostata a 6. In altre parole, i valori di riepilogo per tutte e tre le righe sono gli stessi. L'ultimo esempio di questa figura utilizza entrambe le clausole PARTITION BY e ORDER BY. In questo caso, le fatture sono raggruppate per TermsID e ordinate per data fattura. Per questo motivo, i riepiloghi per ogni data di fattura sono accumulati separatamente all'interno di ciascun TermsID. Quando un TermsID termina e inizia il successivo, l'accumulo ricomincia.
aggregate_function OVER ([partition_by_clause] [order_by_clause])
USE AP
GO
SELECT InvoiceNumber, InvoiceDate, InvoiceTotal,
SUM(InvoiceTotal) OVER (PARTITION BY InvoiceDate) AS DateTotal,
COUNT(InvoiceTotal) OVER (PARTITION BY InvoiceDate) AS DateCount,
AVG(InvoiceTotal) OVER (PARTITION BY InvoiceDate) AS DateAvg
FROM Invoices
USE AP
GO
SELECT InvoiceNumber, InvoiceDate, InvoiceTotal,
SUM(InvoiceTotal) OVER (ORDER BY InvoiceDate) AS CumTotal,
COUNT(InvoiceTotal) OVER (ORDER BY InvoiceDate) AS Count,
AVG(InvoiceTotal) OVER (ORDER BY InvoiceDate) AS MovingAvg
FROM Invoices
USE AP
GO
SELECT InvoiceNumber, TermsID, InvoiceDate, InvoiceTotal,
SUM(InvoiceTotal)
OVER (PARTITION BY TermsID ORDER BY InvoiceDate) AS CumTotal,
COUNT(InvoiceTotal)
OVER (PARTITION BY TermsID ORDER BY InvoiceDate) AS Count,
AVG(InvoiceTotal)
OVER (PARTITION BY TermsID ORDER BY InvoiceDate) AS MovingAvg
FROM Invoices
In questa lezione si è appreso come codificare le query che raggruppano e riassumono i dati. Nella maggior parte dei casi, si potranno usare le tecniche presentate qui per ottenere le informazioni di riepilogo necessarie. In caso contrario, si consiglia di informarsi su un altro strumento fornito da SQL Server 2016, chiamato Analysis Services. Questo strumento offre un'interfaccia grafica che consente di creare modelli di dati complessi basati su cubi. Quindi, è possibile utilizzare tali modelli per analizzare il database utilizzando modelli e correlazioni complesse. Per saperne di più su questo strumento, cercate “Analysis Services” nella documentazione di SQL Server.
USE AP
GO
SELECT VendorID, SUM(PaymentTotal) AS PaymentSum
FROM Invoices
GROUP BY VendorID
USE AP
GO
SELECT TOP 10 VendorName, SUM(PaymentTotal) AS PaymentSum
FROM Vendors v
JOIN Invoices i
ON v.VendorID = i.VendorID
GROUP BY VendorName
ORDER BY PaymentSum DESC
USE AP
GO
SELECT VendorName, COUNT(*) AS InvoiceCount,
SUM(InvoiceTotal) AS InvoiceSum
FROM Vendors v
JOIN Invoices i
ON v.VendorID = i.VendorID
GROUP BY VendorName
ORDER BY InvoiceCount DESC
USE AP
GO
SELECT gl.AccountDescription, COUNT(*) AS LineItemCount,
SUM(InvoiceLineItemAmount) AS LineItemSum
FROM GLAccounts gl
JOIN InvoiceLineItems li
ON gl.AccountNo = li.AccountNo
GROUP BY gl.AccountDescription
HAVING COUNT(*) > 1
ORDER BY LineItemCount DESC
USE AP
GO
SELECT gl.AccountDescription, COUNT(*) AS LineItemCount,
SUM(InvoiceLineItemAmount) AS LineItemSum
FROM GLAccounts gl
JOIN InvoiceLineItems li
ON gl.AccountNo = li.AccountNo
JOIN Invoices i
ON li.InvoiceID = i.InvoiceID
WHERE InvoiceDate BETWEEN '2022-10-01' AND '2022-12-31'
GROUP BY gl.AccountDescription
HAVING COUNT(*) > 1
ORDER BY LineItemCount DESC
USE AP
GO
SELECT AccountNo, SUM(InvoiceLineItemAmount) AS LineItemSum
FROM InvoiceLineItems
GROUP BY AccountNo WITH ROLLUP
USE AP
GO
SELECT VendorName, AccountDescription, COUNT(*) AS LineItemCount,
SUM(InvoiceLineItemAmount) AS LineItemSum
FROM Vendors v
JOIN Invoices i
ON v.VendorID = i.VendorID
JOIN InvoiceLineItems li
ON i.InvoiceID = li.InvoiceID
JOIN GLAccounts gl
ON li.AccountNo = gl.AccountNo
GROUP BY VendorName, AccountDescription
ORDER BY VendorName, AccountDescription
USE AP
GO
SELECT VendorName,
COUNT(DISTINCT li.AccountNo) AS [# of Accounts]
FROM Vendors v
JOIN Invoices i
ON v.VendorID = i.VendorID
JOIN InvoiceLineItems li
ON i.InvoiceID = li.InvoiceID
GROUP BY VendorName
HAVING COUNT(DISTINCT li.AccountNo) > 1
ORDER BY VendorName
USE AP
GO
SELECT VendorID, InvoiceDate, InvoiceTotal,
SUM(InvoiceTotal) OVER (PARTITION BY VendorID) AS VendorTotal,
COUNT(InvoiceTotal) OVER (PARTITION BY VendorID) AS VendorCount,
AVG(InvoiceTotal) OVER (PARTITION BY VendorID) AS VendorAvg
FROM Invoices