Estoy tratando de calcular el total acumulado. Pero debería restablecerse cuando la suma acumulativa sea mayor que otro valor de columna
create table #reset_runn_total
(
id int identity(1,1),
val int,
reset_val int,
grp int
)
insert into #reset_runn_total
values
(1,10,1),
(8,12,1),(6,14,1),(5,10,1),(6,13,1),(3,11,1),(9,8,1),(10,12,1)
SELECT Row_number()OVER(partition BY grp ORDER BY id)AS rn,*
INTO #test
FROM #reset_runn_total
Detalles del índice:
CREATE UNIQUE CLUSTERED INDEX ix_load_reset_runn_total
ON #test(rn, grp)
Data de muestra
+----+-----+-----------+-----+
| id | val | reset_val | Grp |
+----+-----+-----------+-----+
| 1 | 1 | 10 | 1 |
| 2 | 8 | 12 | 1 |
| 3 | 6 | 14 | 1 |
| 4 | 5 | 10 | 1 |
| 5 | 6 | 13 | 1 |
| 6 | 3 | 11 | 1 |
| 7 | 9 | 8 | 1 |
| 8 | 10 | 12 | 1 |
+----+-----+-----------+-----+
Resultado Esperado
+----+-----+-----------------+-------------+
| id | val | reset_val | Running_tot |
+----+-----+-----------------+-------------+
| 1 | 1 | 10 | 1 |
| 2 | 8 | 12 | 9 | --1+8
| 3 | 6 | 14 | 15 | --1+8+6 -- greater than reset val
| 4 | 5 | 10 | 5 | --reset
| 5 | 6 | 13 | 11 | --5+6
| 6 | 3 | 11 | 14 | --5+6+3 -- greater than reset val
| 7 | 9 | 8 | 9 | --reset -- greater than reset val
| 8 | 10 | 12 | 10 | --reset
+----+-----+-----------------+-------------+
Consulta:
Obtuve el resultado usando Recursive CTE
. La pregunta original está aquí /programming/42085404/reset-running-total-based-on-another-column
;WITH cte
AS (SELECT rn,id,
val,
reset_val,
grp,
val AS running_total,
Iif (val > reset_val, 1, 0) AS flag
FROM #test
WHERE rn = 1
UNION ALL
SELECT r.*,
Iif(c.flag = 1, r.val, c.running_total + r.val),
Iif(Iif(c.flag = 1, r.val, c.running_total + r.val) > r.reset_val, 1, 0)
FROM cte c
JOIN #test r
ON r.grp = c.grp
AND r.rn = c.rn + 1)
SELECT *
FROM cte
¿Hay alguna alternativa mejor T-SQL
sin usar CLR
?
50000
grupos con60
identificaciones . entonces el conteo total de registros estará alrededor3000000
. Estoy seguro deRecursive CTE
que no escalará bien3000000
. Actualizará las métricas cuando regrese a la oficina. ¿Podemos lograr esto usandosum()Over(Order by)
como lo ha usado en este artículo sqlperformance.com/2012/07/t-sql-queries/running-totalsRespuestas:
He analizado problemas similares y nunca he podido encontrar una solución de función de ventana que haga un solo paso sobre los datos. No creo que sea posible. Las funciones de ventana deben poder aplicarse a todos los valores de una columna. Eso hace que los cálculos de reinicio como este sean muy difíciles, porque un reinicio cambia el valor de todos los siguientes valores.
Una forma de pensar sobre el problema es que puede obtener el resultado final que desea si calcula un total acumulado básico siempre que pueda restar el total acumulado de la fila anterior correcta. Por ejemplo, en sus datos de muestra, el valor de
id
4 es elrunning total of row 4 - the running total of row 3
. El valor deid
6 esrunning total of row 6 - the running total of row 3
porque todavía no se ha reiniciado. El valor deid
7 es elrunning total of row 7 - the running total of row 6
y así sucesivamente.Enfocaría esto con T-SQL en un bucle. Me dejé llevar y creo que tengo una solución completa. Durante 3 millones de filas y 500 grupos, el código terminó en 24 segundos en mi escritorio. Estoy probando con SQL Server 2016 Developer Edition con 6 vCPU. Aprovecho las inserciones paralelas y la ejecución paralela en general, por lo que es posible que deba cambiar el código si tiene una versión anterior o tiene limitaciones de DOP.
Debajo del código que usé para generar los datos. Los rangos en
VAL
yRESET_VAL
deben ser similares a sus datos de muestra.El algoritmo es como sigue:
1) Comience insertando todas las filas con un total acumulado estándar en una tabla temporal.
2) En un bucle:
2a) Para cada grupo, calcule la primera fila con un total acumulado por encima del valor reset_valor restante en la tabla y almacene la identificación, el total acumulado que era demasiado grande y el total acumulado anterior que era demasiado grande en una tabla temporal.
2b) Eliminar filas de la primera tabla temporal en una tabla temporal de resultados que tenga un valor
ID
menor o igual alID
de la segunda tabla temporal. Use las otras columnas para ajustar el total acumulado según sea necesario.3) Después de que la eliminación ya no procese filas, ejecute un adicional
DELETE OUTPUT
en la tabla de resultados. Esto es para filas al final del grupo que nunca exceden el valor de reinicio.Revisaré una implementación del algoritmo anterior en T-SQL paso a paso.
Comience creando algunas tablas temporales.
#initial_results
contiene los datos originales con el total acumulado estándar,#group_bookkeeping
se actualiza cada ciclo para determinar qué filas se pueden mover y#final_results
contiene los resultados con el total acumulado ajustado para restablecimientos.Luego creo el índice agrupado en la tabla temporal para que la inserción y la creación del índice se puedan hacer en paralelo. Hice una gran diferencia en mi máquina, pero puede que no en la tuya. La creación de un índice en la tabla de origen no pareció ayudar, pero eso podría ayudar en su máquina.
El siguiente código se ejecuta en el bucle y actualiza la tabla de contabilidad. Para cada grupo, necesitamos obtener el máximo de búsqueda
ID
que se debe mover a la tabla de resultados. Necesitamos el total acumulado de esa fila para poder restarlo del total acumulado inicial. Lagrp_done
columna se establece en 1 cuando no hay más trabajo que hacer para agrp
.Realmente no soy fanático de la
LOOP JOIN
pista en general, pero esta es una consulta simple y fue la forma más rápida de obtener lo que quería. Para optimizar realmente el tiempo de respuesta, quería combinaciones de bucles anidados paralelos en lugar de combinaciones de combinación DOP 1.El siguiente código se ejecuta en el bucle y mueve los datos de la tabla inicial a la tabla de resultados final. Observe el ajuste al total acumulado inicial.
Para su comodidad, a continuación se encuentra el código completo:
fuente
Recursive CTE
tomó 2 minutos y 15 segundosUsando un CURSOR:
Consulte aquí: http://rextester.com/WSPLO95303
fuente
No en ventana, sino pura versión SQL:
No soy especialista en dialecto de SQL Server. Esta es una versión inicial para PostrgreSQL (si entiendo correctamente no puedo usar LIMIT 1 / TOP 1 en la parte recursiva en SQL Server):
fuente
grp
columna.Parece que tiene varias consultas / métodos para atacar el problema, pero no nos ha proporcionado, ¿ni siquiera lo ha considerado? - los índices sobre la mesa.
¿Qué índices hay en la tabla? ¿Es un montón o tiene un índice agrupado?
Probaría las diversas soluciones sugeridas después de agregar este índice:
O simplemente cambie (o cree) que el índice agrupado sea
(grp, id)
.Tener un índice que se dirija a la consulta específica debería mejorar la eficiencia de la mayoría, si no de todos los métodos.
fuente