Multiple Case When 会降低查询性能

Multiple Case When slows down query performance

我有以下查询,其中每个 'AttributeId' 列来自另一个 table 并被聚合。添加这些语句会显着增加时间复杂度。如果我对 TSQL 不是很精通,如果有任何改进性能的建议,我将不胜感激。

查询:

SELECT t1.ServiceWaittime,
       t1.Starttime,
       t1.Endtime,
       t1.prevEndTime,
       CONVERT(float, t1.Starttime - t1.prevEndTime) / 24.0 / 60.0 AS continuityDuration,
       t1.Duration,
       t1.MainCpseId,
       t1.Yield,
       t1.Scrap,
       t1.MachineYield,
       ISNULL((t1.MachineYield / NULLIF(t1.Duration, 0)), 0) AS Geschwindigkeit,
       t1.Te,
       t1.Tr,
       t1.CalendarWeek,
       t1.InterruptionTriggerAttributeId,
       t1.ReasonId,
       t1.InterruptionName,
       t1.ReasonName,
       CASE
            WHEN 1 = 1 THEN ISNULL((SELECT AVG(Value)
                                    FROM CpseEventLogger
                                    WHERE AttributeId = 4
                                      AND MainCpseId = 12
                                      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                                   0)
       END AS Sys_Speed_Value, --statement1
       CASE
            WHEN 1 = 1 THEN ISNULL((SELECT COUNT(*)
                                    FROM CpseEventLogger
                                    WHERE AttributeId = 103
                                      AND MainCpseId = 12
                                      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                                   0)
       END AS attr_103, --statement2
       CASE
            WHEN 1 = 1 THEN ISNULL((SELECT COUNT(*)
                                    FROM CpseEventLogger
                                    WHERE AttributeId = 292
                                      AND MainCpseId = 12
                                      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                                   0)
       END AS attr_292, --statement3
       CASE
            WHEN 1 = 1 THEN ISNULL((SELECT COUNT(*)
                                    FROM CpseEventLogger
                                    WHERE AttributeId = 293
                                      AND MainCpseId = 12
                                      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                                   0)
       END AS attr_293, --statement4
       CASE
            WHEN 1 = 1 THEN ISNULL((SELECT COUNT(*)
                                    FROM CpseEventLogger
                                    WHERE AttributeId = 294
                                      AND MainCpseId = 12
                                      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                                   0)
       END AS attr_294, --statement5
       CASE
            WHEN 1 = 1 THEN ISNULL((SELECT COUNT(*)
                                    FROM CpseEventLogger
                                    WHERE AttributeId = 8159
                                      AND MainCpseId = 12
                                      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                                   0)
       END AS attr_8159, --statement6
       CASE
            WHEN 1 = 1 THEN ISNULL((SELECT COUNT(*)
                                    FROM CpseEventLogger
                                    WHERE AttributeId = 8175
                                      AND MainCpseId = 12
                                      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                                   0)
       END AS attr_8175, --statement7
       CASE
            WHEN 1 = 1 THEN ISNULL((SELECT COUNT(*)
                                    FROM CpseEventLogger
                                    WHERE AttributeId = 8186
                                      AND MainCpseId = 12
                                      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                                   0)
       END AS attr_8186, --statement8
       CASE
            WHEN 1 = 1 THEN ISNULL((SELECT COUNT(*)
                                    FROM CpseEventLogger
                                    WHERE AttributeId = 8208
                                      AND MainCpseId = 12
                                      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                                   0)
       END AS attr_8208, --statement9
       CASE
            WHEN 1 = 1 THEN ISNULL((SELECT COUNT(*)
                                    FROM CpseEventLogger
                                    WHERE AttributeId = 8209
                                      AND MainCpseId = 12
                                      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                                   0)
       END AS attr_8209 --statement10

FROM (SELECT t1.TimeType,
             t1.ServiceWaittime,
             t1.Starttime,
             t1.Endtime,
             LAG(t1.Endtime) OVER (PARTITION BY t1.MainCpseId ORDER BY t1.Starttime ASC) AS prevEndTime,
             t1.Duration,
             t1.MainCpseId,
             t1.Yield,
             t1.Scrap,
             t1.MachineYield,
             t1.Te,
             t1.Tr,
             t1.CalendarWeek,
             t1.InterruptionTriggerAttributeId,
             t1.ReasonId,
             t2.Name AS InterruptionName,
             t3.Name AS ReasonName
      FROM CpseProcessLogger t1
           LEFT JOIN AttributeType t2 ON t1.InterruptionTriggerAttributeId = t2.Id
           LEFT JOIN Reason t3 ON t1.ReasonId = t3.Id
      WHERE 1 = 1
        AND TimeType IN ('UNT')
        AND MainCpseId = 12) t1
ORDER BY t1.Starttime ASC;

根据@larnu 的建议,猜测速度变慢的原因是读取 CpseEventLogger table,您可以 LEFT OUTER JOIN 到那个 table 并在主查询中执行一次聚合。这看起来像:

SELECT t1.ServiceWaittime,
       t1.Starttime,
       t1.Endtime,
       t1.prevEndTime,
       CONVERT(float, t1.Starttime - t1.prevEndTime) / 24.0 / 60.0 AS continuityDuration,
       t1.Duration,
       t1.MainCpseId,
       t1.Yield,
       t1.Scrap,
       t1.MachineYield,
       ISNULL((t1.MachineYield / NULLIF(t1.Duration, 0)), 0) AS Geschwindigkeit,
       t1.Te,
       t1.Tr,
       t1.CalendarWeek,
       t1.InterruptionTriggerAttributeId,
       t1.ReasonId,
       t1.InterruptionName,
       t1.ReasonName,
       Avg(CASE WHEN CpseEventLogger.AttributeId = 4 THEN CpseEventLogger.Value) AS Sys_Speed_Value,
       COUNT(CASE WHEN CpseEventLogger.AttributeId = 103 THENCpseEventLogger.Value) AS attr_103, --statement2
       COUNT(CASE WHEN CpseEventLogger.AttributeId = 292 THENCpseEventLogger.Value) AS attr_292, --statement3
       COUNT(CASE WHEN CpseEventLogger.AttributeId = 293 THENCpseEventLogger.Value) AS attr_293, --statement4
       COUNT(CASE WHEN CpseEventLogger.AttributeId = 294 THENCpseEventLogger.Value) AS attr_294, --statement5
       COUNT(CASE WHEN CpseEventLogger.AttributeId = 8159 THENCpseEventLogger.Value) AS attr_8159, --statement6
       COUNT(CASE WHEN CpseEventLogger.AttributeId = 8175 THENCpseEventLogger.Value) AS attr_8175, --statement7
       COUNT(CASE WHEN CpseEventLogger.AttributeId = 8186 THENCpseEventLogger.Value) AS attr_8186, --statement8
       COUNT(CASE WHEN CpseEventLogger.AttributeId = 8208 THENCpseEventLogger.Value) AS attr_8208, --statement9
       COUNT(CASE WHEN CpseEventLogger.AttributeId = 8209 THENCpseEventLogger.Value) AS attr_8209 --statement10

FROM (SELECT t1.TimeType,
             t1.ServiceWaittime,
             t1.Starttime,
             t1.Endtime,
             LAG(t1.Endtime) OVER (PARTITION BY t1.MainCpseId ORDER BY t1.Starttime ASC) AS prevEndTime,
             t1.Duration,
             t1.MainCpseId,
             t1.Yield,
             t1.Scrap,
             t1.MachineYield,
             t1.Te,
             t1.Tr,
             t1.CalendarWeek,
             t1.InterruptionTriggerAttributeId,
             t1.ReasonId,
             t2.Name AS InterruptionName,
             t3.Name AS ReasonName
      FROM CpseProcessLogger t1
           LEFT JOIN AttributeType t2 ON t1.InterruptionTriggerAttributeId = t2.Id
           LEFT JOIN Reason t3 ON t1.ReasonId = t3.Id
      WHERE 1 = 1
        AND TimeType IN ('UNT')
        AND MainCpseId = 12) t1
    LEFT OUTER JOIN CpseEventLogger
        ON CpseEventLogger.MainCpseId = 12
        AND CpseEvevntLogger.AttributeId IN (4,103,292,293,294,8159,8175,8186,8208,8209)
        AND CpseEventLogger.Timestamp BETWEEN t1.Starttime AND t1.Endtime
GROUP BY 
       t1.ServiceWaittime,
       t1.Starttime,
       t1.Endtime,
       t1.prevEndTime,
       continuityDuration,
       t1.Duration,
       t1.MainCpseId,
       t1.Yield,
       t1.Scrap,
       t1.MachineYield,
       Geschwindigkeit,
       t1.Te,
       t1.Tr,
       t1.CalendarWeek,
       t1.InterruptionTriggerAttributeId,
       t1.ReasonId,
       t1.InterruptionName,
       t1.ReasonName,
ORDER BY t1.Starttime ASC;

关于我对过度使用 case 表达式的评论,您的原始查询具有以下形式:

   CASE
        WHEN 1 = 1 THEN ISNULL((SELECT AVG(Value)
                                FROM CpseEventLogger
                                WHERE AttributeId = 4
                                  AND MainCpseId = 12
                                  AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
                               0)
   END AS Sys_Speed_Value, --statement1

这很奇怪,因为我们使用 case 表达式仅在条件为 true 时执行 code/logic。您的条件是 1=1,它始终为真。只有 when 而没有 else 所以这与 100% 相同:

   ISNULL((SELECT AVG(Value)
           FROM CpseEventLogger
           WHERE AttributeId = 4
                 AND MainCpseId = 12
                 AND Timestamp BETWEEN t1.Starttime AND t1.Endtime),
           0)

另一种选择 APPLY 对您的情况可能更容易。 (OUTER APPLY 类似于 LEFT JOINCROSS APPLY 类似于 INNER JOIN)。

您可以在子查询中使用条件聚合。

SELECT t1.ServiceWaittime,
       t1.Starttime,
       t1.Endtime,
       t1.prevEndTime,
       CONVERT(float, t1.Starttime - t1.prevEndTime) / 24.0 / 60.0 AS continuityDuration,
       t1.Duration,
       t1.MainCpseId,
       t1.Yield,
       t1.Scrap,
       t1.MachineYield,
       ISNULL((t1.MachineYield / NULLIF(t1.Duration, 0)), 0) AS Geschwindigkeit,
       t1.Te,
       t1.Tr,
       t1.CalendarWeek,
       t1.InterruptionTriggerAttributeId,
       t1.ReasonId,
       t1.InterruptionName,
       t1.ReasonName,
       el.Sys_Speed_Value, --statement1
       el.attr_103, --statement2
       el.attr_292, --statement3
       el.attr_293, --statement4
       el.attr_294, --statement5
       el.attr_8159, --statement6
       el.attr_8175, --statement7
       el.attr_8186, --statement8
       el.attr_8208, --statement9
       el.attr_8209 --statement10

FROM (
      SELECT t1.TimeType,
             t1.ServiceWaittime,
             t1.Starttime,
             t1.Endtime,
             LAG(t1.Endtime) OVER (PARTITION BY t1.MainCpseId ORDER BY t1.Starttime ASC) AS prevEndTime,
             t1.Duration,
             t1.MainCpseId,
             t1.Yield,
             t1.Scrap,
             t1.MachineYield,
             t1.Te,
             t1.Tr,
             t1.CalendarWeek,
             t1.InterruptionTriggerAttributeId,
             t1.ReasonId,
             t2.Name AS InterruptionName,
             t3.Name AS ReasonName
      FROM CpseProcessLogger t1
           LEFT JOIN AttributeType t2 ON t1.InterruptionTriggerAttributeId = t2.Id
           LEFT JOIN Reason t3 ON t1.ReasonId = t3.Id
      WHERE 1 = 1
        AND TimeType IN ('UNT')
        AND MainCpseId = 12
) t1
CROSS APPLY (
    SELECT
      ISNULL(AVG(CASE WHEN AttributeId = 4 THEN Value END), 0) AS Sys_Speed_Value,
      COUNT(CASE WHEN AttributeId = 103 THEN 1 END) AS attr_103,
      COUNT(CASE WHEN AttributeId = 292 THEN 1 END) AS attr_292,
      COUNT(CASE WHEN AttributeId = 293 THEN 1 END) AS attr_293,
      COUNT(CASE WHEN AttributeId = 294 THEN 1 END) AS attr_294,
      COUNT(CASE WHEN AttributeId = 8159 THEN 1 END) AS attr_8159,
      COUNT(CASE WHEN AttributeId = 8175 THEN 1 END) AS attr_8175,
      COUNT(CASE WHEN AttributeId = 8186 THEN 1 END) AS attr_8186,
      COUNT(CASE WHEN AttributeId = 8208 THEN 1 END) AS attr_8208,
      COUNT(CASE WHEN AttributeId = 8209 THEN 1 END) AS attr_8209
    FROM CpseEventLogger
    WHERE MainCpseId = 12
      AND Timestamp BETWEEN t1.Starttime AND t1.Endtime
) el
ORDER BY t1.Starttime ASC;

要使此查询正常执行,您需要以下索引:

CpseEventLogger (MainCpseId, Timestamp) INCLUDE (AttributeId, Value)
CpseProcessLogger (MainCpseId, TimeType, InterruptionTriggerAttributeId) INCLUDE (
  ReasonId, .... othercolumns)
AttributeType (Id) INCLUDE (Name)
Reason (Id) INCLUDE (Name)

或者您可以使用聚簇索引(所有 non-key 列都是 INCLUDE),而不是具有 INCLUDE 列的 non-clustered 索引。

您可能想要交换索引中的 InterruptionTriggerAttributeIdReasonId 位置