sql >> Database teknologi >  >> RDS >> Sqlserver

Partitionering resulterer i en kørende totalforespørgsel

Hvis du ikke har brug for at GEMME dataene (hvilket du ikke bør, fordi du skal opdatere de løbende totaler hver gang en række ændres, tilføjes eller slettes), og hvis du ikke stoler på den finurlige opdatering (som du bør ikke, fordi det ikke er garanteret at virke, og dets adfærd kan ændre sig med et hotfix, service pack, opgradering eller endda en underliggende indeks eller statistikændring), kan du prøve denne type forespørgsel under kørsel. Dette er en metode, kollega MVP Hugo Kornelis opfandt "sæt-baseret iteration" (han postede noget lignende i et af sine kapitler af SQL Server MVP Deep Dives ). Da løbende totaler typisk kræver en markør over hele sættet, en finurlig opdatering over hele sættet eller en enkelt ikke-lineær selvforbindelse, der bliver dyrere og dyrere, efterhånden som rækkeantallet stiger, er tricket her at gå gennem nogle endelige element i sættet (i dette tilfælde "rangen" for hver række i form af måned, for hver bruger - og du behandler kun hver rang én gang for alle bruger/måned-kombinationer i den rang, så i stedet for at gå gennem 200.000 rækker, du looper op til 24 gange).

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;

Resultater:

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

Selvfølgelig kan du opdatere basistabellen fra denne tabelvariabel, men hvorfor bekymre sig, da disse lagrede værdier kun er gode, indtil næste gang tabellen berøres af en DML-sætning?

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

Da vi ikke er afhængige af implicit bestilling af nogen art, er dette 100 % understøttet og fortjener en præstationssammenligning i forhold til den ikke-understøttede finurlige opdatering. Selvom det ikke slår det, men kommer tæt på, bør du overveje at bruge det alligevel IMHO.

Hvad angår SQL Server 2012-løsningen, nævner Matt RANGE men da denne metode bruger en spool på disken, bør du også teste med ROWS i stedet for bare at køre med RANGE . Her er et hurtigt eksempel til din sag:

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

Sammenlign dette med RANGE UNBOUNDED PRECEDING eller ingen ROWS\RANGE overhovedet (som også vil bruge RANGE). spool på disk). Ovenstående vil have lavere samlet varighed og måde mindre I/O, selvom planen ser lidt mere kompleks ud (en ekstra sekvensprojektoperatør).

Jeg har for nylig udgivet et blogindlæg, der skitserer nogle præstationsforskelle, jeg observerede for et specifikt scenarie med løbende totaler:

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



  1. Oracle:Vælg Fra Record Datatype

  2. Synkronisering af en SQLite-klientdatabase med en MySQL-serverdatabase

  3. Accent- og store og små bogstaver i Oracle med LIKE

  4. Sådan rettes Security Advisor MySQL Alert