Este es un tipo de tarea trivial en mi mundo natal de C #, pero todavía no lo hago en SQL y preferiría resolverlo basado en conjuntos (sin cursores). Un conjunto de resultados debe provenir de una consulta como esta.
SELECT SomeId, MyDate,
dbo.udfLastHitRecursive(param1, param2, MyDate) as 'Qualifying'
FROM T
¿Cómo debería funcionar?
Envío esos tres parámetros a un UDF.
La UDF utiliza internamente parámetros para obtener filas relacionadas <= 90 días más antiguas, desde una vista.
El UDF atraviesa 'MyDate' y devuelve 1 si debe incluirse en un cálculo total.
Si no debería, entonces devuelve 0. Nombrado aquí como "calificado".
Lo que hará la udf
Liste las filas en orden de fecha. Calcule los días entre filas. La primera fila en el conjunto de resultados por defecto es Hit = 1. Si la diferencia es de hasta 90, - pase a la siguiente fila hasta que la suma de las brechas sea de 90 días (el día 90 debe pasar) Cuando se alcanza, establezca Hit en 1 y restablezca la brecha a 0 También funcionaría en su lugar omitir la fila del 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
En la tabla anterior, la columna MaxDiff es el intervalo desde la fecha en la línea anterior. El problema con mis intentos hasta ahora es que no puedo ignorar la segunda última fila de la muestra anterior.
[EDITAR]
Según el comentario, agrego una etiqueta y también pego el udf que he compilado en este momento. Sin embargo, es solo un marcador de posición y no dará resultados útiles.
;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
)
Tengo otra consulta que defino por separado que está más cerca de lo que necesito, pero bloqueada con el hecho de que no puedo calcular en columnas con ventanas. También probé uno similar que da más o menos la misma salida solo con un LAG () sobre MyDate, rodeado de un fechado.
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;
fuente
Respuestas:
Mientras leo la pregunta, el algoritmo recursivo básico requerido es:
Esto es relativamente fácil de implementar con una expresión de tabla común recursiva.
Por ejemplo, utilizando los siguientes datos de muestra (basados en la pregunta):
El código recursivo es:
Los resultados son:
Con un índice
TheDate
como clave principal, el plan de ejecución es muy eficiente:Puede elegir incluir esto en una función y ejecutarlo directamente contra la vista mencionada en la pregunta, pero mis instintos están en contra. Por lo general, el rendimiento es mejor cuando selecciona filas de una vista en una tabla temporal, proporciona el índice apropiado en la tabla temporal y luego aplica la lógica anterior. Los detalles dependen de los detalles de la vista, pero esta es mi experiencia general.
Para completar (e impulsado por la respuesta de ypercube), debo mencionar que mi otra solución para este tipo de problema (hasta que T-SQL obtenga las funciones de conjunto ordenadas adecuadas) es un cursor SQLCLR ( vea mi respuesta aquí para ver un ejemplo de la técnica ) Esto funciona mucho mejor que un cursor T-SQL, y es conveniente para aquellos con habilidades en lenguajes .NET y la capacidad de ejecutar SQLCLR en su entorno de producción. Puede que no ofrezca mucho en este escenario sobre la solución recursiva porque la mayoría del costo es del tipo, pero vale la pena mencionarlo.
fuente
Como se trata de una pregunta de SQL Server 2014, también podría agregar una versión de procedimiento almacenado compilada de forma nativa de un "cursor".
Tabla fuente con algunos datos:
Un tipo de tabla que es el parámetro del procedimiento almacenado. Ajuste el
bucket_count
apropiado .Y un procedimiento almacenado que recorre el parámetro con valores de tabla y recoge las filas
@R
.Código para llenar una variable de tabla de memoria optimizada que se utiliza como parámetro para el procedimiento almacenado compilado de forma nativa y llamar al procedimiento.
Resultado:
Actualizar:
Si por alguna razón no necesita visitar todas las filas de la tabla, puede hacer el equivalente de la versión "saltar a la próxima fecha" que Paul White implementa en el CTE recursivo.
El tipo de datos no necesita la columna ID y no debe usar un índice hash.
Y el procedimiento almacenado utiliza a
select top(1) ..
para encontrar el siguiente valor.fuente
T.TheDate >= dateadd(day, 91, @CurDate)
todo, estaría bien, ¿verdad?TheDate
inTType
aDate
.Una solución que usa un cursor.
(primero, algunas tablas y variables necesarias) :
El cursor real:
Y obteniendo los resultados:
Probado en SQLFiddle
fuente
INSERT @cd
solo cuando@Qualify=1
(y así no insertar 13M filas si no las necesita todas en la salida). Y la solución depende de encontrar un índice enTheDate
. Si no hay uno, no será eficiente.Resultado
También eche un vistazo a Cómo calcular el total acumulado en SQL Server
actualización: vea a continuación los resultados de las pruebas de rendimiento.
Debido a la lógica diferente utilizada para encontrar el "intervalo de 90 días", los ypercube y mis soluciones si se dejan intactos pueden devolver resultados diferentes a la solución de Paul White. Esto se debe al uso de las funciones DATEDIFF y DATEADD respectivamente.
Por ejemplo:
devuelve '2014-04-01 00: 00: 00.000', lo que significa que '2014-04-01 01: 00: 00.000' tiene más de 90 días de diferencia
pero
Devuelve '90', lo que significa que todavía está dentro del espacio.
Considere un ejemplo de un minorista. En este caso, está bien vender un producto perecedero que tenga fecha de vencimiento '2014-01-01' en '2014-01-01 23: 59: 59: 999'. Entonces el valor DATEDIFF (DÍA, ...) en este caso está bien.
Otro ejemplo es un paciente que espera ser visto. Para alguien que viene en '2014-01-01 00: 00: 00: 000' y se va en '2014-01-01 23: 59: 59: 999' son 0 (cero) días si se usa DATEDIFF aunque La espera real fue de casi 24 horas. Una vez más, el paciente que llega a '2014-01-01 23:59:59' y se retira a '2014-01-02 00:00:01' esperó un día si se usa DATEDIFF.
Pero yo divago.
Dejé las soluciones DATEDIFF e incluso el rendimiento las probó, pero realmente deberían estar en su propia liga.
También se observó que para los grandes conjuntos de datos es imposible evitar los valores del mismo día. Entonces, si tenemos 13 millones de registros que abarcan 2 años de datos, terminaremos teniendo más de un registro durante algunos días. Esos registros se filtran lo antes posible en las soluciones DATEDIFF de mi y ypercube. Espero que a Ypercube no le importe esto.
Las soluciones se probaron en la siguiente tabla
con dos índices agrupados diferentes (mydate en este caso):
La tabla se rellenó de la siguiente manera
Para un caso de filas multimillonarias, INSERT se cambió de tal manera que se agregaron aleatoriamente entradas de 0-20 minutos.
Todas las soluciones fueron cuidadosamente envueltas en el siguiente código
Códigos reales probados (sin ningún orden en particular):
Solución DATEDIFF de Ypercube ( YPC, DATEDIFF )
Solución DATEADD de Ypercube ( YPC, DATEADD )
La solución de Paul White ( PW )
Mi solución DATEADD ( PN, DATEADD )
Mi solución DATEDIFF ( PN, DATEDIFF )
Estoy usando SQL Server 2012, así que pido disculpas a Mikael Eriksson, pero su código no se probará aquí. Todavía esperaría que sus soluciones con DATADIFF y DATEADD devuelvan valores diferentes en algunos conjuntos de datos.
Y los resultados reales son:
fuente
Ok, ¿me perdí algo o por qué no te saltearías la recursión y te unirías a ti mismo? Si la fecha es la clave principal, debe ser única y en orden cronológico si planea calcular el desplazamiento a la siguiente fila
Rendimientos
A menos que me haya perdido por completo algo importante ...
fuente
WHERE [TheDate] > [T1].[TheDate]
para tener en cuenta el umbral de diferencia de 90 días. Pero aún así, su salida no es la deseada.