sql >> Databáze >  >> RDS >> Sqlserver

Vypočítejte průběžný součet / průběžný zůstatek

Pro ty, kteří nepoužívají SQL Server 2012 nebo vyšší, je pravděpodobně nejúčinnějším podporovaným kurzor. a zaručené metoda mimo CLR. Existují i ​​další přístupy, jako je „zvláštní aktualizace“, která může být o něco rychlejší, ale není zaručeno, že bude v budoucnu fungovat, a samozřejmě přístupy založené na množinách s hyperbolickými profily výkonu, jak se tabulka zvětšuje, a rekurzivní metody CTE, které často vyžadují přímé #tempdb I/O nebo způsobit úniky, které mají zhruba stejný dopad.

INNER JOIN – nedělejte toto:

Pomalý přístup založený na množinách má tvar:

SELECT t1.TID, t1.amt, RunningTotal = SUM(t2.amt)
FROM dbo.Transactions AS t1
INNER JOIN dbo.Transactions AS t2
  ON t1.TID >= t2.TID
GROUP BY t1.TID, t1.amt
ORDER BY t1.TID;

Proč je to pomalé? Jak se tabulka zvětšuje, každý přírůstkový řádek vyžaduje čtení n-1 řádků v tabulce. To je exponenciální a vázáno na selhání, časové limity nebo jen naštvané uživatele.

Korelovaný poddotaz – neprovádějte ani toto:

Formulář dílčího dotazu je podobně bolestivý z podobně bolestivých důvodů.

SELECT TID, amt, RunningTotal = amt + COALESCE(
(
  SELECT SUM(amt)
    FROM dbo.Transactions AS i
    WHERE i.TID < o.TID), 0
)
FROM dbo.Transactions AS o
ORDER BY TID;

Svérázná aktualizace – provádějte to na vlastní riziko:

Metoda „zvláštní aktualizace“ je efektivnější než výše uvedená, ale chování není zdokumentováno, neexistují žádné záruky ohledně pořádku a chování může fungovat dnes, ale v budoucnu se může zlomit. Zahrnuji to, protože je to populární metoda a je účinná, ale to neznamená, že ji podporuji. Hlavním důvodem, proč jsem na tuto otázku odpověděl, místo abych ji zavřel jako duplikát, je to, že druhá otázka má jako přijatou odpověď podivnou aktualizaci.

DECLARE @t TABLE
(
  TID INT PRIMARY KEY,
  amt INT,
  RunningTotal INT
);
 
DECLARE @RunningTotal INT = 0;
 
INSERT @t(TID, amt, RunningTotal)
  SELECT TID, amt, RunningTotal = 0
  FROM dbo.Transactions
  ORDER BY TID;
 
UPDATE @t
  SET @RunningTotal = RunningTotal = @RunningTotal + amt
  FROM @t;
 
SELECT TID, amt, RunningTotal
  FROM @t
  ORDER BY TID;

Rekurzivní CTE

Tento první spoléhá na to, že TID bude souvislý, bez mezer:

;WITH x AS
(
  SELECT TID, amt, RunningTotal = amt
    FROM dbo.Transactions
    WHERE TID = 1
  UNION ALL
  SELECT y.TID, y.amt, x.RunningTotal + y.amt
   FROM x 
   INNER JOIN dbo.Transactions AS y
   ON y.TID = x.TID + 1
)
SELECT TID, amt, RunningTotal
  FROM x
  ORDER BY TID
  OPTION (MAXRECURSION 10000);

Pokud se na to nemůžete spolehnout, můžete použít tuto variantu, která jednoduše vytvoří souvislou sekvenci pomocí ROW_NUMBER() :

;WITH y AS 
(
  SELECT TID, amt, rn = ROW_NUMBER() OVER (ORDER BY TID)
    FROM dbo.Transactions
), x AS
(
    SELECT TID, rn, amt, rt = amt
      FROM y
      WHERE rn = 1
    UNION ALL
    SELECT y.TID, y.rn, y.amt, x.rt + y.amt
      FROM x INNER JOIN y
      ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
  FROM x
  ORDER BY x.rn
  OPTION (MAXRECURSION 10000);

V závislosti na velikosti dat (např. sloupců, o kterých nevíme), můžete dosáhnout lepšího celkového výkonu tím, že příslušné sloupce nejprve nacpete pouze do #temp tabulky a zpracujete proti ní namísto základní tabulky:

CREATE TABLE #x
(
  rn  INT PRIMARY KEY,
  TID INT,
  amt INT
);

INSERT INTO #x (rn, TID, amt)
SELECT ROW_NUMBER() OVER (ORDER BY TID),
  TID, amt
FROM dbo.Transactions;

;WITH x AS
(
  SELECT TID, rn, amt, rt = amt
    FROM #x
    WHERE rn = 1
  UNION ALL
  SELECT y.TID, y.rn, y.amt, x.rt + y.amt
    FROM x INNER JOIN #x AS y
    ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
  FROM x
  ORDER BY TID
  OPTION (MAXRECURSION 10000);

DROP TABLE #x;

Pouze první metoda CTE poskytne výkon konkurující svérázné aktualizaci, ale vytváří velký předpoklad o povaze dat (žádné mezery). Další dvě metody se vrátí zpět a v těchto případech můžete také použít kurzor (pokud nemůžete použít CLR a ještě nepoužíváte SQL Server 2012 nebo vyšší).

Kurzor

Každému se říká, že kurzory jsou zlé a že je třeba se jim za každou cenu vyhnout, ale to ve skutečnosti překonává výkon většiny ostatních podporovaných metod a je to bezpečnější než podivná aktualizace. Jediné, co preferuji před řešením kurzoru, jsou metody 2012 a CLR (níže):

CREATE TABLE #x
(
  TID INT PRIMARY KEY, 
  amt INT, 
  rt INT
);

INSERT #x(TID, amt) 
  SELECT TID, amt
  FROM dbo.Transactions
  ORDER BY TID;

DECLARE @rt INT, @tid INT, @amt INT;
SET @rt = 0;

DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
  FOR SELECT TID, amt FROM #x ORDER BY TID;

OPEN c;

FETCH c INTO @tid, @amt;

WHILE @@FETCH_STATUS = 0
BEGIN
  SET @rt = @rt + @amt;
  UPDATE #x SET rt = @rt WHERE TID = @tid;
  FETCH c INTO @tid, @amt;
END

CLOSE c; DEALLOCATE c;

SELECT TID, amt, RunningTotal = rt 
  FROM #x 
  ORDER BY TID;

DROP TABLE #x;

SQL Server 2012 nebo vyšší

Nové funkce okna zavedené v SQL Server 2012 tento úkol značně usnadňují (a také funguje lépe než všechny výše uvedené metody):

SELECT TID, amt, 
  RunningTotal = SUM(amt) OVER (ORDER BY TID ROWS UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;

Všimněte si, že na větších souborech dat zjistíte, že výše uvedené funguje mnohem lépe než kterákoli z následujících dvou možností, protože RANGE používá zařazování na disku (a výchozí používá RANGE). Je však také důležité poznamenat, že chování a výsledky se mohou lišit, takže se ujistěte, že oba vrátí správné výsledky, než se mezi nimi na základě tohoto rozdílu rozhodnete.

SELECT TID, amt, 
  RunningTotal = SUM(amt) OVER (ORDER BY TID)
FROM dbo.Transactions
ORDER BY TID;

SELECT TID, amt, 
  RunningTotal = SUM(amt) OVER (ORDER BY TID RANGE UNBOUNDED PRECEDING)
FROM dbo.Transactions
ORDER BY TID;

CLR

Pro úplnost nabízím odkaz na metodu CLR Pavla Pawlowského, což je zdaleka preferovaná metoda ve verzích před SQL Server 2012 (ale samozřejmě ne 2000).

http://www.pawlowski.cz/2010/09/sql-server-and-fastest-running-totals-using-clr/

Závěr

Pokud používáte SQL Server 2012 nebo vyšší, volba je zřejmá – použijte nový SUM() OVER() konstrukci (pomocí ROWS vs. RANGE ). U dřívějších verzí budete chtít porovnat výkon alternativních přístupů na vašem schématu, datech a – s ohledem na faktory nesouvisející s výkonem – určit, který přístup je pro vás ten pravý. Velmi dobře to může být přístup CLR. Zde jsou má doporučení v pořadí podle preferencí:

  1. SUM() OVER() ... ROWS , pokud jde o rok 2012 nebo vyšší
  2. Metoda CLR, pokud je to možné
  3. Pokud je to možné, první rekurzivní metoda CTE
  4. Kurzor
  5. Další rekurzivní metody CTE
  6. Nevšední aktualizace
  7. Připojit se a/nebo korelovat dílčí dotaz

Další informace s porovnáním výkonu těchto metod naleznete v této otázce na http://dba.stackexchange.com:

https://dba.stackexchange.com/questions/19507/running-total-with-count

Více podrobností o těchto srovnáních jsem také napsal zde:

http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals

Také pro seskupené/rozdělené průběžné součty viz následující příspěvky:

http://sqlperformance.com/2014/01/t-sql-queries/grouped-running-totals

Výsledkem rozdělení je dotaz na součet

Více průběžných součtů s Group By



  1. Jak nainstalovat SQLOPS na Mac

  2. Jak spustit Create Table DDL s EXECUTE IMMEDIATE v Oracle Database

  3. Jak změnit databázi na postgresql pomocí Symfony 2.0?

  4. Vědět, jak obnovit smazanou tabulku v SQL Server 2012 bez zálohování