Database
 sql >> Database >  >> RDS >> Database

L'arte di aggregare i dati in SQL dalle aggregazioni semplici a quelle scorrevoli

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:

  1. Vero
  2. Falso
  3. 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:

  1. Dati (tabella) con colonne che, se aggregate, hanno senso
  2. 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):

  1. Numero d'ordine (OrderId)
  2. Data in cui è stato effettuato l'ordine (OrderDate)
  3. 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:

  1. Somma
  2. Conte
  3. Minimo
  4. Massimo
  5. 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 PRECEDENTE con Order By (dopo Partition By) mentre ROWS PRECEEDING determina l'ambito di Frame all'interno della finestra.

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:

  1. Prova a scrivere uno script di aggregazione in esecuzione creando finestre su altre colonne come Importo totale.
  2. Prova anche a scrivere uno script di aggregati scorrevoli creando frame su altre colonne come Importo totale.
  3. Puoi aggiungere più colonne e record alla tabella (o anche più tabelle) per provare altre combinazioni di aggregazione.
  4. 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