Tenho a seguinte entrada:
id | value
----+-------
1 | 136
2 | NULL
3 | 650
4 | NULL
5 | NULL
6 | NULL
7 | 954
8 | NULL
9 | 104
10 | NULL
Espero o seguinte resultado:
id | value
----+-------
1 | 136
2 | 136
3 | 650
4 | 650
5 | 650
6 | 650
7 | 954
8 | 954
9 | 104
10 | 104
A solução trivial seria juntar as tabelas com uma <
relação e, em seguida, selecionar o MAX
valor em a GROUP BY
:
WITH tmp AS (
SELECT t2.id, MAX(t1.id) AS lastKnownId
FROM t t1, t t2
WHERE
t1.value IS NOT NULL
AND
t2.id >= t1.id
GROUP BY t2.id
)
SELECT
tmp.id, t.value
FROM t, tmp
WHERE t.id = tmp.lastKnownId;
No entanto, a execução trivial desse código criaria internamente o quadrado da contagem das linhas da tabela de entrada ( O(n^2) ). Eu esperava que o t-sql o otimizasse - em um nível de bloco/registro, a tarefa a ser feita é muito fácil e linear, essencialmente um loop for ( O(n) ).
No entanto, em meus experimentos, o MS SQL 2016 mais recente não pode otimizar essa consulta corretamente, impossibilitando a execução dessa consulta para uma tabela de entrada grande.
Além disso, a consulta precisa ser executada rapidamente, tornando inviável uma solução baseada em cursor igualmente fácil (mas muito diferente).
Usar alguma tabela temporária com suporte de memória pode ser um bom compromisso, mas não tenho certeza se pode ser executado significativamente mais rápido, considerando que minha consulta de exemplo usando subconsultas não funcionou.
Também estou pensando em desenterrar algumas funções de janelas dos documentos do t-sql, o que poderia ser enganado para fazer o que eu quero. Por exemplo, soma cumulativa está fazendo algo muito semelhante, mas não consegui enganá-lo para fornecer o elemento não nulo mais recente, e não a soma dos elementos anteriores.
A solução ideal seria uma consulta rápida sem código de procedimento ou tabelas temporárias. Alternativamente, também uma solução com tabelas temporárias é aceitável, mas iterar a tabela proceduralmente não é.
Uma solução comum para este tipo de problema é dada por Itzik Ben-Gan em seu artigo The Last non NULL Puzzle :
Demonstração: db<>fiddle
Essa não é a consulta que você escreveu. Pode não ser equivalente à consulta que você escreveu, dependendo de alguns detalhes menores do esquema da tabela. Você está esperando demais do otimizador de consultas.
Com a indexação correta, você pode obter o algoritmo que procura através do seguinte T-SQL:
Para cada linha, o processador de consultas percorre o índice para trás e para quando encontra uma linha com um valor não nulo para
[VALUE]
. Na minha máquina, isso termina em cerca de 90 segundos para 100 milhões de linhas na tabela de origem. A consulta é executada por mais tempo do que o necessário porque algum tempo é desperdiçado no cliente descartando todas essas linhas.Não está claro para mim se você precisa de resultados ordenados ou o que planeja fazer com um conjunto de resultados tão grande. A consulta pode ser ajustada para atender ao cenário real. A maior vantagem dessa abordagem é que ela não requer uma classificação no plano de consulta. Isso pode ajudar para conjuntos de resultados maiores. Uma desvantagem é que o desempenho não será ótimo se houver muitos NULLs na tabela porque muitas linhas serão lidas do índice e descartadas. Você deve conseguir melhorar o desempenho com um índice filtrado que exclua NULLs para esse caso.
Dados de amostra para o teste:
Um método, usando
OVER()
andMAX()
eCOUNT()
com base nessa fonte , pode ser:Resultado
Outro método baseado nesta fonte , intimamente relacionado com o primeiro exemplo