sql >> Base de Datos >  >> RDS >> Sqlserver

La partición da como resultado una consulta de totales acumulados

Si no necesita ALMACENAR los datos (que no debería, porque necesita actualizar los totales acumulados cada vez que se cambia, agrega o elimina una fila), y si no confía en la peculiar actualización (que no debería, porque no se garantiza que funcione y su comportamiento podría cambiar con una revisión, un paquete de servicio, una actualización o incluso un índice subyacente o un cambio de estadísticas), puede probar este tipo de consulta en tiempo de ejecución. Este es un método que el compañero MVP Hugo Kornelis acuñó como "iteración basada en conjuntos" (publicó algo similar en uno de sus capítulos de Inmersiones profundas de MVP de SQL Server ). Dado que los totales acumulados generalmente requieren un cursor sobre todo el conjunto, una actualización peculiar sobre todo el conjunto o una única autounión no lineal que se vuelve cada vez más costosa a medida que aumenta el número de filas, el truco aquí es recorrer algunos finitos elemento en el conjunto (en este caso, el "rango" de cada fila en términos de mes, para cada usuario, y procesa solo cada rango una vez para todas las combinaciones de usuario/mes en ese rango, por lo que en lugar de recorrer 200,000 filas, puedes repetir hasta 24 veces).

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;

Resultados:

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

Por supuesto que puedes actualice la tabla base desde esta variable de tabla, pero ¿por qué molestarse, ya que esos valores almacenados solo son válidos hasta la próxima vez que una instrucción DML toque la tabla?

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

Dado que no confiamos en pedidos implícitos de ningún tipo, esto es 100% compatible y merece una comparación de rendimiento en relación con la actualización peculiar no compatible. Incluso si no lo supera pero se acerca, debería considerar usarlo de todos modos en mi humilde opinión.

En cuanto a la solución SQL Server 2012, Matt menciona RANGE pero dado que este método usa un carrete en disco, también debe probar con ROWS en lugar de simplemente ejecutar con RANGE . Aquí hay un ejemplo rápido para su 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];

Compare esto con RANGE UNBOUNDED PRECEDING o no ROWS\RANGE en absoluto (que también utilizará el RANGE bobina en disco). Lo anterior tendrá menor duración general y forma menos E/S, aunque el plan parece un poco más complejo (un operador de proyecto de secuencia adicional).

Recientemente publiqué una publicación de blog que describe algunas diferencias de rendimiento que observé para un escenario específico de totales acumulados:

http://www.sqlperformance.com/2012/07 /t-sql-queries/totales acumulados