Iniziamo il nostro viaggio SQL per comprendere l'aggregazione dei dati in SQL e i tipi di aggregazione, comprese le aggregazioni semplici e scorrevoli.
Prima di passare alle aggregazioni, vale la pena considerare fatti interessanti spesso persi da alcuni sviluppatori quando si tratta di SQL in generale e dell'aggregazione in particolare.
In questo articolo, SQL si riferisce a T-SQL che è la versione Microsoft di SQL e ha più funzionalità rispetto all'SQL standard.
La matematica dietro SQL
È molto importante capire che T-SQL si basa su alcuni solidi concetti matematici sebbene non sia un linguaggio rigido basato sulla matematica.
Secondo il libro "Microsoft_SQL_Server_2008_T_SQL_Fundamentals" di Itzik Ben-Gan, SQL è progettato per interrogare e gestire i dati in un sistema di gestione di database relazionali (RDBMS).
Lo stesso sistema di gestione del database relazionale si basa su due solidi rami matematici:
- Teoria degli insiemi
- Logica predicata
Teoria degli insiemi
La teoria degli insiemi, come indica il nome, è una branca della matematica sugli insiemi che possono anche essere chiamati raccolte di oggetti distinti definiti.
In breve, nella teoria degli insiemi, pensiamo alle cose o agli oggetti nel loro insieme nello stesso modo in cui pensiamo a un singolo elemento.
Ad esempio, un libro è un insieme di tutti i libri ben distinti, quindi prendiamo un libro nel suo insieme che è sufficiente per ottenere i dettagli di tutti i libri in esso contenuti.
Logica predicato
La logica predicata è una logica booleana che restituisce true o false a seconda della condizione o dei valori delle variabili.
La logica del predicato può essere utilizzata per applicare regole di integrità (il prezzo deve essere maggiore di 0.00) o filtrare i dati (dove il prezzo è maggiore di 10.00), tuttavia, nel contesto di T-SQL, abbiamo tre valori logici come segue:
- Vero
- Falso
- Sconosciuto (Null)
Questo può essere illustrato come segue:
Un esempio di predicato è "Dove il prezzo del libro è maggiore di 10,00".
Questo è abbastanza per la matematica, ma tieni presente che ne parlerò più avanti nell'articolo.
Perché è facile aggregare i dati in SQL
L'aggregazione dei dati in SQL nella sua forma più semplice consiste nel conoscere i totali in una volta sola.
Ad esempio, se abbiamo una tabella clienti che contiene un elenco di tutti i clienti insieme ai loro dettagli, i dati aggregati della tabella clienti possono darci il numero totale di clienti che abbiamo.
Come discusso in precedenza, pensiamo a un set come a un singolo elemento, quindi applichiamo semplicemente una funzione di aggregazione alla tabella per ottenere i totali.
Poiché SQL è originariamente un linguaggio basato su insiemi (come discusso in precedenza), quindi è relativamente più facile applicarvi funzioni aggregate rispetto ad altri linguaggi.
Ad esempio, se disponiamo di una tabella prodotti che contiene record di tutti i prodotti nel database, possiamo applicare immediatamente la funzione di conteggio a una tabella prodotti per ottenere il numero totale di prodotti anziché contarli uno per uno in un ciclo.
Ricetta di aggregazione dei dati
Per aggregare i dati in SQL, sono necessarie almeno le seguenti cose:
- Dati (tabella) con colonne che, se aggregate, hanno senso
- Una funzione aggregata da applicare sui dati
Preparazione dei dati di esempio (tabella)
Prendiamo un esempio di una semplice tabella degli ordini che contiene tre cose (colonne):
- Numero d'ordine (OrderId)
- Data in cui è stato effettuato l'ordine (OrderDate)
- Importo dell'ordine (TotalAmount)
Creiamo il database AggregateSample per procedere ulteriormente:
-- Create aggregate sample database CREATE DATABASE AggregateSample
Ora crea la tabella degli ordini nel database di esempio come segue:
-- Create order table in the aggregate sample database USE AggregateSample CREATE TABLE SimpleOrder (OrderId INT PRIMARY KEY IDENTITY(1,1), OrderDate DATETIME2, TotalAmount DECIMAL(10,2) )
Popolazione dei dati di esempio
Popolare la tabella aggiungendo una riga:
INSERT INTO dbo.SimpleOrder ( OrderDate ,TotalAmount ) VALUES ( '20180101' -- OrderDate - datetime2 ,20.50 -- TotalAmount - decimal(10, 2) ); GO
Guardiamo ora la tabella:
-- View order table SELECT OrderId ,OrderDate ,TotalAmount FROM SimpleOrder
Si noti che in questo articolo sto usando dbForge Studio per SQL Server, quindi solo l'aspetto dell'output potrebbe differire se si esegue lo stesso codice in SSMS (SQL Server Management Studio), non vi è alcuna differenza per quanto riguarda gli script e i relativi risultati.
Funzioni di aggregazione di base
Le funzioni di aggregazione di base che possono essere applicate alla tabella sono le seguenti:
- Somma
- Conte
- Minimo
- Massimo
- Media
Tabella di aggregazione di record singoli
Ora la domanda interessante è:"possiamo aggregare (sommare o contare) i dati (record) in una tabella se ha solo una riga come nel nostro caso?" La risposta è "Sì", possiamo, anche se non ha molto senso, ma può aiutarci a capire come i dati si preparano per l'aggregazione.
Per ottenere il numero totale di ordini, utilizziamo la funzione count() con la tabella, come discusso in precedenza, possiamo semplicemente applicare la funzione di aggregazione alla tabella poiché SQL è un linguaggio basato su insiemi e le operazioni possono essere applicate a un insieme direttamente.
-- Getting total number of orders placed so far SELECT COUNT(*) AS Total_Orders FROM SimpleOrder
Ora, che dire dell'ordine con un importo minimo, massimo e medio per un singolo record:
-- Getting order with minimum amount, maximum amount, average amount and total orders SELECT COUNT(*) AS Total_Orders ,MIN(TotalAmount) AS Min_Amount ,MAX(TotalAmount) AS Max_Amount ,AVG(TotalAmount) Average_Amount FROM SimpleOrder
Come possiamo vedere dall'output, l'importo minimo, massimo e medio è lo stesso se abbiamo un singolo record, quindi è possibile applicare una funzione di aggregazione a un singolo record ma ci dà gli stessi risultati.
Abbiamo bisogno di almeno più di un record per dare un senso ai dati aggregati.
Tabella di aggregazione di più record
Aggiungiamo ora altri quattro record come segue:
INSERT INTO dbo.SimpleOrder ( OrderDate ,TotalAmount ) VALUES ( '20180101' -- OrderDate - datetime2 ,20.50 -- TotalAmount - decimal(10, 2) ), ( '20180102' -- OrderDate - datetime2 ,30.50 -- TotalAmount - decimal(10, 2) ), ( '20180103' -- OrderDate - datetime2 ,10.50 -- TotalAmount - decimal(10, 2) ), ( '20180110' -- OrderDate - datetime2 ,100.50 -- TotalAmount - decimal(10, 2) ); GO
La tabella ora appare come segue:
Se applichiamo ora le funzioni aggregate alla tabella, otterremo buoni risultati:
-- Getting order with minimum amount, maximum amount, average amount and total orders SELECT COUNT(*) AS Total_Orders ,MIN(TotalAmount) AS Min_Amount ,MAX(TotalAmount) AS Max_Amount ,AVG(TotalAmount) Average_Amount FROM SimpleOrder
Raggruppamento di dati aggregati
Possiamo raggruppare i dati aggregati per qualsiasi colonna o set di colonne per ottenere aggregati basati su quella colonna.
Ad esempio, se vogliamo conoscere il numero totale di ordini per data, dobbiamo raggruppare la tabella per data usando Raggruppa per clausola come segue:
-- Getting total orders per date SELECT OrderDate ,COUNT(*) AS Total_Orders FROM SimpleOrder GROUP BY OrderDate
L'output è il seguente:
Quindi, se vogliamo vedere la somma di tutto l'importo dell'ordine, possiamo semplicemente applicare la funzione di somma alla colonna dell'importo totale senza alcun raggruppamento come segue:
-- Sum of all the orders amount SELECT SUM(TotalAmount) AS Sum_of_Orders_Amount FROM SimpleOrder
Per ottenere la somma dell'importo degli ordini per data, aggiungiamo semplicemente il gruppo per data all'istruzione SQL sopra come segue:
-- Sum of all the orders amount per date SELECT OrderDate ,SUM(TotalAmount) AS Sum_of_Orders FROM SimpleOrder GROUP BY OrderDate
Come ottenere i totali senza raggruppare i dati
Possiamo ottenere subito totali come ordini totali, importo massimo dell'ordine, importo minimo dell'ordine, somma dell'importo degli ordini, importo medio dell'ordine senza la necessità di raggrupparlo se l'aggregazione è intesa per tutte le tabelle.
-- Getting order with minimum amount, maximum amount, average amount, sum of amount and total orders SELECT COUNT(*) AS Total_Orders ,MIN(TotalAmount) AS Min_Amount ,MAX(TotalAmount) AS Max_Amount ,AVG(TotalAmount) AS Average_Amount ,SUM(TotalAmount) AS Sum_of_Amount FROM SimpleOrder
Aggiunta di clienti agli ordini
Aggiungiamo un po' di divertimento aggiungendo i clienti nella nostra tabella. Possiamo farlo creando un'altra tabella di clienti e passando l'id cliente alla tabella degli ordini, tuttavia per mantenerlo semplice e per prendere in giro lo stile del data warehouse (dove le tabelle sono denormalizzate), sto aggiungendo la colonna del nome del cliente nella tabella degli ordini come segue :
-- Adding CustomerName column and data to the order table ALTER TABLE SimpleOrder ADD CustomerName VARCHAR(40) NULL GO UPDATE SimpleOrder SET CustomerName = 'Eric' WHERE OrderId = 1 GO UPDATE SimpleOrder SET CustomerName = 'Sadaf' WHERE OrderId = 2 GO UPDATE SimpleOrder SET CustomerName = 'Peter' WHERE OrderId = 3 GO UPDATE SimpleOrder SET CustomerName = 'Asif' WHERE OrderId = 4 GO UPDATE SimpleOrder SET CustomerName = 'Peter' WHERE OrderId = 5 GO
Ricevere ordini totali per cliente
Riesci a indovinare ora come ottenere ordini totali per cliente? È necessario raggruppare per cliente (CustomerName) e applicare la funzione di aggregazione count() a tutti i record come segue:
-- Total orders per customer SELECT CustomerName,COUNT(*) AS Total_Orders FROM SimpleOrder GROUP BY CustomerName
Aggiunta di altri cinque record alla tabella degli ordini
Ora aggiungeremo altre cinque righe alla tabella degli ordini semplici come segue:
-- Adding 5 more records to order table INSERT INTO SimpleOrder (OrderDate, TotalAmount, CustomerName) VALUES ('01-Jan-2018', 70.50, 'Sam'), ('02-Jan-2018', 170.50, 'Adil'), ('03-Jan-2018',50.00,'Sarah'), ('04-Jan-2018',50.00,'Asif'), ('11-Jan-2018',50.00,'Peter') GO
Dai un'occhiata ai dati ora:
-- Viewing order table after adding customer name and five more rows SELECT OrderId,CustomerName,OrderDate,TotalAmount FROM SimpleOrder GO
Ottenere il totale degli ordini per cliente ordinati dal massimo al minimo
Se sei interessato al totale degli ordini per cliente ordinati per ordine massimo o minimo, non è affatto una cattiva idea suddividerlo in passaggi più piccoli come segue:
-- (1) Getting total orders SELECT COUNT(*) AS Total_Orders FROM SimpleOrder
-- (2) Getting total orders per customer SELECT CustomerName,COUNT(*) AS Total_Orders FROM SimpleOrder GROUP BY CustomerName
Per ordinare il conteggio degli ordini dal massimo al minimo, dobbiamo utilizzare la clausola Order By DESC (ordine discendente) con count() alla fine come segue:
-- (3) Getting total orders per customer from maximum to minimum orders SELECT CustomerName,COUNT(*) AS Total_Orders FROM SimpleOrder GROUP BY CustomerName ORDER BY COUNT(*) DESC
Ottenere il totale degli ordini per data ordinati prima in base all'ordine più recente
Utilizzando il metodo sopra, ora possiamo scoprire gli ordini totali per data ordinati prima per ordine più recente come segue:
-- Getting total orders per date from most recent first SELECT CAST(OrderDate AS DATE) AS OrderDate,COUNT(*) AS Total_Orders FROM SimpleOrder GROUP BY OrderDate ORDER BY OrderDate DESC
La funzione CAST ci aiuta a ottenere solo la parte della data. L'output è il seguente:
Puoi utilizzare quante più combinazioni possibili purché abbiano senso.
Esecuzione di aggregazioni
Ora che abbiamo familiarità con l'applicazione delle funzioni di aggregazione ai nostri dati, passiamo alla forma avanzata di aggregazione e una di queste aggregazioni è l'aggregazione in esecuzione.
Le aggregazioni in esecuzione sono le aggregazioni applicate a un sottoinsieme di dati anziché all'intero insieme di dati, il che ci aiuta a creare piccole finestre sui dati.
Finora abbiamo visto che tutte le funzioni di aggregazione vengono applicate a tutte le righe della tabella che possono essere raggruppate per alcune colonne come la data dell'ordine o il nome del cliente, ma con le aggregazioni in esecuzione abbiamo la libertà di applicare le funzioni di aggregazione senza raggruppare l'intero set di dati.
Ovviamente, questo significa che possiamo applicare la funzione di aggregazione senza usare la clausola Group By, il che è alquanto strano per quei principianti di SQL (o talvolta alcuni sviluppatori trascurano questo) che non hanno familiarità con le funzioni di windowing e con l'esecuzione di aggregazioni.
Finestre sui dati
Come detto in precedenza, l'aggregazione in esecuzione viene applicata a un sottoinsieme di set di dati o (in altre parole) a piccole finestre di dati.
Pensa a Windows come a un set all'interno di un set o a una tabella all'interno di una tabella. Un buon esempio di windowing sui dati nel nostro caso è che abbiamo la tabella degli ordini che contiene gli ordini effettuati in date diverse, quindi cosa succede se ogni data è una finestra separata, quindi possiamo applicare funzioni aggregate su ciascuna finestra nello stesso modo in cui abbiamo applicato a il tavolo.
Se ordiniamo la tabella degli ordini (SimpleOrder) per data dell'ordine (OrderDate) come segue:
-- View order table sorted by order date SELECT so.OrderId ,so.OrderDate ,so.TotalAmount ,so.CustomerName FROM SimpleOrder so ORDER BY so.OrderDate
Di seguito è possibile visualizzare Windows sui dati pronti per l'esecuzione di aggregazioni:
Possiamo anche considerare queste finestre o sottoinsiemi come sei mini tabelle basate sulla data degli ordini e gli aggregati possono essere applicati a ciascuna di queste mini tabelle.
Uso della partizione all'interno della clausola OVER()
Le aggregazioni in esecuzione possono essere applicate partizionando la tabella utilizzando "Partition by" all'interno della clausola OVER().
Ad esempio, se vogliamo partizionare la tabella degli ordini per date, ad esempio ogni data è una sottotabella o una finestra sul set di dati, dobbiamo partizionare i dati per data dell'ordine e ciò può essere ottenuto utilizzando una funzione aggregata come COUNT( ) con OVER() e Partition by inside OVER() come segue:
-- Running Aggregation on Order table by partitioning by dates SELECT OrderDate, Total_Orders=COUNT(*) OVER(PARTITION BY OrderDate) FROM SimpleOrder
Ottenere i totali parziali per finestra di data (partizione)
L'esecuzione delle aggregazioni ci aiuta a limitare l'ambito di aggregazione solo alla finestra definita e possiamo ottenere i totali parziali per finestra come segue:
-- Getting total orders, minimum amount, maximum amount, average amount and sum of all amounts per date window (partition by date) SELECT CAST (OrderDate AS DATE) AS OrderDate, Count=COUNT(*) OVER (PARTITION BY OrderDate), Min_Amount=MIN(TotalAmount) OVER (PARTITION BY OrderDate) , Max_Amount=MAX(TotalAmount) OVER (PARTITION BY OrderDate) , Average_Amount=AVG(TotalAmount) OVER (PARTITION BY OrderDate), Sum_Amount=SUM(TotalAmount) OVER (PARTITION BY OrderDate) FROM SimpleOrder
Ottenere i totali parziali per finestra cliente (partizione)
Proprio come i totali parziali per finestra di data, possiamo anche calcolare i totali parziali per finestra del cliente suddividendo l'insieme di ordini (tabella) in sottoinsiemi di clienti piccoli (partizioni) come segue:
-- Getting total orders, minimum amount, maximum amount, average amount and sum of all amounts per customer window (partition by customer) SELECT CustomerName, CAST (OrderDate AS DATE) AS OrderDate, Count=COUNT(*) OVER (PARTITION BY CustomerName), Min_Amount=MIN(TotalAmount) OVER (PARTITION BY CustomerName) , Max_Amount=MAX(TotalAmount) OVER (PARTITION BY CustomerName) , Average_Amount=AVG(TotalAmount) OVER (PARTITION BY CustomerName), Sum_Amount=SUM(TotalAmount) OVER (PARTITION BY CustomerName) FROM SimpleOrder ORDER BY Count DESC,OrderDate
Aggregazioni scorrevoli
Le aggregazioni scorrevoli sono le aggregazioni che possono essere applicate ai frame all'interno di una finestra, il che significa restringere ulteriormente l'ambito all'interno della finestra (partizione).
In altre parole, i totali parziali ci danno i totali (somma, media, minimo, massimo, conteggio) per l'intera finestra (sottoinsieme) che creiamo all'interno di una tabella, mentre i totali scorrevoli ci danno i totali (somma, media, minimo, massimo, conteggio) per il frame (sottoinsieme del sottoinsieme) all'interno della finestra (sottoinsieme) della tabella.
Ad esempio, se creiamo una finestra sui dati in base al cliente (partizione per cliente), possiamo vedere che il cliente "Pietro" ha tre record nella sua finestra e tutte le aggregazioni vengono applicate a questi tre record. Ora, se vogliamo creare un frame solo per due righe alla volta, significa che l'aggregazione viene ulteriormente ristretta e viene quindi applicata alla prima e alla seconda riga, quindi alla seconda e alla terza riga e così via.
Utilizzo di RIGA PRECEDENTI con Order By all'interno della clausola OVER()
Le aggregazioni scorrevoli possono essere applicate aggiungendo ROWS
Ad esempio, se desideriamo aggregare i dati solo per due righe alla volta per ciascun cliente, è necessario applicare le aggregazioni scorrevoli alla tabella degli ordini come segue:
-- Getting minimum amount, maximum amount, average amount per frame per customer window SELECT CustomerName, Min_Amount=Min(TotalAmount) OVER (PARTITION BY CustomerName ORDER BY OrderDate ROWS 1 PRECEDING), Max_Amount=Max(TotalAmount) OVER (PARTITION BY CustomerName ORDER BY OrderDate ROWS 1 PRECEDING) , Average_Amount=AVG(TotalAmount) OVER (PARTITION BY CustomerName ORDER BY OrderDate ROWS 1 PRECEDING) FROM SimpleOrder so ORDER BY CustomerName
Per capire come funziona, osserviamo la tabella originale nel contesto di cornici e finestre:
Nella prima riga della finestra del cliente Peter, ha effettuato un ordine con un importo di 30,50 poiché questo è l'inizio del riquadro all'interno della finestra del cliente, quindi min e max sono gli stessi in quanto non esiste una riga precedente con cui confrontare.
Successivamente, l'importo minimo rimane lo stesso ma il massimo diventa 100,50 poiché l'importo della riga precedente (prima riga) è 30,50 e l'importo della riga è 100,50, quindi il massimo dei due è 100,50.
Successivamente, passando alla terza riga, il confronto avverrà con la seconda riga quindi l'importo minimo delle due è 50,00 e l'importo massimo delle due righe è 100,50.
Funzione MDX dall'inizio dell'anno (YTD) e aggregazioni in esecuzione
MDX è un linguaggio di espressione multidimensionale utilizzato per interrogare dati multidimensionali (come il cubo) e viene utilizzato nelle soluzioni di business intelligence (BI).
Secondo https://docs.microsoft.com/en-us/sql/mdx/ytd-mdx, la funzione Year to Date (YTD) in MDX funziona allo stesso modo dell'esecuzione o dello scorrimento delle aggregazioni. Ad esempio, YTD spesso utilizzato in combinazione con nessun parametro fornito mostra un totale parziale fino ad oggi.
Ciò significa che se applichiamo questa funzione all'anno fornisce tutti i dati dell'anno, ma se analizziamo fino a marzo ci darà tutti i totali dall'inizio dell'anno fino a marzo e così via.
Questo è molto utile nei rapporti SSRS.
Cose da fare
Questo è tutto! Sei pronto per eseguire alcune analisi di base dei dati dopo aver letto questo articolo e puoi migliorare ulteriormente le tue abilità con le seguenti cose:
- Prova a scrivere uno script di aggregazione in esecuzione creando finestre su altre colonne come Importo totale.
- Prova anche a scrivere uno script di aggregati scorrevoli creando frame su altre colonne come Importo totale.
- Puoi aggiungere più colonne e record alla tabella (o anche più tabelle) per provare altre combinazioni di aggregazione.
- Gli script di esempio citati in questo articolo possono essere trasformati in stored procedure da utilizzare nei report SSRS dietro i set di dati.
Riferimenti:
- Anno (MDX)
- dbForge Studio per SQL Server