Bruk vindusfunksjoner for analyse
Analytiske spørringer krever ofte beregninger som dekker flere rader samtidig som de returnerer individuelle raddetaljer. Tradisjonelle aggregerte funksjoner kollapser rader til grupper, og mister radnivåinformasjon. Vindusfunksjoner løser denne utfordringen ved å utføre beregninger over et sett med rader relatert til den nåværende raden, uten å kollapse resultatsettet.
Forstå vindusfunksjonssyntaksen
Vindusfunksjoner beregner verdier over et "vindu" av rader definert av klausulen OVER . I motsetning til vanlige aggregerte funksjoner, grupperer ikke vindusfunksjoner rader til én enkelt utgangsrad. I stedet beregner de verdier på tvers av relaterte rader samtidig som de bevarer alle opprinnelige rader i resultatet.
Den generelle syntaksen for en vindusfunksjon er:
function_name(arguments) OVER (
[PARTITION BY partition_expression]
[ORDER BY order_expression [ASC | DESC]]
[ROWS | RANGE frame_specification]
)
Klausulkomponentene OVER styrer hvordan vinduet defineres:
- PARTITION BY: Deler rader inn i grupper (partisjoner) for beregningen
- ORDER BY: Bestemmer den logiske rekkefølgen på rader innenfor hver partisjon
- RADER/OMRÅDE: Definerer rammegrensene i forhold til den nåværende raden
Følgende spørring demonstrerer en enkel vindusfunksjon som beregner et løpende antall ordrebeløp per kunde:
SELECT
CustomerID,
SalesOrderID,
OrderDate,
TotalDue,
SUM(TotalDue) OVER (PARTITION BY CustomerID ORDER BY OrderDate) AS RunningTotal
FROM SalesLT.SalesOrderHeader
ORDER BY CustomerID, OrderDate;
Bemerkning
Når du spesifiserer ORDER BY i klausulen OVER uten rammespesifikasjon, er RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW standardrammen for aggregerte funksjoner. Dette skaper kumulative beregninger.
Bruk rangeringsfunksjoner
Rangeringsfunksjoner tildeler sekvensielle tall til rader basert på deres posisjon innenfor en partisjon. SQL Server tilbyr fire rangeringsfunksjoner. Hver funksjon håndterer uavgjort forskjellig:
ROW_NUMBER() tildeler et unikt sekvensielt nummer til hver rad, uten duplikater selv for likegyldige verdier:
SELECT
ProductID,
Name,
ListPrice,
ROW_NUMBER() OVER (ORDER BY ListPrice DESC) AS PriceRank
FROM SalesLT.Product
WHERE ListPrice > 0;
Resultatsettet ser slik ut:
ProductID Name ListPrice PriceRank
--------- ------------------------- --------- ---------
749 Road-150 Red, 62 3578.27 1
750 Road-150 Red, 44 3578.27 2
751 Road-150 Red, 48 3578.27 3
771 Mountain-100 Silver, 38 3399.99 4
Dette spørsmålet rangerer alle produkter etter pris fra høyest til lavest. Hvert produkt får et unikt nummer, uavhengig av om flere produkter deler samme pris eller ikke.
RANK() tildeler samme rang til verdier som er likt, og hopper deretter over tall for å ta høyde for uavgjorte:
SELECT
ProductID,
Name,
ListPrice,
RANK() OVER (ORDER BY ListPrice DESC) AS PriceRank
FROM SalesLT.Product
WHERE ListPrice > 0;
Resultatsettet ser slik ut:
ProductID Name ListPrice PriceRank
--------- ------------------------- --------- ---------
749 Road-150 Red, 62 3578.27 1
750 Road-150 Red, 44 3578.27 1
751 Road-150 Red, 48 3578.27 1
771 Mountain-100 Silver, 38 3399.99 4
Når to produkter har identiske priser, får begge samme rangering. Neste produkts rangering reflekterer totalt antall produkter rangert høyere, noe som skaper hull i rekkefølgen.
DENSE_RANK() tildeler samme rang til bundne verdier, men hopper ikke over tall:
SELECT
ProductID,
Name,
ListPrice,
DENSE_RANK() OVER (ORDER BY ListPrice DESC) AS PriceRank
FROM SalesLT.Product
WHERE ListPrice > 0;
Resultatsettet ser slik ut:
ProductID Name ListPrice PriceRank
--------- ------------------------- --------- ---------
749 Road-150 Red, 62 3578.27 1
750 Road-150 Red, 44 3578.27 1
751 Road-150 Red, 48 3578.27 1
771 Mountain-100 Silver, 38 3399.99 2
Som RANK(), har bundne verdier samme rang. Men DENSE_RANK() fortsetter med neste påfølgende tall, så du kan bruke det til å telle ulike prisnivåer.
NTILE(n) fordeler rader i et spesifisert antall omtrent like grupper:
SELECT
ProductID,
Name,
ListPrice,
NTILE(4) OVER (ORDER BY ListPrice DESC) AS PriceQuartile
FROM SalesLT.Product
WHERE ListPrice > 0;
Resultatsettet ser slik ut:
ProductID Name ListPrice PriceQuartile
--------- --------------------------- --------- -------------
749 Road-150 Red, 62 3578.27 1
771 Mountain-100 Silver, 38 3399.99 1
722 LL Road Frame - Black, 58 337.22 2
859 Half-Finger Gloves, S 24.49 4
Dette spørsmålet deler produktene inn i fire grupper basert på pris. De dyreste produktene er i kvartil 1, og de rimeligste er i kvartil 4. Bruk NTILE() til prosentilanalyse eller jevn fordeling av arbeid.
Kombinasjon PARTITION BY med rangeringsfunksjoner muliggjør rangeringer per gruppe:
SELECT
pc.Name AS Category,
p.Name AS Product,
p.ListPrice,
ROW_NUMBER() OVER (
PARTITION BY p.ProductCategoryID
ORDER BY p.ListPrice DESC
) AS CategoryPriceRank
FROM SalesLT.Product AS p
INNER JOIN SalesLT.ProductCategory AS pc
ON p.ProductCategoryID = pc.ProductCategoryID
WHERE p.ListPrice > 0;
Resultatsettet ser slik ut:
Category Product ListPrice CategoryPriceRank
--------------- ------------------------- --------- -----------------
Road Bikes Road-150 Red, 62 3578.27 1
Road Bikes Road-150 Red, 44 3578.27 2
Mountain Bikes Mountain-100 Silver, 38 3399.99 1
Mountain Bikes Mountain-100 Black, 38 3374.99 2
Denne spørringen rangerer produkter innenfor hver kategori separat. Rangeringen starter på nytt på 1 for hver kategori, så du kan identifisere det dyreste produktet i hver kategori ved å filtrere for CategoryPriceRank = 1.
Tips
Bruk ROW_NUMBER() når du trenger nøyaktig én rad per rang (for eksempel å finne de øverste N per gruppe). Bruk RANK() eller DENSE_RANK() når du trenger å bevare båndinformasjon for rapporteringsformål.
Bruk aggregerte vindusfunksjoner
Standard aggregerte funksjoner som SUM, AVG, , COUNT, MINog MAX kan brukes som vindusfunksjoner ved å legge til klausulen OVER . Dette lar deg beregne aggregater samtidig som du beholder individuelle raddetaljer.
Følgende spørring demonstrerer hvordan man beregner løpende totaler og kumulative aggregater:
SELECT
SalesOrderID,
OrderDate,
TotalDue,
SUM(TotalDue) OVER (ORDER BY OrderDate, SalesOrderID) AS RunningTotal,
AVG(TotalDue) OVER (ORDER BY OrderDate, SalesOrderID) AS RunningAverage,
COUNT(*) OVER (ORDER BY OrderDate, SalesOrderID) AS OrderNumber
FROM SalesLT.SalesOrderHeader
ORDER BY OrderDate, SalesOrderID;
Resultatsettet ser slik ut:
SalesOrderID OrderDate TotalDue RunningTotal RunningAverage OrderNumber
------------ ---------- --------- ------------ -------------- -----------
71774 2008-06-01 972.785 972.785 972.785 1
71776 2008-06-01 87.083 1059.868 529.934 2
71780 2008-06-01 42452.65 43512.518 14504.172 3
71782 2008-06-01 43962.79 87475.308 21868.827 4
Viktig!
Når man bruker aggregerte vindusfunksjoner uten ORDER BY i klausulen OVER , beregner funksjonen over hele partisjonen. Å legge til ORDER BY skaper en løpende beregning fra partisjonsstarten til gjeldende rad.
Definer vindusrammer med ROWS og RANGE
Vindusrammer lar deg spesifisere nøyaktig hvilke rader i forhold til den nåværende raden som skal inkluderes i beregningen. Klausulen ROWS teller fysiske rader, mens RANGE den grupperer rader med lik verdi.
Rammegrenser kan spesifiseres ved hjelp av:
-
UNBOUNDED PRECEDING: Fra partisjonsstarten -
n PRECEDING:nrader før nåværende rad -
CURRENT ROW: Den nåværende raden -
n FOLLOWING:nrader etter nåværende rad -
UNBOUNDED FOLLOWING: Til delingssiden
Følgende spørring beregner et glidende gjennomsnitt over de tre siste ordenene:
SELECT
SalesOrderID,
OrderDate,
TotalDue,
AVG(TotalDue) OVER (
ORDER BY OrderDate
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS MovingAvg3Orders
FROM SalesLT.SalesOrderHeader
ORDER BY OrderDate;
Resultatsettet ser slik ut:
SalesOrderID OrderDate TotalDue MovingAvg3Orders
------------ ---------- --------- ----------------
71774 2008-06-01 972.785 972.785
71776 2008-06-01 87.083 529.934
71780 2008-06-01 42452.65 14504.172
71782 2008-06-01 43962.79 28834.174
Denne spørringen beregner et 3-ordens glidende gjennomsnitt ved å inkludere gjeldende rad og de to radene før den. For første rad er bare én verdi tilgjengelig, så gjennomsnittet er TotalDuelik . Ved tredje rad inkluderer vinduet alle tre radene.
Bruk analytiske funksjoner
Analytiske funksjoner lar deg få tilgang til data fra andre rader uten å bruke selvsammenkoblinger eller underforespørsler. Disse funksjonene er nyttige for tidsserieanalyse, trenddeteksjon og sammenligning av nåværende verdier med historiske eller fremtidige verdier. I motsetning til aggregerte vindusfunksjoner som beregner sammendrag, henter analytiske funksjoner spesifikke verdier fra bestemte rader i vinduet.
LAG() og LEAD() tilgang til verdier fra tidligere eller påfølgende rader, slik som dette:
SELECT
SalesOrderID,
OrderDate,
TotalDue,
LAG(TotalDue, 1, 0) OVER (ORDER BY OrderDate) AS PreviousOrderTotal,
LEAD(TotalDue, 1, 0) OVER (ORDER BY OrderDate) AS NextOrderTotal,
TotalDue - LAG(TotalDue, 1, 0) OVER (ORDER BY OrderDate) AS ChangeFromPrevious
FROM SalesLT.SalesOrderHeader
ORDER BY OrderDate;
Resultatsettet ser slik ut:
SalesOrderID OrderDate TotalDue PreviousOrderTotal NextOrderTotal ChangeFromPrevious
------------ ---------- --------- ------------------ -------------- ------------------
71774 2008-06-01 972.785 0 87.083 972.785
71776 2008-06-01 87.083 972.785 42452.65 -885.702
71780 2008-06-01 42452.65 87.083 43962.79 42365.567
71782 2008-06-01 43962.79 42452.65 0 1510.14
LAG() henter en verdi fra en tidligere rad, mens LEAD() henter fra en påfølgende rad. Den andre parameteren spesifiserer hvor mange rader som skal ses bakover eller fremover (standard er 1), og den tredje parameteren gir en standardverdi når det ikke finnes noen rad (for eksempel for første rad med LAG()). Bruk disse funksjonene til å beregne periode-over-periode-endringer, identifisere trender eller oppdage avvik i sekvensielle data.
FIRST_VALUE() og LAST_VALUE() returnerer verdier fra første eller siste rad i rammen:
SELECT
ProductID,
Name,
ListPrice,
ProductCategoryID,
FIRST_VALUE(Name) OVER (
PARTITION BY ProductCategoryID
ORDER BY ListPrice DESC
) AS MostExpensiveInCategory,
LAST_VALUE(Name) OVER (
PARTITION BY ProductCategoryID
ORDER BY ListPrice DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS LeastExpensiveInCategory
FROM SalesLT.Product
WHERE ListPrice > 0;
Resultatsettet ser slik ut:
ProductID Name ListPrice ProductCategoryID MostExpensiveInCategory LeastExpensiveInCategory
--------- ------------------------- --------- ----------------- ------------------------ ------------------------
749 Road-150 Red, 62 3578.27 5 Road-150 Red, 62 LL Road Frame - Red, 58
750 Road-150 Red, 44 3578.27 5 Road-150 Red, 62 LL Road Frame - Red, 58
722 LL Road Frame - Red, 58 337.22 5 Road-150 Red, 62 LL Road Frame - Red, 58
771 Mountain-100 Silver, 38 3399.99 6 Mountain-100 Silver, 38 Mountain-500 Black, 52
FIRST_VALUE() returnerer verdien fra første rad i det ordnede vinduet, som i dette tilfellet er det dyreste produktet per kategori.
LAST_VALUE() Returer er de billigste, men krever en eksplisitt ramme som inkluderer alle rader. Disse funksjonene hjelper deg å sammenligne hver rad med referanseverdier som høyeste, laveste eller basisverdi i en gruppe.
Bemerkning
LAST_VALUE() krever en eksplisitt rammespesifikasjon for å inkludere rader etter gjeldende rad. Uten den inkluderer standardrammen kun rader opp til gjeldende rad, noe som gjør LAST_VALUE() at verdien til den nåværende raden returneres.
PERCENT_RANK() og CUME_DIST() beregner relativ posisjon innenfor en partisjon:
SELECT
Name,
ListPrice,
PERCENT_RANK() OVER (ORDER BY ListPrice) AS PercentRank,
CUME_DIST() OVER (ORDER BY ListPrice) AS CumulativeDistribution
FROM SalesLT.Product
WHERE ListPrice > 0
ORDER BY ListPrice;
Resultatsettet ser slik ut:
Name ListPrice PercentRank CumulativeDistribution
------------------------- --------- ----------- ----------------------
Patch Kit/8 Patches 2.29 0.0 0.0081
Road Tire Tube 3.99 0.0081 0.0162
Touring Tire Tube 4.99 0.0162 0.0243
Road-150 Red, 62 3578.27 0.9919 1.0
PERCENT_RANK() returnerer en verdi mellom 0 og 1 som angir hvor stor prosentandel av radene som har lavere verdier (0 betyr laveste, én betyr høyest).
CUME_DIST() viser den kumulative fordelingen, som indikerer hvor stor andel radene som har verdier mindre enn eller lik den nåværende raden. Bruk disse funksjonene til prosentilanalyse, identifisering av uteliggere eller opprettelse av distribusjonsrapporter.
For mer informasjon om vindusfunksjoner, se Vindusfunksjoner (Transact-SQL) og Rangeringsfunksjoner.