Estou tentando calcular o total em execução. Mas deve ser redefinido quando a soma cumulativa for maior que outro valor de coluna
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
Detalhes do índice:
CREATE UNIQUE CLUSTERED INDEX ix_load_reset_runn_total
ON #test(rn, grp)
dados de amostra
+----+-----+-----------+-----+
| 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:
Eu obtive o resultado usando Recursive CTE
. A pergunta original está aqui https://stackoverflow.com/questions/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
Existe alguma alternativa melhor T-SQL
sem usar CLR
.?
Eu olhei para problemas semelhantes e nunca consegui encontrar uma solução de função de janela que faça uma única passagem pelos dados. Eu não acho que seja possível. As funções de janela precisam poder ser aplicadas a todos os valores em uma coluna. Isso torna os cálculos de redefinição muito difíceis, porque uma redefinição altera o valor de todos os valores a seguir.
Uma maneira de pensar sobre o problema é que você pode obter o resultado final desejado se calcular um total básico de corrida, desde que possa subtrair o total de corrida da linha anterior correta. Por exemplo, em seus dados de amostra, o valor para
id
4 é orunning total of row 4 - the running total of row 3
. O valor paraid
6 érunning total of row 6 - the running total of row 3
porque uma reinicialização ainda não aconteceu. O valor paraid
7 é orunning total of row 7 - the running total of row 6
e assim por diante.Eu abordaria isso com T-SQL em um loop. Eu me empolguei um pouco e acho que tenho uma solução completa. Para 3 milhões de linhas e 500 grupos, o código terminou em 24 segundos na minha área de trabalho. Estou testando com o SQL Server 2016 Developer Edition com 6 vCPU. Estou aproveitando as inserções paralelas e a execução paralela em geral, então talvez seja necessário alterar o código se estiver em uma versão mais antiga ou tiver limitações de DOP.
Abaixo o código que usei para gerar os dados. Os intervalos em
VAL
eRESET_VAL
devem ser semelhantes aos seus dados de amostra.O algoritmo é o seguinte:
1) Comece inserindo todas as linhas com um total de execução padrão em uma tabela temporária.
2) Em um loop:
2a) Para cada grupo, calcule a primeira linha com um total em execução acima do valor_redefinido restante na tabela e armazene o id, o total em execução que era muito grande e o total em execução anterior que era muito grande em uma tabela temporária.
2b) Exclua linhas da primeira tabela temporária em uma tabela temporária de resultados que tenha um
ID
valor menor ou igual aID
na segunda tabela temporária. Use as outras colunas para ajustar o total em execução conforme necessário.3) Após a exclusão não processar mais as linhas, execute um adicional
DELETE OUTPUT
na tabela de resultados. Isso é para linhas no final do grupo que nunca excedem o valor de redefinição.Vou passar por uma implementação do algoritmo acima em T-SQL passo a passo.
Comece criando algumas tabelas temporárias.
#initial_results
mantém os dados originais com o total em execução padrão,#group_bookkeeping
é atualizado a cada loop para descobrir quais linhas podem ser movidas e#final_results
contém os resultados com o total em execução ajustado para redefinições.Eu crio o índice clusterizado na tabela temporária depois para que a inserção e a construção do índice possam ser feitas em paralelo. Fez uma grande diferença na minha máquina, mas pode não fazer na sua. Criar um índice na tabela de origem não pareceu ajudar, mas pode ajudar na sua máquina.
O código abaixo é executado no loop e atualiza a tabela de contabilidade. Para cada grupo, precisamos encontrar o máximo
ID
que deve ser movido para a tabela de resultados. Precisamos do total acumulado dessa linha para que possamos subtraí-lo do total acumulado inicial. Agrp_done
coluna é definida como 1 quando não há mais trabalho a fazer para um arquivogrp
.Realmente não sou fã da
LOOP JOIN
dica em geral, mas essa é uma consulta simples e foi a maneira mais rápida de conseguir o que eu queria. Para realmente otimizar o tempo de resposta, eu queria junções de loop aninhadas paralelas em vez de junções de mesclagem DOP 1.O código abaixo é executado no loop e move os dados da tabela inicial para a tabela de resultados finais. Observe o ajuste no total inicial.
Para sua comodidade, segue abaixo o código completo:
Usando um CURSOR:
Confira aqui: http://rextester.com/WSPLO95303
Não em janela, mas em versão SQL pura:
Não sou especialista no dialeto do SQL Server. Esta é uma versão inicial para PostrgreSQL (se entendi bem não consigo usar LIMIT 1 / TOP 1 na parte recursiva no SQL Server):
Parece que você tem várias consultas/métodos para atacar o problema, mas não nos forneceu - ou sequer considerou? - os índices na tabela.
Quais índices existem na tabela? É um heap ou tem um índice clusterizado?
Eu tentaria as várias soluções sugeridas depois de adicionar este índice:
Ou apenas altere (ou faça) o índice clusterizado para ser
(grp, id)
.Ter um índice que tenha como alvo a consulta específica deve melhorar a eficiência - da maioria, se não de todos os métodos.