sql >> Databasteknik >  >> RDS >> Database

Konsten att samla data i SQL från enkla till glidande aggregationer

Låt oss börja vår SQL-resa för att förstå aggregeringsdata i SQL och typer av aggregering inklusive enkla och glidande aggregationer.

Innan vi hoppar till aggregeringarna är det värt att överväga intressanta fakta som ofta missas av vissa utvecklare när det kommer till SQL i allmänhet och aggregeringen i synnerhet.

I den här artikeln hänvisar SQL till T-SQL som är Microsoft-versionen av SQL och har fler funktioner än standard-SQL.

Matematik bakom SQL

Det är mycket viktigt att förstå att T-SQL är baserat på några solida matematiska begrepp även om det inte är ett styvt matematikbaserat språk.

Enligt boken "Microsoft_SQL_Server_2008_T_SQL_Fundamentals" av Itzik Ben-Gan är SQL designad för att fråga och hantera data i ett relationsdatabashanteringssystem (RDBMS).

Själva relationsdatabashanteringssystemet är baserat på två solida matematiska grenar:

  • Mängdteori
  • Predikatlogik

Mängdteori

Mängdlära, som namnet indikerar, är en gren av matematiken om mängder som också kan kallas samlingar av bestämda distinkta objekt.

Kort sagt, i mängdteorin tänker vi på saker eller objekt som en helhet på samma sätt som vi tänker på ett enskilt objekt.

Till exempel är en bok en uppsättning av alla bestämda distinkta böcker, så vi tar en bok som helhet vilket är tillräckligt för att få detaljer om alla böcker i den.

Predikatlogik

Predikatlogik är en boolesk logik som returnerar sant eller falskt beroende på tillståndet eller värdena för variablerna.

Predikatlogik kan användas för att genomdriva integritetsregler (priset måste vara större än 0,00) eller filtrera data (där priset är mer än 10,00), men i T-SQL-sammanhang har vi tre logiska värden enligt följande:

  1. Sant
  2. False
  3. Okänd (Null)

Detta kan illustreras enligt följande:

Ett exempel på ett predikat är "Där bokens pris är högre än 10,00".

Det räcker med matematik, men kom ihåg att jag kommer att hänvisa till det senare i artikeln.

Varför det är enkelt att samla data i SQL

Att aggregera data i SQL i sin enklaste form handlar om att ta reda på summan på en gång.

Om vi ​​till exempel har en kundtabell som innehåller en lista över alla kunder tillsammans med deras uppgifter kan aggregerade data från kundtabellen ge oss det totala antalet kunder vi har.

Som diskuterats tidigare tänker vi på en uppsättning som en enskild artikel, så vi använder helt enkelt en aggregatfunktion på tabellen för att få summan.

Eftersom SQL ursprungligen är ett uppsättningsbaserat språk (som diskuterats tidigare), så är det relativt lättare att tillämpa aggregerade funktioner på det jämfört med andra språk.

Till exempel, om vi har en produkttabell som har register över alla produkter i databasen kan vi direkt tillämpa räknefunktionen på en produkttabell för att få det totala antalet produkter istället för att räkna dem en efter en i en slinga.

Dataaggregationsrecept

För att aggregera data i SQL behöver vi åtminstone följande saker:

  1. Data (tabell) med kolumner som när de är aggregerade är meningsfulla
  2. En aggregerad funktion som ska användas på data

Förbereda provdata (tabell)

Låt oss ta ett exempel på en enkel ordningstabell som innehåller tre saker (kolumner):

  1. Beställningsnummer (OrderId)
  2. Datum då beställningen gjordes (OrderDate)
  3. Beställningens belopp (TotalAmount)

Låt oss skapa AggregateSample-databasen för att gå vidare:

-- Create aggregate sample database 
CREATE DATABASE AggregateSample

Skapa nu ordertabellen i exempeldatabasen enligt följande:

-- 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)
  )

Pulera provdata

Fyll tabellen genom att lägga till en rad:

INSERT INTO dbo.SimpleOrder
(
  OrderDate
 ,TotalAmount
)
VALUES
(
  '20180101' -- OrderDate - datetime2
 ,20.50 -- TotalAmount - decimal(10, 2)
);
GO

Låt oss titta på tabellen nu:

-- View order table 
SELECT OrderId ,OrderDate ,TotalAmount FROM SimpleOrder

Observera att jag använder dbForge Studio för SQL Server i den här artikeln så endast utdatautseendet kan skilja sig om du kör samma kod i SSMS (SQL Server Management Studio), det är ingen skillnad vad gäller skript och deras resultat.

Grundläggande samlade funktioner

De grundläggande aggregerade funktionerna som kan tillämpas på tabellen är följande:

  1. Summa
  2. Räkna
  3. Min
  4. Max
  5. Genomsnitt

Aggregerande enstaka inspelningstabell

Nu är den intressanta frågan, "kan vi aggregera (summa eller räkna) data (poster) i en tabell om den bara har en rad som i vårt fall?" Svaret är "Ja", det kan vi, även om det inte är så meningsfullt men det kan hjälpa oss att förstå hur data blir redo för aggregering.

För att få det totala antalet order använder vi funktionen count() med tabellen, som diskuterats tidigare kan vi helt enkelt tillämpa aggregatfunktionen på tabellen eftersom SQL är ett setbaserat språk och operationer kan tillämpas på en uppsättning direkt.

-- Getting total number of orders placed so far
SELECT COUNT(*) AS Total_Orders FROM SimpleOrder

Hur är det nu med beställningen med ett lägsta, högsta och genomsnittliga belopp för en enda post:

-- 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

Som vi kan se från utdata är det minsta, högsta och genomsnittliga beloppet detsamma om vi har en enda post, så det är möjligt att tillämpa en aggregerad funktion på en enda post, men det ger oss samma resultat.

Vi behöver minst mer än en post för att förstå den aggregerade informationen.

Tabell för sammanställning av flera poster

Låt oss nu lägga till ytterligare fyra poster enligt följande:

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

Tabellen ser nu ut som följer:

Om vi ​​tillämpar de aggregerade funktionerna på tabellen nu kommer vi att få bra resultat:

-- 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

Gruppera aggregerad data

Vi kan gruppera den aggregerade informationen efter vilken kolumn eller uppsättning kolumner som helst för att få aggregat baserat på den kolumnen.

Till exempel, om vi vill veta det totala antalet beställningar per datum, vi måste gruppera tabellen efter datum med hjälp av Group by-satsen enligt följande:

-- Getting total orders per date
SELECT
  OrderDate
 ,COUNT(*) AS Total_Orders
FROM SimpleOrder
GROUP BY OrderDate

Utgången är som följer:

Så om vi vill se summan av hela beställningsbeloppet, vi kan helt enkelt tillämpa summafunktionen på kolumnen för totalt belopp utan någon gruppering enligt följande:

-- Sum of all the orders amount
SELECT
  SUM(TotalAmount) AS Sum_of_Orders_Amount
FROM SimpleOrder

För att få summan av orderbelopp per datum lägger vi helt enkelt till grupp efter datum till ovanstående SQL-sats enligt följande:

-- Sum of	all	the	orders amount per date
SELECT
  OrderDate
 ,SUM(TotalAmount) AS Sum_of_Orders
FROM SimpleOrder
GROUP BY OrderDate

Hämta totaler utan att gruppera data

Vi kan genast få summor som totala order, högsta orderbelopp, minsta orderbelopp, summa av orderbelopp, genomsnittligt orderbelopp utan att behöva gruppera det om aggregeringen är avsedd för alla tabeller.

-- 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

Lägga till kunder i beställningarna

Låt oss lägga till lite kul genom att lägga till kunder i vår tabell. Vi kan göra detta genom att skapa en annan tabell över kunder och skicka kund-id till ordertabellen, men för att hålla det enkelt och för att håna datalagerstilen (där tabeller är avnormaliserade), lägger jag till kolumnen kundnamn i ordertabellen enligt följande :

-- 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

Få totala beställningar per kund

Kan du nu gissa hur man får totala beställningar per kund? Du måste gruppera efter kund (CustomerName) och tillämpa den aggregerade funktionen count() på alla poster enligt följande:

-- Total orders per customer
  SELECT CustomerName,COUNT(*) AS Total_Orders FROM SimpleOrder 
    GROUP BY CustomerName

Lägga till ytterligare fem poster i beställningstabellen

Nu ska vi lägga till fem rader till i den enkla ordningstabellen enligt följande:

-- 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

Ta en titt på data nu:

-- Viewing order table after adding customer name and five more rows
SELECT OrderId,CustomerName,OrderDate,TotalAmount FROM SimpleOrder 
GO

Få totala beställningar per kund sorterade efter högsta till lägsta beställning

Om du är intresserad av den totala beställningen per kund sorterad efter max- till minimiorder, är det ingen dum idé att dela upp detta i mindre steg enligt följande:

-- (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

För att sortera order räknas från maximalt till minimum måste vi använda satsen Order By DESC (fallande ordning) med count() i slutet enligt följande:

-- (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

Få totala beställningar per datum sorterade efter senaste beställning först

Med metoden ovan kan vi nu ta reda på de totala beställningarna per datum sorterade efter senaste beställning först enligt följande:

-- 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

CAST-funktionen hjälper oss att bara få fram datumdelen. Utgången är som följer:

Du kan använda så många kombinationer som möjligt så länge de är vettiga.

Köra aggregationer

Nu när vi är bekanta med att tillämpa aggregerade funktioner på vår data, låt oss gå vidare till den avancerade formen av aggregering och en sådan aggregering är den löpande aggregeringen.

Körande aggregering är aggregering som tillämpas på en delmängd av data snarare än på hela datamängden, vilket hjälper oss att skapa små fönster på datan.

Hittills har vi sett att alla aggregerade funktioner tillämpas på alla rader i tabellen som kan grupperas efter någon kolumn som orderdatum eller kundnamn, men med löpande aggregering har vi friheten att tillämpa de aggregerade funktionerna utan att gruppera hela dataset.

Uppenbarligen betyder detta att vi kan tillämpa aggregatfunktionen utan att använda Group By-satsen, vilket är något konstigt för de SQL-nybörjare (eller ibland förbiser vissa utvecklare detta) som inte är bekanta med fönsterfunktionerna och köra aggregering.

Windows på data

Som nämnts tidigare tillämpas den löpande aggregeringen på en delmängd av dataset eller (med andra ord) på små datafönster.

Tänk på fönster som en uppsättning inom en uppsättning eller en tabell inom en tabell. Ett bra exempel på fönster på data i vårt fall är att vi har ordertabellen som innehåller beställningar som lagts på olika datum, så tänk om varje datum är ett separat fönster, då kan vi tillämpa aggregerade funktioner på varje fönster på samma sätt som vi tillämpade på bordet.

Om vi ​​sorterar ordertabellen (SimpleOrder) efter orderdatum (OrderDate) enligt följande:

-- View order table sorted by order date
SELECT so.OrderId
      ,so.OrderDate
      ,so.TotalAmount
      ,so.CustomerName FROM SimpleOrder so
  ORDER BY so.OrderDate

Windows på data redo för körning av aggregering kan ses nedan:

Vi kan också betrakta dessa fönster eller delmängder som sex miniorderdatumbaserade tabeller och aggregat kan tillämpas på var och en av dessa minitabeller.

Användning av Partition By inuti OVER()-klausulen

Körande aggregationer kan tillämpas genom att partitionera tabellen med hjälp av "Partition by" inuti OVER()-satsen.

Till exempel, om vi vill partitionera beställningstabellen efter datum, som att varje datum är en undertabell eller ett fönster på datamängden, måste vi partitionera data efter beställningsdatum och detta kan uppnås genom att använda en aggregerad funktion som COUNT( ) med OVER() och partitionera med inuti OVER() enligt följande:

-- Running Aggregation on Order table by partitioning by dates
SELECT OrderDate, Total_Orders=COUNT(*) OVER(PARTITION BY OrderDate)  FROM SimpleOrder

Fönstret Få löpande summor per datum (partition)

Körande aggregering hjälper oss att begränsa aggregeringsomfånget till endast det definierade fönstret och vi kan få löpande summor per fönster enligt följande:

-- 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

Få löpande summor per kundfönster (partition)

Precis som löpande totalsummor per datumfönster kan vi också beräkna löpande totalsummor per kundfönster genom att dela upp orderuppsättningen (tabellen) i små kundunderuppsättningar (partitioner) enligt följande:

-- 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

Glidande aggregationer

Glidande aggregering är de sammanställningar som kan appliceras på ramarna i ett fönster, vilket innebär att omfattningen begränsas ytterligare inom fönstret (partitionen).

Med andra ord, löpande summor ger oss totaler (summa, medelvärde, min, max, antal) för hela fönstret (delmängd) vi skapar i en tabell, medan glidande summor ger oss totaler (summa, medelvärde, min, max, antal) för ramen (undermängd av undermängd) inom fönstret (undermängd) i tabellen.

Till exempel, om vi skapar ett fönster på data baserat på (uppdelning efter kund) kund kan vi se att kunden "Peter" har tre poster i sitt fönster och alla aggregationer tillämpas på dessa tre poster. Om vi ​​nu vill skapa en ram för endast två rader åt gången betyder det att aggregeringen minskas ytterligare och att den sedan tillämpas på den första och andra raden och sedan den andra och tredje raden och så vidare.

Användning av ROWS FÖREGÅENDE med Order By inuti OVER()-satsen

Glidande aggregering kan tillämpas genom att lägga till ROWS PRECEEDING med Order By (efter Partition By) medan ROWS PRECEEDING bestämmer omfattningen av Frame i fönstret.

Till exempel, om vi vill aggregera data för endast två rader åt gången för varje kund behöver vi glidande aggregering som ska tillämpas på ordertabellen enligt följande:

-- 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

För att förstå hur det fungerar, låt oss titta på den ursprungliga tabellen i samband med ramar och fönster:

I den första raden av kunden Peters fönster lade han en beställning med beloppet 30,50 eftersom detta är början på ramen inom kundfönstret, så min och max är desamma eftersom det inte finns någon tidigare rad att jämföra med.

Därefter förblir minimibeloppet detsamma men maxbeloppet blir 100,50 eftersom föregående rad (första raden) beloppet är 30,50 och detta radbelopp är 100,50, så det maximala av de två är 100,50.

När du sedan flyttar till den tredje raden kommer jämförelsen att ske med den andra raden så att det minsta beloppet av de två är 50,00 och det maximala beloppet för de två raderna är 100,50.

MDX Year to Date (YTD) funktion och löpande aggregationer

MDX är ett flerdimensionellt uttrycksspråk som används för att söka efter flerdimensionell data (som kub) och används i Business Intelligence (BI)-lösningar.

Enligt https://docs.microsoft.com/en-us/sql/mdx/ytd-mdx fungerar funktionen Year to Date (YTD) i MDX på samma sätt som körande eller glidande aggregering fungerar. Till exempel, YTD som ofta används i kombination utan att någon parameter tillhandahålls visar en löpande summa hittills.

Det betyder att om vi använder den här funktionen på år så ger den all årsdata, men om vi borrar ner till mars kommer det att ge oss alla summor från början av året till mars och så vidare.

Detta är mycket användbart i SSRS-rapporter.

Saker att göra

Det är allt! Du är redo att göra lite grundläggande dataanalys efter att ha gått igenom den här artikeln och du kan förbättra dina färdigheter ytterligare genom följande saker:

  1. Försök att skriva ett körande aggregeringsskript genom att skapa fönster på andra kolumner, t.ex. Totalt belopp.
  2. Försök också att skriva ett glidande sammanslagningsskript genom att skapa ramar i andra kolumner, som totalt belopp.
  3. Du kan lägga till fler kolumner och poster i tabellen (eller till och med fler tabeller) för att prova andra aggregeringskombinationer.
  4. Exempelskripten som nämns i den här artikeln kan omvandlas till lagrade procedurer för att användas i SSRS-rapporter bakom datauppsättning(ar).

Referenser:

  • Ytd (MDX)
  • dbForge Studio för SQL Server

  1. Hur man ökar filstorleken för en datafil i SQL Server (T-SQL)

  2. MariaDB LOCALTIMESTAMP() Förklarad

  3. Lär dig hur du använder SQL Server Management Studio

  4. Uppdatera data via en tabellvärderad funktion i SQL Server