TLDR
Это ошибка, которая была исправлена в CU8 , поэтому установка хотя бы этого CU и, в идеале, самого последнего исправит его.
Pre SQL Server 2017
В SQL Server 2016 план выглядит так, как указано выше.IN
обрабатывается так же, как и EXISTS
, поэтому он оценивает следующие три столбца:
CASE WHEN IsPrior = 1 AND EXISTS (SELECT * FROM ForSubQuery WHERE SecID = MainTable.SecurityID) THEN 1 ELSE 0 END AS IsPriorAfter
CASE WHEN IsIdeal = 1 AND EXISTS (SELECT * FROM ForSubQuery WHERE SecID = MainTable.SecurityID) THEN 1 ELSE 0 END AS IsIdealAfter
CASE WHEN IsCurrent = 1 AND EXISTS (SELECT * FROM ForSubQuery WHERE SecID = MainTable.SecurityID) THEN 1 ELSE 0 END AS IsCurrentAfter
Каждый экземпляр подзапроса получает свой собственный оператор в плане, и запрос возвращает правильный результат, но это subОптимальный, поскольку идентичный подзапрос может быть выполнен от до три раза в строке.
Поскольку каждый подзапрос имеет AND
рядом с ним, SQL Server может пропустить оценку подзапроса, если результатом этого выражения является ложь.Это достигается каждым вложенным циклом, содержащим предикат прохода.Например, выражение, соответствующее оценке IsPriorAfter
, имеет предикат сквозного доступа IsFalseOrNull (IsPrior=1)
IsPrior=1
- логическое выражение, которое может возвращать false
, null
или true
.IsFalseOrNull
затем инвертирует результат и возвращает 1
для false
, null
и 0
для true
.Таким образом, предикат сквозной передачи оценивается как true
/ 1
, если IsPrior
отличается от 1
(включая NULL
), и затем пропускает выполнение подзапроса.
SQLRTM Server 2017
В SQL Server 2017 введено новое правило оптимизации CollapseIdenticalScalarSubquery
.В RTM-версии план выполнения неверен.
План задач
Подзапростеперь в одном операторе и сквозных предикатах объединяются
IsFalseOrNull([IsCurrent]=(1)) OR IsFalseOrNull([IsIdeal]=(1)) OR IsFalseOrNull([IsPrior]=(1))
Однако это условие неверно!Он оценивается как true
, если все три из IsPrior
, IsIdeal
, IsCurrent
не равны 1
.
Таким образом, в вашем случае подзапрос выполняется только один раз (для первой строки таблицы - где все три столбца равны 1).
Для двух других строк этодолжен быть выполнен, но это не так.Вложенные циклы имеют столбец зонда, который устанавливается на 1
, если коррелированный подзапрос возвращает строку.(Помечено Expr1016
в плане).Когда выполнение пропускается, для этого столбца зонда устанавливается значение NULL
. Окончательный вычисляемый скаляр в плане имеет следующее выражение.Если Expr1016
равно null
, это означает 0
для всех трех ваших вычисляемых столбцов с использованием CASE
.
[Expr1005] = Scalar Operator(CASE WHEN [IsPrior]=(1) AND [Expr1016] THEN (1) ELSE (0) END),
[Expr1009] = Scalar Operator(CASE WHEN [IsIdeal]=(1) AND [Expr1016] THEN (1) ELSE (0) END),
[Expr1013] = Scalar Operator(CASE WHEN [IsCurrent]=(1) AND [Expr1016] THEN (1) ELSE (0) END)
SQL Server 2017 исправлен
Окончательный фиксированный план после применения CU имеет ту же форму плана, что и план RTM на 2017 год (с подзапросом, появляющимся только один раз), но предикат сквозной передачи теперь равен
IsFalseOrNull([IsCurrent]=(1)) AND IsFalseOrNull([IsIdeal]=(1)) AND IsFalseOrNull([IsPrior]=(1))
Это оценивается только true
если нет из этих столбцов имеют значение 1
, поэтому подзапрос теперь вычисляется точно при необходимости.