Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Oblicz sumę bieżącą / bilans bieżący

Dla tych, którzy nie używają SQL Server 2012 lub nowszego, kursor jest prawdopodobnie najbardziej wydajnym obsługiwanym i gwarantowane metoda poza CLR. Istnieją inne podejścia, takie jak „dziwaczna aktualizacja”, która może być nieznacznie szybsza, ale nie ma gwarancji, że zadziała w przyszłości, i oczywiście podejścia oparte na zbiorach z hiperbolicznymi profilami wydajności w miarę powiększania się tabeli oraz rekurencyjne metody CTE, które często wymagają bezpośredniego #tempdb I/O lub powodują wycieki, które mają mniej więcej taki sam wpływ.

DOŁĄCZENIE WEWNĘTRZNE — nie rób tego:

Powolne, oparte na zbiorach podejście ma postać:

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;

Dlaczego to jest powolne? W miarę powiększania się tabeli każdy kolejny wiersz wymaga odczytania n-1 wierszy w tabeli. Jest to wykładniczy i związany z awariami, przekroczeniami limitu czasu lub po prostu rozgniewanymi użytkownikami.

Skorelowane podzapytanie – też tego nie rób:

Forma podzapytania jest podobnie bolesna z podobnie bolesnych powodów.

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;

Dziwaczna aktualizacja – rób to na własne ryzyko:

Metoda „dziwacznej aktualizacji” jest bardziej wydajna niż powyższa, ale zachowanie nie jest udokumentowane, nie ma gwarancji co do porządku, a zachowanie może działać dzisiaj, ale może ulec awarii w przyszłości. Włączam to, ponieważ jest to popularna metoda i jest skuteczna, ale to nie znaczy, że ją popieram. Głównym powodem, dla którego odpowiedziałem na to pytanie, zamiast zamykać je jako duplikat, jest to, że drugie pytanie ma dziwaczną aktualizację jako zaakceptowaną odpowiedź.

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;

Rekursywne CTE

Ten pierwszy opiera się na TID, aby był ciągły, bez przerw:

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

Jeśli nie możesz na tym polegać, możesz użyć tej odmiany, która po prostu tworzy ciągłą sekwencję za 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);

W zależności od rozmiaru danych (np. kolumn, o których nie wiemy), możesz uzyskać lepszą ogólną wydajność, umieszczając najpierw odpowiednie kolumny tylko w tabeli #temp i przetwarzając je na podstawie tego zamiast tabeli podstawowej:

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;

Tylko pierwsza metoda CTE zapewni wydajność rywalizującą z dziwaczną aktualizacją, ale zakłada duże założenie co do natury danych (bez luk). Pozostałe dwie metody wycofają się iw takich przypadkach równie dobrze możesz użyć kursora (jeśli nie możesz używać CLR i nie korzystasz jeszcze z SQL Server 2012 lub nowszego).

Kursor

Wszystkim mówi się, że kursory są złe i należy ich unikać za wszelką cenę, ale to faktycznie bije wydajność większości innych obsługiwanych metod i jest bezpieczniejsze niż dziwaczna aktualizacja. Jedyne, które wolę od rozwiązania kursora, to metody 2012 i CLR (poniżej):

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

Nowe funkcje okien wprowadzone w SQL Server 2012 znacznie ułatwiają to zadanie (i działają lepiej niż wszystkie powyższe metody):

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

Zauważ, że w przypadku większych zestawów danych powyższe działa znacznie lepiej niż którakolwiek z poniższych dwóch opcji, ponieważ RANGE używa buforowania na dysku (a domyślnie używa RANGE). Jednak ważne jest również, aby pamiętać, że zachowanie i wyniki mogą się różnić, więc upewnij się, że oba zwracają prawidłowe wyniki, zanim zdecydujesz między nimi na podstawie tej różnicy.

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

Dla kompletności oferuję link do metody CLR Pawła Pawłowskiego, która jest zdecydowanie preferowaną metodą w wersjach wcześniejszych niż SQL Server 2012 (ale oczywiście nie 2000).

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

Wniosek

Jeśli korzystasz z SQL Server 2012 lub nowszego, wybór jest oczywisty — użyj nowej metody SUM() OVER() konstrukcja (z ROWS w porównaniu z RANGE ). W przypadku wcześniejszych wersji warto porównać wydajność alternatywnych podejść w schemacie, danych i — biorąc pod uwagę czynniki niezwiązane z wydajnością — określić, które podejście jest dla Ciebie odpowiednie. Bardzo dobrze może to być podejście CLR. Oto moje zalecenia w kolejności preferencji:

  1. SUM() OVER() ... ROWS , jeśli w 2012 r. lub nowszym
  2. Metoda CLR, jeśli to możliwe
  3. Pierwsza rekurencyjna metoda CTE, jeśli to możliwe
  4. Kursor
  5. Inne rekurencyjne metody CTE
  6. Dziwaczna aktualizacja
  7. Dołącz i/lub skorelowane podzapytanie

Więcej informacji na temat porównań wydajności tych metod można znaleźć w tym pytaniu na stronie http://dba.stackexchange.com:

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

Więcej szczegółów na temat tych porównań napisałem tutaj:

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

Również w przypadku zgrupowanych/partycjonowanych sum bieżących zobacz następujące posty:

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

Partycjonowanie wyników w uruchomionym zapytaniu podsumowującym

Wiele sum bieżących z grupowaniem według



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. MVC4:UserIsInRole — nie można połączyć się z bazą danych SQL Server

  2. SQL Server 2016:Widok projektanta

  3. Jaki jest odpowiednik „tabeli opisowej” w SQL Server?

  4. Nieudane wywołanie ODBC z procedurą składowaną — zapytanie przekazujące

  5. Jak utworzyć złożony klucz obcy w SQL Server (przykład T-SQL)