2011-10-07 8 views
5

Tengo una consulta que se está tomando un tiempo muy serio para ejecutar en algo más antiguo que el pasado, por ejemplo, las horas de datos. Esto creará una vista que se utilizará para la minería de datos, por lo que las expectativas son que podría buscar semanas o meses de datos y regresar en un tiempo razonable (incluso un par de minutos está bien ... funcionó para un rango de fechas de 10/3/2011 12:00pm a 10/3/2011 1:00pm y tomó 44 minutos!)acelerar SQL Query

El problema es con los dos LEFT OUTER JOIN s en la parte inferior. Cuando los saco, puede funcionar en unos 10 segundos. Sin embargo, esos son el pan y la mantequilla de esta consulta.

Todo esto proviene de una sola tabla. Lo ÚNICO que esta consulta devuelve de manera diferente a la tabla original es la columna xweb_range. xweb_range es una columna de campo calculado (gama), que sólo utilizará los valores de [LO,LC,RO,RC]_Avg donde su correspondiente [LO,LC,RO,RC]_Sensor_Alarm = 0 (no se incluyen en el cálculo de la distancia si la alarma del sensor = 1)

WITH Alarm (sub_id, 
LO_Avg, LO_Sensor_Alarm, LC_Avg, LC_Sensor_Alarm, RO_Avg, RO_Sensor_Alarm, RC_Avg, RC_Sensor_Alarm) AS (
SELECT sub_id, LO_Avg, LO_Sensor_Alarm, LC_Avg, LC_Sensor_Alarm, RO_Avg, RO_Sensor_Alarm, RC_Avg, RC_Sensor_Alarm 
FROM dbo.some_table 
where sub_id <> '0' 
) 
, AddRowNumbers AS (
SELECT rowNumber = ROW_NUMBER() OVER (ORDER BY LO_Avg) 
    , sub_id 
    , LO_Avg, LO_Sensor_Alarm 
    , LC_Avg, LC_Sensor_Alarm 
    , RO_Avg, RO_Sensor_Alarm 
    , RC_Avg, RC_Sensor_Alarm 
FROM Alarm 
) 
, UnPivotColumns AS (
SELECT rowNumber, value = LO_Avg FROM AddRowNumbers WHERE LO_Sensor_Alarm = 0 
UNION ALL SELECT rowNumber, LC_Avg FROM AddRowNumbers WHERE LC_Sensor_Alarm = 0 
UNION ALL SELECT rowNumber, RO_Avg FROM AddRowNumbers WHERE RO_Sensor_Alarm = 0 
UNION ALL SELECT rowNumber, RC_Avg FROM AddRowNumbers WHERE RC_Sensor_Alarm = 0 
) 
SELECT rowNumber.sub_id 
    , cds.equipment_id 
    , cds.read_time 
    , cds.LC_Avg 
    , cds.LC_Dev 
    , cds.LC_Ref_Gap 
    , cds.LC_Sensor_Alarm 
    , cds.LO_Avg 
    , cds.LO_Dev 
    , cds.LO_Ref_Gap 
    , cds.LO_Sensor_Alarm 
    , cds.RC_Avg 
    , cds.RC_Dev 
    , cds.RC_Ref_Gap 
    , cds.RC_Sensor_Alarm 
    , cds.RO_Avg 
    , cds.RO_Dev 
    , cds.RO_Ref_Gap 
    , cds.RO_Sensor_Alarm 
    , COALESCE(range1.range, range2.range) AS xweb_range 
FROM AddRowNumbers rowNumber 
    LEFT OUTER JOIN (SELECT rowNumber, range = MAX(value) - MIN(value) FROM UnPivotColumns GROUP BY rowNumber HAVING COUNT(*) > 1) range1 ON range1.rowNumber = rowNumber.rowNumber 
    LEFT OUTER JOIN (SELECT rowNumber, range = AVG(value) FROM UnPivotColumns  GROUP BY rowNumber HAVING COUNT(*) = 1) range2 ON range2.rowNumber = rowNumber.rowNumber 
    INNER JOIN dbo.some_table cds 
    ON rowNumber.sub_id = cds.sub_id 
+1

creo que esto es una cuestión aceptable. Sin embargo, debe publicar su esquema de tabla e información sobre los índices que ha configurado. – jadarnel27

+6

La primera pregunta más obvia: ** ¿Hay un índice en 'Rownumber INCLUDE (value)'? ** – JNK

+2

Además, sabes que 'CTE's son solo vistas desechables, ¿no? No hay beneficio de rendimiento? Básicamente tiene 3 niveles de vistas anidadas que está agregando aquí ... – JNK

Respuesta

2

Es difícil de entender exactamente lo que su consulta es tratando de hacer sin saber el dominio. Sin embargo, me parece que su consulta simplemente intenta encontrar, para cada fila en dbo.some_table donde sub_id no es 0, el rango de las siguientes columnas en el registro (o, si solo coincide uno, ese valor único):

  • LO_AVG cuando LO_SENSOR_ALARM = 0
  • LC_AVG cuando LC_SENSOR_ALARM = 0
  • RO_AVG cuando RO_SENSOR_ALARM = 0
  • RC_AVG cuando RC_SENSOR_ALARM = 0

Se construyó este qu Al asignar a cada fila un número de fila secuencial, las columnas _AVG sin anotación junto con su número de fila, calcularon la agrupación agregada de rango por número de fila y luego volvieron a unir los registros originales por número de fila. Los CTE no materializan los resultados (ni están indexados, como se comenta en los comentarios). Por lo tanto, cada referencia a AddRowNumbers es costosa, porque ROW_NUMBER() OVER (ORDER BY LO_Avg) es un género.

En lugar de cortar esta tabla para arriba apenas para unirse de nuevo juntos por el número de fila, por qué no hacer algo como:

SELECT cds.sub_id 
    , cds.equipment_id 
    , cds.read_time 
    , cds.LC_Avg 
    , cds.LC_Dev 
    , cds.LC_Ref_Gap 
    , cds.LC_Sensor_Alarm 
    , cds.LO_Avg 
    , cds.LO_Dev 
    , cds.LO_Ref_Gap 
    , cds.LO_Sensor_Alarm 
    , cds.RC_Avg 
    , cds.RC_Dev 
    , cds.RC_Ref_Gap 
    , cds.RC_Sensor_Alarm 
    , cds.RO_Avg 
    , cds.RO_Dev 
    , cds.RO_Ref_Gap 
    , cds.RO_Sensor_Alarm 

    --if the COUNT is 0, xweb_range will be null (since MAX will be null), if it's 1, then use MAX, else use MAX - MIN (as per your example) 
    , (CASE WHEN stats.[Count] < 2 THEN stats.[MAX] ELSE stats.[MAX] - stats.[MIN] END) xweb_range 

FROM dbo.some_table cds 

    --cross join on the following table derived from values in cds - it will always contain 1 record per row of cds 
    CROSS APPLY 
    (
     SELECT COUNT(*), MIN(Value), MAX(Value) 
     FROM 
     (
      --construct a table using the column values from cds we wish to aggregate 
      VALUES (LO_AVG, LO_SENSOR_ALARM), 
        (LC_AVG, LC_SENSOR_ALARM), 
        (RO_AVG, RO_SENSORALARM), 
        (RC_AVG, RC_SENSOR_ALARM) 


     ) x (Value, Sensor_Alarm) --give a name to the columns for _AVG and _ALARM 
     WHERE Sensor_Alarm = 0 --filter our constructed table where _ALARM=0 

    ) stats([Count], [Min], [Max]) --give our derived table and its columns some names 

WHERE cds.sub_id <> '0' --this is a filter carried over from the first CTE in your example 
+0

Gracias por su esfuerzo en esto. Me gustaría probar este método, sin embargo, estoy recibiendo un error cerca de VALUES. No estoy seguro de la sintaxis correcta para esa parte del código ya que nunca he visto algo como lo que estás haciendo. ¿Cómo se llama esta técnica? – Tom

+0

¿Está usando SQL Server 2008? La palabra clave 'VALUES' es el Constructor de valores de tabla. La documentación está aquí: http://technet.microsoft.com/en-us/library/dd776382.aspx y puede ver un uso similar como una tabla derivada en una cláusula 'FROM' en el Ejemplo C. –

+0

Además, si 'no puede usar 'VALUES', puede deberse a que su nivel de compatibilidad SQL se haya establecido en algún valor distinto de 100 (SQL 2008). También puede reemplazar 'VALUES ...' con 'SELECT LO_AVG, LO_SENSOR_ALARM UNION ALL SELECT LC_AVG, LC_SENSOR_ALARM UNION ALL ...' –