6

Tenemos varias máquinas que registran datos en una base de datos a intervalos esporádicos. Para cada registro, me gustaría obtener el período de tiempo entre esta grabación y grabación previa.Optimizar ROW_NUMBER() en SQL Server

que pueda hacer esto utilizando ROW_NUMBER de la siguiente manera:

WITH TempTable AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY Machine_ID ORDER BY Date_Time) AS Ordering 
    FROM dbo.DataTable 
) 

SELECT [Current].*, Previous.Date_Time AS PreviousDateTime 
FROM TempTable AS [Current] 
INNER JOIN TempTable AS Previous 
    ON [Current].Machine_ID = Previous.Machine_ID 
    AND Previous.Ordering = [Current].Ordering + 1 

El problema es, que va realmente lenta (varios minutos sobre una mesa con unos 10k entradas) - He intentado crear índices del separados en Machine_ID y Date_Time, y un único índice unido, pero nada ayuda.

¿Hay alguna forma de reescribir esta consulta para ir más rápido?

Respuesta

5

¿Cómo se compara a esta versión ?:

SELECT x.* 
    ,(SELECT MAX(Date_Time) 
     FROM dbo.DataTable 
     WHERE Machine_ID = x.Machine_ID 
      AND Date_Time < x.Date_Time 
    ) AS PreviousDateTime 
FROM dbo.DataTable AS x 

¿O esta versión ?:

SELECT x.* 
    ,triang_join.PreviousDateTime 
FROM dbo.DataTable AS x 
INNER JOIN (
    SELECT l.Machine_ID, l.Date_Time, MAX(r.Date_Time) AS PreviousDateTime 
    FROM dbo.DataTable AS l 
    LEFT JOIN dbo.DataTable AS r 
    ON l.Machine_ID = r.Machine_ID 
     AND l.Date_Time > r.Date_Time 
    GROUP BY l.Machine_ID, l.Date_Time 
) AS triang_join 
ON triang_join.Machine_ID = x.Machine_ID 
    AND triang_join.Date_Time = x.Date_Time 

Ambos funcionarían mejor con un índice en Machine_ID, Date_Time y para obtener resultados correctos, supongo que esto es único.

No ha mencionado lo que está oculto en * y que a veces puede significar mucho, ya que un Machine_ID, el índice Date_Time generalmente no cubrirá y si tiene muchas columnas allí o tienen una gran cantidad de datos, ...

+0

La segunda consulta finaliza en segundos en lugar de en minutos, pero la primera consulta se ejecuta más rápido de lo que puedo cronometrarla. ¡Perfecto gracias! –

7

La partición y el orden ROW_NUMBER dado() requieren un índice en (Machine_ID, Date_Time) para satisfacer en una sola pasada:

CREATE INDEX idxMachineIDDateTime ON DataTable (Machine_ID, Date_Time); 

índices separados sobre Machine_ID y Date_Time ayudarán poca, o ninguna.

+0

Como dije, también creé ese índice, y no mejoró en absoluto el rendimiento de la consulta. –

+4

Eso es porque * activa el punto de inflexión del índice. Restrinqúelo solo a las columnas necesarias y utilice include para hacer que el índice no agrupado cubra. Si se necesitan demasiadas columnas, debe cambiarse a un índice agrupado, con todas las consecuencias. –

+0

Parece que está en lo cierto, eliminando * disminuye el tiempo de la consulta en solo unos segundos. No puedo imaginarme por qué sucedería esto: ¿podría proporcionar algún vínculo sobre qué es * un punto de inflexión del índice *? –

0

¿Qué sucede si utiliza un activador para almacenar la última marca de tiempo y resta cada vez para obtener la diferencia?

+0

Desafortunadamente, son datos históricos, y no siempre se agregan en orden. –

2

Tuve algunos problemas de rendimiento extraños al usar CTE en SQL Server 2005. En muchos casos, reemplazar el CTE con una tabla de temperatura real resolvió el problema.

Intentaré esto antes de ir más allá con el uso de un CTE.

Nunca encontré ninguna explicación para los problemas de rendimiento que he visto, y realmente no tuve tiempo de profundizar en las causas. Sin embargo, siempre sospeché que el motor no podía optimizar el CTE de la misma manera que puede optimizar una tabla temporal (que puede indexarse ​​si se necesita más optimización).

actualización

Después de su comentario de que esta es una visión, lo primero que pondría a prueba la consulta con una tabla temporal para ver si eso funciona mejor.

Si lo hace, y usar un proceso almacenado no es una opción, puede considerar convertir el CTE actual en una vista indexada/materializada. Deberá leer sobre el tema antes de seguir por este camino, ya que si esta es una buena idea depende de muchos factores, entre ellos la frecuencia con la que se actualizan los datos.

+0

¿Cómo haría eso? ¿Debería reemplazar la vista con un Sproc (ya que las vistas no pueden tener variables)? –

+0

Sí, no estaba seguro de que fuera una opinión de su pregunta. Ver la actualización de mi respuesta (seguirá en unos minutos). –

0

Si necesita estos datos con frecuencia, en lugar de calcularlos cada vez que extrae los datos, ¿por qué no agrega una columna y la calcula/rellena cada vez que se agrega una fila?

(índice compuesto de Remus hará que la consulta rápida;. Ejecutarlo sola vez debería hacerlo más rápido todavía)

4

Si el número de filas en dbo.DataTable es grande, entonces es probable que usted está experimentando la problema debido a que el CTE se une a sí mismo.Hay una publicación en el blog que explica el problema con algún detalle here

De vez en cuando, en tales casos he recurrido a crear una tabla temporal para insertar el resultado de la consulta CTE y luego hacer las uniones contra esa tabla temporal (aunque esto por lo general ha sido para los casos en que sea necesario un gran número de combinaciones en contra de la tabla temporal - en el caso de una sola unirse a la diferencia de rendimiento será menos notable)

+1

En segundo lugar este enfoque. Los CTE son simplemente reescrituras en línea. Al igual que repetir tu propio código y auto unir, no hay nada para garantizar que el optimizador lo distribuirá en una tabla temporal. Si coloca elementos en su propia tabla, puede elegir índices y/o evitar el doble trabajo. Una vez dicho esto, utilizo CTE donde el mantenimiento del código es importante y donde el esquema es susceptible de cambiar muy rápidamente (o en vistas, como en este caso). –

Cuestiones relacionadas