sql >> Database >  >> RDS >> Sqlserver

Partitioneren resulteert in een lopende totalenquery

Als u de gegevens niet OPSLAAN (wat u niet zou moeten doen, omdat u de lopende totalen moet bijwerken telkens wanneer een rij wordt gewijzigd, toegevoegd of verwijderd), en als u de eigenaardige update (die u zou niet moeten, omdat het niet gegarandeerd werkt en het gedrag kan veranderen met een hotfix, servicepack, upgrade of zelfs een onderliggende index of statistische wijziging), kunt u dit type query tijdens runtime proberen. Dit is een methode die collega MVP Hugo Kornelis "set-based iteratie" bedacht (hij plaatste iets soortgelijks in een van zijn hoofdstukken van SQL Server MVP diepe duiken ). Aangezien het uitvoeren van totalen doorgaans een cursor over de hele set vereist, een eigenzinnige update over de hele set, of een enkele niet-lineaire self-join die steeds duurder wordt naarmate het aantal rijen toeneemt, is de truc hier om door een eindige element in de set (in dit geval de "rang" van elke rij in termen van maand, voor elke gebruiker - en je verwerkt elke rang slechts één keer voor alle combinaties van gebruiker/maand op die rang, dus in plaats van 200.000 rijen te doorlopen, u kunt maximaal 24 keer herhalen).

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;

Resultaten:

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

Natuurlijk kunt u update de basistabel van deze tabelvariabele, maar waarom zou u zich druk maken, aangezien die opgeslagen waarden alleen goed zijn tot de volgende keer dat de tabel wordt aangeraakt door een DML-instructie?

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

Aangezien we niet vertrouwen op impliciete bestellingen van welke aard dan ook, wordt dit 100% ondersteund en verdient het een prestatievergelijking ten opzichte van de niet-ondersteunde eigenzinnige update. Zelfs als het niet beter is, maar in de buurt komt, zou je moeten overwegen het toch te gebruiken IMHO.

Wat betreft de SQL Server 2012-oplossing, noemt Matt RANGE maar aangezien deze methode een spool op schijf gebruikt, moet u ook testen met ROWS in plaats van alleen te draaien met RANGE . Hier is een snel voorbeeld voor uw geval:

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

Vergelijk dit met RANGE UNBOUNDED PRECEDING of geen ROWS\RANGE helemaal niet (die ook de RANGE . zal gebruiken op de schijfspoel). Het bovenstaande heeft een lagere totale duur en manier minder I/O, hoewel het plan er iets complexer uitziet (een extra sequentieprojectoperator).

Ik heb onlangs een blogpost gepubliceerd waarin enkele prestatieverschillen worden beschreven die ik heb waargenomen voor een specifiek scenario met lopende totalen:

http://www.sqlperformance.com/2012/07 /t-sql-queries/lopende-totalen



  1. De naam van een beperking vinden in MySQL

  2. Hoe het maximale aantal verbindingen in MySQL te vergroten?

  3. SQL Server Agent-taken lokaliseren in Azure Data Studio

  4. hoe maak je een inlogpagina in de Android-app?