sql >> Databáze >  >> RDS >> Database

Udržování seskupeného běhu MAX (nebo MIN)

Poznámka:Tento příspěvek byl původně publikován pouze v naší elektronické knize, High Performance Techniques for SQL Server, Volume 3. O našich elektronických knihách se můžete dozvědět zde.

Jedním požadavkem, který občas vidím, je vrácený dotaz s objednávkami seskupenými podle zákazníka, který ukazuje maximální splatnou částku pro libovolnou dosavadní objednávku ("běžící maximum"). Představte si tedy tyto ukázkové řádky:

SalesOrderID Číslo zákazníka Datum objednávky TotalDue
12 2 1. 1. 2014 37,55
23 1 2014-01-02 45,29
31 2 2014-01-03 24.56
32 2 2014-01-04 89,84
37 1 2014-01-05 32,56
44 2 2014-01-06 45,54
55 1 2014-01-07 99,24
62 2 2014-01-08 12.55

Několik řádků ukázkových dat

Požadované výsledky z uvedených požadavků jsou následující – zjednodušeně řečeno seřadit objednávky každého zákazníka podle data a uvést každou objednávku. Pokud je to nejvyšší hodnota TotalDue pro všechny objednávky zobrazené do tohoto data, vytiskněte celkovou hodnotu této objednávky, jinak vytiskněte nejvyšší hodnotu TotalDue ze všech předchozích objednávek:

SalesOrderID CustomerID Datum objednávky TotalDue MaxTotalDue
12 1 2014-01-02 45,29 45,29
23 1 2014-01-05 32,56 45,29
31 1 2014-01-07 99,24 99,24
32 2 1. 1. 2014 37,55 37,55
37 2 2014-01-03 24,56 37,55
44 2 2014-01-04 89,84 89,84
55 2 2014-01-06 45,54 89,84
62 2 2014-01-08 12,55 89,84

Ukázka požadovaných výsledků

Mnoho lidí by k tomu instinktivně chtělo použít kurzor nebo smyčku while, ale existuje několik přístupů, které tyto konstrukce nezahrnují.

Korelovaný dílčí dotaz

Tento přístup se zdá být nejjednodušším a nejpřímějším přístupem k problému, ale znovu a znovu se prokázalo, že se neškáluje, protože hodnoty rostou exponenciálně, jak se tabulka zvětšuje:

SELECT /* Korelovaný poddotaz */ SalesOrderID, CustomerID, OrderDate, TotalDue, MaxTotalDue =(SELECT MAX(TotalDue) FROM Sales.SalesOrderHeader WHERE CustomerID =h.CustomerID AND SalesOrderID <=h.SalesOrderID) h.SalesOrderID) FROM Sales.Hear ORDER BY CustomerID, SalesOrderID;

Zde je plán proti AdventureWorks2014 pomocí SQL Sentry Plan Explorer:

Plán provádění pro korelovaný dílčí dotaz (kliknutím zvětšíte)

KŘÍŽOVÁ POUŽITÍ s vlastním odkazem

Tento přístup je téměř identický s přístupem Correlated Subquery, pokud jde o syntaxi, tvar plánu a výkon v měřítku.

SELECT /* CROSS APPLY */ h.SalesOrderID, h.CustomerID, h.OrderDate, h.TotalDue, x.MaxTotalDueFROM Sales.SalesOrderHeader AS hCROSS APPLY( SELECT MaxTotalDue =MAXder FROM AS SalesWHESules) i.CustomerID =h.CustomerID A i.SalesOrderID <=h.SalesOrderID) JAKO xORDER BY h.CustomerID, h.SalesOrderID;

Plán je docela podobný korelovanému plánu poddotazů, jediným rozdílem je umístění řazení:

Prováděcí plán pro CROSS APPLY (kliknutím zvětšíte)

Rekurzivní CTE

V zákulisí to používá smyčky, ale dokud to skutečně nespustíme, můžeme tak trochu předstírat, že tomu tak není (ačkoli je to snadno nejsložitější část kódu, kterou bych kdy chtěl napsat, abych vyřešil tento konkrétní problém):

;WITH /* Rekurzivní CTE */ cte AS ( SELECT SalesOrderID, CustomerID, OrderDate, TotalDue, MaxTotalDue FROM ( SELECT SalesOrderID, CustomerID, OrderDate, TotalDue, MaxTotalDue =TotalDue, rn =ROW_NUMBER() OVER (PARTITION BY customer) BY SalesOrderID) Z Sales.SalesOrderHeader ) AS x WHERE rn =1 UNION ALL SELECT r.SalesOrderID, r.CustomerID, r.OrderDate, r.TotalDue, MaxTotalDue =CASE WHEN r.TotalTo.MaeDue> cteTotalTo.MaeDue .MaxTotalDue END FROM cte CROSS APPLY ( SELECT SalesOrderID, CustomerID, OrderDate, TotalDue, rn =ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY SalesOrderID) Z Sales.SalesOrderHeader AS h AND WHERE =h.CustomerID.ID cte.SalesOrderID ) AS r WHERE r.rn =1)VYBERTE ID objednávky, ID zákazníka, Datum objednávky, TotalDue, MaxTotalDueFROM cteORDER BY CustomerID, SalesOrderIDOPTION (MAXRECURSION 0);

Okamžitě vidíte, že plán je složitější než předchozí dva, což není překvapivé vzhledem ke složitějšímu dotazu:

Plán provádění pro rekurzivní CTE (kliknutím zvětšíte)

Kvůli některým špatným odhadům vidíme hledání indexu s doprovodným vyhledáváním klíče, které by pravděpodobně mělo být nahrazeno jediným skenováním, a také dostáváme operaci řazení, která se nakonec musí přenést do tempdb (můžete to vidět v popisku pokud najedete na operátor řazení s ikonou varování):

MAXIMÁLNĚ() PŘES (ŘÁDKY NEOZEŽENY)

Toto je řešení dostupné pouze v SQL Server 2012 a vyšších, protože využívá nově zavedená rozšíření funkcí oken.

SELECT /* MAX() OVER() */ SalesOrderID, CustomerID, OrderDate, TotalDue, MaxTotalDue =MAX(TotalDue) NAD ( ODDĚLENÍ PODLE OBJEDNÁVKY ČÍSLO zákazníka PODLE ŘÁDKŮ ID objednávky NEZAPOJENO PŘEDCHÁZEJÍCÍ )OD Sales.Sales 

Plán přesně ukazuje, proč se škáluje lépe než všechny ostatní; má pouze jednu operaci skenování klastrovaného indexu, na rozdíl od dvou (nebo špatná volba skenování a hledání + vyhledávání v případě rekurzivního CTE):

Plán provedení pro MAX() OVER() (kliknutím zvětšíte)

Porovnání výkonu

Plány nás jistě vedou k přesvědčení, že nový MAX() OVER() schopnost v SQL Server 2012 je skutečným vítězem, ale co hmatatelné metriky za běhu? Zde je srovnání poprav:

První dva dotazy byly téměř totožné; zatímco v tomto případě CROSS APPLY byl o něco lepší, pokud jde o celkovou dobu trvání, korelovaný poddotaz jej místo toho někdy o něco překoná. Rekurzivní CTE je pokaždé podstatně pomalejší a můžete vidět faktory, které k tomu přispívají – jmenovitě špatné odhady, obrovské množství čtení, vyhledávání klíčů a další operace řazení. A jak jsem již dříve ukázal na průběžných součtech, řešení SQL Server 2012 je lepší téměř ve všech aspektech.

Závěr

Pokud používáte SQL Server 2012 nebo vyšší, určitě se chcete seznámit se všemi rozšířeními funkcí oken, které byly poprvé představeny v SQL Server 2005 – mohou vám poskytnout docela výrazné zvýšení výkonu při opětovném prohlížení kódu, který je stále spuštěn. starým způsobem." Pokud se chcete o některých z těchto nových funkcí dozvědět více, vřele doporučuji knihu Itzika Ben-Gana, Microsoft SQL Server 2012 High-Performance T-SQL Using Window Functions.

Pokud ještě nepoužíváte SQL Server 2012, alespoň v tomto testu jste si mohli vybrat mezi CROSS APPLY a korelovaný poddotaz. Jako vždy byste měli otestovat různé metody proti vašim datům na vašem hardwaru.


  1. Jak vypsat všechny uložené procedury v MariaDB

  2. Jak získat datum ve formátu RRRR-MM-DD z pole data a času TSQL?

  3. Heroku Postgres:Příliš mnoho spojení. Jak zničím tato spojení?

  4. Jak používat příkaz Zkomprimovat a opravit v Accessu