sql >> Databasteknik >  >> RDS >> Sqlserver

Partitionering resulterar i en löpande totalfråga

Om du inte behöver LAGRA data (vilket du inte borde, eftersom du behöver uppdatera de löpande totalsummorna varje gång en rad ändras, läggs till eller tas bort), och om du inte litar på den udda uppdateringen (som du borde inte, eftersom det inte är garanterat att fungera och dess beteende kan ändras med en snabbkorrigering, service pack, uppgradering eller till och med en underliggande index- eller statistikändring), kan du prova den här typen av fråga under körning. Detta är en metod som kollegan MVP Hugo Kornelis myntade "uppsättningsbaserad iteration" (han postade något liknande i ett av sina kapitel av SQL Server MVP Deep Dives ). Eftersom löpande totaler vanligtvis kräver en markör över hela uppsättningen, en udda uppdatering över hela uppsättningen eller en enda icke-linjär självkoppling som blir dyrare och dyrare när radantalet ökar, är tricket här att gå igenom något ändligt element i uppsättningen (i det här fallet "ranken" för varje rad i termer av månad, för varje användare - och du bearbetar bara varje rankning en gång för alla användare/månadskombinationer i den rankningen, så istället för att gå igenom 200 000 rader, du loopar upp till 24 gånger).

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;

Resultat:

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

Självklart kan uppdatera bastabellen från denna tabellvariabel, men varför bry sig, eftersom de lagrade värdena bara är bra tills nästa gång tabellen berörs av någon DML-sats?

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

Eftersom vi inte förlitar oss på implicit beställning av något slag, stöds detta till 100 % och förtjänar en prestandajämförelse i förhållande till den knäppa uppdateringen som inte stöds. Även om den inte slår den utan kommer nära, bör du överväga att använda den ändå IMHO.

När det gäller SQL Server 2012-lösningen nämner Matt RANGE men eftersom denna metod använder en spool på disk bör du också testa med ROWS istället för att bara köra med RANGE . Här är ett snabbt exempel för ditt fall:

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

Jämför detta med RANGE UNBOUNDED PRECEDING eller ingen ROWS\RANGE alls (som också kommer att använda RANGE på diskspole). Ovanstående kommer att ha lägre total varaktighet och väg mindre I/O, även om planen ser något mer komplex ut (en extra sekvensprojektoperatör).

Jag har nyligen publicerat ett blogginlägg som beskriver några resultatskillnader som jag observerade för ett specifikt scenario med löpande totalsummor:

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



  1. MySQL utlösa syntax för "uppdatering på kolumn".

  2. Hur PI() fungerar i MariaDB

  3. WAMP Virtual Host fungerar inte

  4. Hur kan jag lägga till en kolumn som inte tillåter nollvärden i en Postgresql-databas?