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

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

Pokud nepotřebujete UCHOVÁVAT data (což byste neměli, protože potřebujete aktualizovat průběžné součty při každé změně, přidání nebo odstranění řádku), a pokud nedůvěřujete svérázné aktualizaci (které by nemělo, protože není zaručeno, že bude fungovat a jeho chování by se mohlo změnit opravou hotfix, aktualizací Service Pack, upgradem nebo dokonce změnou základního indexu nebo statistik), můžete tento typ dotazu vyzkoušet za běhu. Toto je metoda, kterou kolega MVP Hugo Kornelis vytvořil „set-based iteration“ (něco podobného zveřejnil v jedné ze svých kapitol SQL Server MVP Deep Dives ). Vzhledem k tomu, že spouštění součtů obvykle vyžaduje kurzor nad celou sadou, svéráznou aktualizaci nad celou sadou nebo jediné nelineární samospojení, které se s rostoucím počtem řádků stává stále dražším, je zde trik procházet nějakým konečným prvek v sadě (v tomto případě „hodnocení“ každého řádku z hlediska měsíce, pro každého uživatele – a vy zpracujete každé hodnocení pouze jednou pro všechny kombinace uživatel/měsíc v tomto pořadí, takže místo procházení 200 000 řádky, opakujte až 24krát).

DECLARE @t TABLE
(
  [user_id] INT, 
  [month] TINYINT,
  total DECIMAL(10,1), 
  RunningTotal DECIMAL(10,1), 
  Rnk INT
);

INSERT @t SELECT [user_id], [month], total, total, 
  RANK() OVER (PARTITION BY [user_id] ORDER BY [month]) 
  FROM dbo.my_table;

DECLARE @rnk INT = 1, @rc INT = 1;

WHILE @rc > 0
BEGIN
  SET @rnk += 1;

  UPDATE c SET RunningTotal = p.RunningTotal + c.total
    FROM @t AS c INNER JOIN @t AS p
    ON c.[user_id] = p.[user_id]
    AND p.rnk = @rnk - 1
    AND c.rnk = @rnk;

  SET @rc = @@ROWCOUNT;
END

SELECT [user_id], [month], total, RunningTotal
FROM @t
ORDER BY [user_id], rnk;

Výsledky:

user_id  month   total   RunningTotal
-------  -----   -----   ------------
1        1       2.0     2.0
1        2       1.0     3.0
1        3       3.5     6.5 -- I think your calculation is off
2        1       0.5     0.5
2        2       1.5     2.0
2        3       2.0     4.0

Samozřejmě můžete aktualizujte základní tabulku z této proměnné tabulky, ale proč se obtěžovat, protože tyto uložené hodnoty jsou dobré pouze do doby, než se tabulky příště dotkne jakýkoli příkaz DML?

UPDATE mt
  SET cumulative_total = t.RunningTotal
  FROM dbo.my_table AS mt
  INNER JOIN @t AS t
  ON mt.[user_id] = t.[user_id]
  AND mt.[month] = t.[month];

Protože se nespoléháme na implicitní řazení jakéhokoli druhu, je to 100% podporováno a zaslouží si srovnání výkonu ve srovnání s nepodporovanou nepředvídatelnou aktualizací. I když to nepřekoná, ale blíží se, měli byste zvážit jeho použití IMHO.

Pokud jde o řešení SQL Server 2012, Matt zmiňuje RANGE ale protože tato metoda používá zařazování na disku, měli byste také testovat pomocí ROWS místo pouhého spuštění s RANGE . Zde je rychlý příklad pro váš případ:

SELECT
  [user_id],
  [month],
  total,
  RunningTotal = SUM(total) OVER 
  (
    PARTITION BY [user_id] 
    ORDER BY [month] ROWS UNBOUNDED PRECEDING
  )
FROM dbo.my_table
ORDER BY [user_id], [month];

Porovnejte to s RANGE UNBOUNDED PRECEDING nebo žádný ROWS\RANGE vůbec (což bude také používat RANGE cívka na disku). Výše uvedené bude mít nižší celkovou dobu trvání a způsob méně I/O, i když plán vypadá o něco složitější (další operátor sekvenčního projektu).

Nedávno jsem publikoval příspěvek na blogu popisující některé rozdíly ve výkonu, které jsem pozoroval u konkrétního scénáře průběžných součtů:

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



  1. MySQL – vztah jeden k jednomu?

  2. Používání Amazon RDS s aplikací pro Android

  3. Skripty SQL – Existuje ekvivalent #define?

  4. Špatné návyky :Při výběru klíčů se zaměřte pouze na místo na disku