Gostaria de saber se existe alguma maneira de reescrever a EXPRESSÃO na consulta (apenas expressão, não a consulta inteira) para a avaliação inútil de curto-circuito da fase THEN?
Dados de demonstração:
CREATE TABLE #Docs (
ID INT NOT NULL
,DocType TINYINT NOT NULL
);
CREATE TABLE #DocsItems (
IDDocs INT NOT NULL
,Amount NUMERIC(19,6)
);
INSERT INTO #Docs(ID, DocType) VALUES(1,1),(2,1),(3,2),(4,2),(5,2),(6,2);
INSERT INTO #DocsItems(IDDocs,Amount) VALUES(3,50.),(3,25.),(3,33.),(4,44.),(4,123.),(6,11.);
Consulta do assunto:
SELECT
-- expression
SumAmount = CASE
WHEN D.DocType <> 1 THEN (SELECT SUM(Amount) FROM #DocsItems WHERE IDDocs = D.ID)
END
FROM #Docs D
WHERE D.DocType = 1 -- so CASE condition evaluates to False
Se eu reescrever a consulta (de propósito) para:
SELECT
-- expression
SumAmount = CASE
WHEN 2 = 1 /* rewrite*/ THEN (SELECT SUM(Amount) FROM #DocsItems WHERE IDDocs = D.ID)
END
FROM #Docs D
WHERE D.DocType = 1
O plano segue do jeito que deveria/poderia/faria na consulta original:
O que está acontecendo atualmente
Ao executar sua consulta, os operadores table scan, stream agg e compute scalar não são avaliados em tempo de execução.
Por que está acontecendo
A junção NL de aplicação significa que, para cada linha em
#Docs
, retorne uma linha#Docsitems
que corresponda ao predicado. Este predicado deve serWHERE IDDocs = D.ID
Mas o operador escalar de cálculo (
EXPR1007
) próximo ao select (a instrução case real) está chamando a função escalar no lado interno do NL apply (EXPR1005
) somente quandoDoctype <> 1
. Isso, como você sabe, não pode acontecer, e ambos retornamNULL
.Calcular escalar entre NL e SELECT:
Calcular escalar no lado interno da junção:
Tudo isso parece ser devido a como a instrução CASE funciona e remove literais. (diferença entre
CASE 2 = 1
vs.CASE WHEN D.DocType <> 1
)Resolvendo
Se você alterar a consulta para isso:
Você deve obter o plano de execução desejado:
Removendo a regra SELonLOJ que está alterando o plano na reescrita.
Adivinhação
A reversão da regra que foi aplicada para se aproximar da consulta CASE WHEN pode ser feita adicionando a dica:
OPTION( QUERYRULEOFF SELonLOJ )
Mostrando a mesma situação (menos filtro e junção esquerda) que a instrução case, mas a eliminação do tempo de execução não está ocorrendo.
&
Outra regra que parece se aproximar ao
case when
desligá-lo éJoinPredNorm
Com a filtragem acontecendo na tabela #Docs conforme o esperado.