Я хочу повысить производительность представления SQL, в котором есть несколько объединений и операторов case - PullRequest
2 голосов
/ 08 апреля 2020

На мой взгляд, у меня есть несколько объединений, и одно из них связано со многими ко многим. Поэтому, когда я присоединяюсь к этой таблице, количество записей резко увеличивается, и для ее решения я использовал инструкцию case, которая помогает в фильтрации нежелательных записей. Но вся эта фильтрация нежелательных записей создает очень медленный запрос.

Вот базовая c структура моего запроса.

> WITH PeriodCTE   AS (     SELECT PeriodId,            StartTime,          Name,
>           LEAD(StartTime, 1) OVER (ORDER BY TimePeriodId) [EndTime]   FROM
> Period WHERE Active = 1 )
> 
> Select * From (Select 
>         Id, 
>         Name, 
>         Period,
>         CASE      WHEN CAST(B.OpenTimestamp AS TIME) BETWEEN '00:00:00.0000000'           
>                            AND (SELECT MIN(P.Starttime) FROM Period P 
>                            INNER JOIN DepartmentPeriod DP ON P.PeriodId = DP.PeriodId 
>                            WHERE DepartmentId = B.DepartmentId) 
>                               AND CTE.StartTime = (SELECT MAX(T.Starttime) FROM 
>                                                      Period P 
>                                                    INNER JOIN 
>                                                      DepartmentPeriod DP ON 
>                                                      P.PeriodId = DP.PeriodId 
>                                                    WHERE DepartmentId = B.DepartmentId) 
>                   Then 1
>                   When CAST(B.OpenTimestamp AS TIME) BETWEEN CTE.StartTime 
>                       AND '23:59:59.9999999' AND CTE.StartTime = 
>                            (SELECT MAX(P.Starttime) FROM Period P 
>                                 INNER JOIN DepartmentPeriod DP ON 
>                                 P.TimePeriodId = DP.TimePeriodId 
>                             WHERE DepartmentId = C.DepartmentId) 
>                   Then 1
>                   WHEN CAST(B.OpenTimestamp AS TIME) BETWEEN CTE.StartTime AND CTE.EndTime 
>                   THEN 1
>                   WHEN DP.DepartmentId NOT IN (SELECT DISTINCT(DepartmentId) FROM DepartmentPeriod) 
>                   THEN 1
>                   WHEN CTE.StartTime IS NULL
>                   THEN 1          
>          END AS [Flag] 
>          From Bill AS B 
>          LEFT OUTER JOIN Department AS D ON B.DepartmentId = D.DepartmentId 
>          LEFT OUTER JOIN DepartmentPeriod AS DP ON D.DepartmentId = D.DepartmentId 
>          LEFT OUTER JOIN PeriodCTE AS CTE ON P.PeriodId = DP.PeriodId ) AS X 
> Where Flag = 1

Есть ли предложения по улучшению производительности мой запрос.

1 Ответ

2 голосов
/ 15 апреля 2020

Во-первых, обратите внимание, что ваш запрос, как есть, всегда будет возвращать 1 для соответствующих строк независимо от того, что. Как есть, вы можете изменить код на [flag] = 1. Тем не менее, пока я просто проигнорирую это.

Далее, хорошо, что ваш код отформатирован организованно. Не только для облегчения отладки и обслуживания, но и для лучших ответов, если вы разместите код на этих форумах. Я привел его в порядок, чтобы лучше объяснить свой ответ и помочь другим обеспечить более качественные ответы:

WITH PeriodCTE AS 
(
  SELECT -- 1. You want an index to handle the sort for TimePeriodId
    PeriodId, StartTime, Name, LEAD(StartTime, 1) OVER (ORDER BY TimePeriodId) AS [EndTime]
  FROM  [Period]
  WHERE Active = 1
) -- INDEX: CREATE NONCLUSTERED INDEX <name> ON [Period](TimePeriodId) WHERE Active = 1;
SELECT *
FROM
(
  SELECT
    Id, 
    Name, 
    Period,
    [Flag] =
      CASE 
        WHEN CAST(B.OpenTimestamp AS TIME) BETWEEN '00:00:00.0000000' AND (
                  SELECT MIN(P.Starttime)
                  FROM   Period           AS P 
                  JOIN   DepartmentPeriod AS DP
                    ON   P.PeriodId = DP.PeriodId 
                  WHERE  DepartmentId = B.DepartmentId)
          AND CTE.StartTime = (
                  SELECT MAX(T.Starttime)
                  FROM   Period           AS P 
                  JOIN   DepartmentPeriod AS DP
                  ON     P.PeriodId = DP.PeriodId 
                  WHERE  DepartmentId = B.DepartmentId)
        THEN 1
        WHEN CAST(B.OpenTimestamp AS TIME) BETWEEN CTE.StartTime AND '23:59:59.9999999'
          AND CTE.StartTime = (
                  SELECT MAX(P.Starttime)
                  FROM   Period           AS P 
                  JOIN   DepartmentPeriod AS DP
                    ON   P.TimePeriodId = DP.TimePeriodId 
                  WHERE DepartmentId = C.DepartmentId)
        THEN 1
        WHEN CAST(B.OpenTimestamp AS TIME) BETWEEN CTE.StartTime AND CTE.EndTime
        THEN 1
        WHEN DP.DepartmentId NOT IN (SELECT /*DISTINCT*/DepartmentId FROM DepartmentPeriod) -- 2. You don't need DISTINCT Here, it causes a needless sort
        THEN 1
        WHEN CTE.StartTime IS NULL
        THEN 1
      END
  FROM      Bill             AS B
  LEFT JOIN Department       AS D   ON B.DepartmentId = D.DepartmentId 
  LEFT JOIN DepartmentPeriod AS DP  ON D.DepartmentId = D.DepartmentId 
  LEFT JOIN PeriodCTE        AS CTE ON P.PeriodId = DP.PeriodId
) AS x
WHERE Flag = 1;

Без DDL или плана выполнения трудно помочь, но вот некоторые незначительные изменения плода. Для вашего первого CTE против [Period] вы хотите, чтобы индекс поддерживал этот запрос:

CREATE NONCLUSTERED INDEX <name> ON [Period](TimePeriodId, StartTime)
INCLUDE (PeriodId, Name)
WHERE Active = 1;

или ...

CREATE NONCLUSTERED INDEX <name> ON [Period](TimePeriodId)
INCLUDE (PeriodId, StartTime, Name)
WHERE Active = 1;

Далее вы можете потерять DISTINCT в этом подзапросе:

WHEN DP.DepartmentId NOT IN (SELECT /*DISTINCT*/DepartmentId FROM DepartmentPeriod)

Это не меняет ответ, но оставляя DISTINCT, может привести к тому, что оптимизатор будет без необходимости сортировать эти строки.

Наконец - у вас есть ТРИ коррелированных подзапроса , также известный как треугольное соединение . ЭТО ТО, ЧТО НАИБОЛЕЕ Дробление ВАША ДЕЯТЕЛЬНОСТЬ. Есть много способов реорганизовать этот код, но это может быть сложно. Простой способ улучшить производительность этих коррелированных подзапросов - превратить этот коррелированный подзапрос в индексированное представление, если это возможно. Логика представления c будет выглядеть следующим образом:

CREATE {viewname} WITH SCHEMABINDING AS
SELECT {Unique key or composite key}, P.Starttime
FROM   dbo.[Period]         AS P 
JOIN   dbo.DepartmentPeriod AS DP
  ON   P.PeriodId   = DP.PeriodId
WHERE  DepartmentId = B.DepartmentId;

Индекс будет представлять собой уникальную комбинацию значений:

CREATE UNIQUE CLUSTERED INDEX {indexname} ON {yourview}(Starttime, {unique key(s)});

Тогда в вашем запросе вы найдете три места где этот код существует:

SELECT MIN(V.Starttime)
FROM   dbo.[Period]         AS P 
JOIN   dbo.DepartmentPeriod AS DP
  ON   P.PeriodId   = DP.PeriodId
WHERE  DepartmentId = B.DepartmentId

и измените его на ...

SELECT MIN(V.Starttime)
FROM   {your new indexed view}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...