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

Il partizionamento genera una query sui totali parziali

Se non hai bisogno di ARCHIVIARE i dati (cosa che non dovresti, perché devi aggiornare i totali parziali ogni volta che una riga viene modificata, aggiunta o eliminata) e se non ti fidi dell'eccentrico aggiornamento (che non dovrebbe, poiché non è garantito il funzionamento e il suo comportamento potrebbe cambiare con un hotfix, un service pack, un aggiornamento o anche un indice sottostante o una modifica delle statistiche), puoi provare questo tipo di query in fase di esecuzione. Questo è un metodo che il collega MVP Hugo Kornelis ha coniato "iterazione basata su set" (ha pubblicato qualcosa di simile in uno dei suoi capitoli di Approfondimenti di SQL Server MVP ). Poiché i totali parziali in genere richiedono un cursore sull'intero set, un bizzarro aggiornamento sull'intero set o un singolo self-join non lineare che diventa sempre più costoso all'aumentare del conteggio delle righe, il trucco qui è scorrere alcuni elementi finiti elemento nel set (in questo caso, la "classifica" di ogni riga in termini di mese, per ogni utente - e si elabora ogni classifica solo una volta per tutte le combinazioni utente/mese in quella classifica, quindi invece di scorrere 200.000 righe, ripeti fino a 24 volte).

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;

Risultati:

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

Ovviamente puoi aggiorna la tabella di base da questa variabile di tabella, ma perché preoccuparsi, dal momento che quei valori memorizzati sono validi solo fino alla prossima volta che la tabella viene toccata da qualsiasi istruzione 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];

Dal momento che non ci basiamo su ordini impliciti di alcun tipo, questo è supportato al 100% e merita un confronto delle prestazioni rispetto all'eccentrico aggiornamento non supportato. Anche se non lo batte ma si avvicina, dovresti considerare di usarlo comunque IMHO.

Per quanto riguarda la soluzione SQL Server 2012, Matt cita RANGE ma poiché questo metodo utilizza uno spool su disco, dovresti anche testare con ROWS invece di eseguire solo con RANGE . Ecco un rapido esempio per il tuo caso:

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

Confrontalo con RANGE UNBOUNDED PRECEDING oppure no ROWS\RANGE del tutto (che utilizzerà anche il RANGE bobina su disco). Quanto sopra avrà una durata complessiva e un modo inferiori meno I/O, anche se il piano sembra leggermente più complesso (un operatore di progetto di sequenza aggiuntivo).

Di recente ho pubblicato un post sul blog che illustra alcune differenze di prestazioni che ho osservato per uno specifico scenario di totali parziali:

http://www.sqlperformance.com/2012/07 /t-query-sql/totali-esecuzione