Esse é um tipo de tarefa trivial no meu mundo natal em C #, mas ainda não o faço em SQL e prefere resolvê-lo com base em conjunto (sem cursores). Um conjunto de resultados deve vir de uma consulta como esta.
SELECT SomeId, MyDate,
dbo.udfLastHitRecursive(param1, param2, MyDate) as 'Qualifying'
FROM T
Como deve funcionar
Envio esses três parâmetros para uma UDF.
O UDF usa internamente parâmetros para buscar linhas relacionadas <= 90 dias mais antigas, de uma exibição.
O UDF percorre 'MyDate' e retorna 1 se for incluído em um cálculo total.
Caso contrário, retornará 0. Nomeado aqui como "qualificado".
O que o udf fará
Listar as linhas em ordem de data. Calcular os dias entre as linhas. A primeira linha do conjunto de resultados é padronizada como Hit = 1. Se a diferença for de até 90, - passe para a próxima linha até que a soma das lacunas seja de 90 dias (o 90º dia deve passar) Quando atingido, defina Hit como 1 e redefina o intervalo como 0 Também funcionaria para omitir a linha do resultado.
|(column by udf, which not work yet)
Date Calc_date MaxDiff | Qualifying
2014-01-01 11:00 2014-01-01 0 | 1
2014-01-03 10:00 2014-01-01 2 | 0
2014-01-04 09:30 2014-01-03 1 | 0
2014-04-01 10:00 2014-01-04 87 | 0
2014-05-01 11:00 2014-04-01 30 | 1
Na tabela acima, a coluna MaxDiff é o intervalo da data na linha anterior. O problema com minhas tentativas até agora é que não posso ignorar a segunda última linha da amostra acima.
[EDIT]
Conforme comentário, adiciono uma tag e colo o udf que compilei agora. Porém, é apenas um espaço reservado e não dará resultados úteis.
;WITH cte (someid, otherkey, mydate, cost) AS
(
SELECT someid, otherkey, mydate, cost
FROM dbo.vGetVisits
WHERE someid = @someid AND VisitCode = 3 AND otherkey = @otherkey
AND CONVERT(Date,mydate) = @VisitDate
UNION ALL
SELECT top 1 e.someid, e.otherkey, e.mydate, e.cost
FROM dbo.vGetVisits AS E
WHERE CONVERT(date, e.mydate)
BETWEEN DateAdd(dd,-90,CONVERT(Date,@VisitDate)) AND CONVERT(Date,@VisitDate)
AND e.someid = @someid AND e.VisitCode = 3 AND e.otherkey = @otherkey
AND CONVERT(Date,e.mydate) = @VisitDate
order by e.mydate
)
Eu tenho outra consulta que eu defino separadamente, que é mais próxima do que eu preciso, mas bloqueada pelo fato de não poder calcular em colunas com janelas. Eu também tentei um similar que fornece mais ou menos a mesma saída apenas com um LAG () sobre MyDate, cercado por um datado.
SELECT
t.Mydate, t.VisitCode, t.Cost, t.SomeId, t.otherkey, t.MaxDiff, t.DateDiff
FROM
(
SELECT *,
MaxDiff = LAST_VALUE(Diff.Diff) OVER (
ORDER BY Diff.Mydate ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM
(
SELECT *,
Diff = ISNULL(DATEDIFF(DAY, LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate),0),
DateDiff = ISNULL(LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate)
FROM dbo.vGetVisits AS r
WHERE r.VisitCode = 3 AND r.SomeId = @SomeID AND r.otherkey = @otherkey
) AS Diff
) AS t
WHERE t.VisitCode = 3 AND t.SomeId = @SomeId AND t.otherkey = @otherkey
AND t.Diff <= 90
ORDER BY
t.Mydate ASC;
fonte
Respostas:
Enquanto leio a pergunta, o algoritmo recursivo básico necessário é:
Isso é relativamente fácil de implementar com uma expressão de tabela comum recursiva.
Por exemplo, usando os seguintes dados de amostra (com base na pergunta):
O código recursivo é:
Os resultados são:
Com um índice tendo
TheDate
como chave principal, o plano de execução é muito eficiente:Você pode optar por agrupar isso em uma função e executá-lo diretamente contra a visão mencionada na pergunta, mas meus instintos são contra. Geralmente, o desempenho é melhor quando você seleciona linhas de uma visualização em uma tabela temporária, fornece o índice apropriado na tabela temporária e aplica a lógica acima. Os detalhes dependem dos detalhes da exibição, mas esta é minha experiência geral.
Para completar (e solicitado pela resposta do ypercube), devo mencionar que minha outra solução para esse tipo de problema (até o T-SQL obter as funções de conjunto ordenadas apropriadas) é um cursor SQLCLR ( consulte minha resposta aqui para obter um exemplo da técnica ) Isso tem um desempenho muito melhor do que um cursor T-SQL e é conveniente para aqueles com habilidades em linguagens .NET e a capacidade de executar o SQLCLR em seu ambiente de produção. Pode não oferecer muito nesse cenário em relação à solução recursiva, porque a maior parte do custo é do tipo, mas vale a pena mencionar.
fonte
Uma vez que este é uma pergunta do SQL Server 2014, também posso adicionar uma versão de procedimento armazenado compilada nativamente de um "cursor".
Tabela de origem com alguns dados:
Um tipo de tabela que é o parâmetro para o procedimento armazenado. Ajuste
bucket_count
adequadamente .E um procedimento armazenado que percorre o parâmetro com valor de tabela e coleta as linhas em
@R
.Código para preencher uma variável de tabela otimizada para memória usada como parâmetro para o procedimento armazenado compilado nativamente e chame o procedimento
Resultado:
Atualizar:
Se, por algum motivo, você não precisar visitar todas as linhas da tabela, poderá fazer o equivalente à versão "pular para a próxima data" implementada no CTE recursivo de Paul White.
O tipo de dados não precisa da coluna ID e você não deve usar um índice de hash.
E o procedimento armazenado usa a
select top(1) ..
para encontrar o próximo valor.fonte
T.TheDate >= dateadd(day, 91, @CurDate)
tudo ficaria bem certo?TheDate
paraTType
paraDate
.Uma solução que usa um cursor.
(primeiro, algumas tabelas e variáveis necessárias) :
O cursor real:
E obtendo os resultados:
Testado no SQLFiddle
fonte
INSERT @cd
apenas quando@Qualify=1
(e, portanto, não inserindo 13 milhões de linhas se não precisar de todas elas na saída). E a solução depende de encontrar um índiceTheDate
. Se não houver, não será eficiente.Resultado
Veja também Como calcular o total em execução no SQL Server
atualização: veja abaixo os resultados dos testes de desempenho.
Devido à lógica diferente usada para encontrar "intervalo de 90 dias", a ypercube e minhas soluções, se deixadas intactas, podem retornar resultados diferentes à solução de Paul White. Isso se deve ao uso de DATEDIFF e DATEADD funções respectivamente.
Por exemplo:
retorna '2014-04-01 00: 00: 00.000', o que significa que '2014-04-01 01: 00: 00.000' está além do intervalo de 90 dias
mas
Retorna '90', o que significa que ainda está dentro da lacuna.
Considere um exemplo de varejista. Nesse caso, vender um produto perecível que tenha sido vendido por data '01-01-2014' em '01-01-2014 23: 59: 59: 999' está correto. Portanto, o valor DATEDIFF (DIA, ...) neste caso é OK.
Outro exemplo é um paciente esperando para ser visto. Para alguém que chega em '2014-01-01 00: 00: 00: 000' e sai em '2014-01-01 23: 59: 59: 999', são 0 (zero) dias se DATEDIFF for usado, mesmo que o a espera real foi de quase 24 horas. Novamente, o paciente que chega em '2014-01-01 23:59:59' e sai em '2014-01-02 00:00:01' esperou um dia se DATEDIFF for usado.
Mas eu discordo.
Deixei as soluções DATEDIFF e até o desempenho as testou, mas elas realmente deveriam estar em sua própria liga.
Também foi observado que, para os grandes conjuntos de dados, é impossível evitar valores no mesmo dia. Portanto, se dissermos 13 milhões de registros com 2 anos de dados, teremos mais de um registro por alguns dias. Esses registros estão sendo filtrados na primeira oportunidade nas soluções DATEDIFF da minha e da ypercube. Espero que ypercube não se importe com isso.
As soluções foram testadas na tabela a seguir
com dois índices agrupados diferentes (mydate neste caso):
A tabela foi preenchida da seguinte maneira
Para um caso de milhões de linhas, o INSERT foi alterado de tal maneira que entradas de 0 a 20 minutos foram adicionadas aleatoriamente.
Todas as soluções foram cuidadosamente agrupadas no código a seguir
Códigos reais testados (em nenhuma ordem específica):
Solução DATEDIFF da Ypercube ( YPC, DATEDIFF )
Solução DATEADD da Ypercube ( YPC, DATEADD )
A solução de Paul White ( PW )
Minha solução DATEADD ( PN, DATEADD )
Minha solução DATEDIFF ( PN, DATEDIFF )
Estou usando o SQL Server 2012, então peço desculpas a Mikael Eriksson, mas seu código não será testado aqui. Eu ainda esperaria que suas soluções com DATADIFF e DATEADD retornassem valores diferentes em alguns conjuntos de dados.
E os resultados reais são:
fonte
Ok, eu perdi alguma coisa ou por que você não pulava a recursão e voltava para si mesmo? Se a data for a chave primária, ela deverá ser única e, em ordem cronológica, se você planeja calcular o deslocamento para a próxima linha
Rendimentos
A menos que eu tenha perdido totalmente algo importante ...
fonte
WHERE [TheDate] > [T1].[TheDate]
para levar em conta o limite de diferença de 90 dias. Mas ainda assim, sua saída não é a desejada.