Na preparação da minha pergunta anterior do Constant Scan, eu estava experimentando VALUES
de várias maneiras e me deparei com a coisa sobre a junção VALUES
que parece estranha para mim.
A configuração é simples
CREATE TABLE #data ([Id] int);
INSERT INTO #data VALUES (101), (103);
Então há uma consulta
DECLARE @id1 int = 101, @id2 int = 102;
SELECT *
FROM (VALUES (@id1), (@id2)) p([Id])
FULL HASH JOIN #data d ON d.[Id] = p.[Id];
Não há nada de especial nisso. Ele funciona e produz seu resultado, se você executá-lo. Aqui está o seu plano de execução
Removendo uma linha de VALUES
no entanto
SELECT *
FROM (VALUES (@id1)) p([Id])
FULL HASH JOIN #data d ON d.[Id] = p.[Id];
faz com que o otimizador falhe
Msg 8622, Level 16, State 1, Line 1
Processador de consultas não pôde produzir um plano de consulta...
Por quê? Existe uma maneira (além de colocar o parâmetro na tabela temporária) para fazê-lo funcionar usando o algoritmo de hash?
Observação: isso não é um dispositivo real e serve para estudar o comportamento e os recursos do otimizador.
O exemplo acima foi testado em
Microsoft SQL Server 2017 (RTM-CU15-GDR) (KB4505225) - 14.0.3192.2 (X64)
Dizendo em resumo. Por causa dos
HASH
tiros de dica para a perna do otimizador e do próprio otimizador para o outro. Ser baleado no otimizador não pode cruzar a linha de chegada.Para ilustrar melhor o que está acontecendo, vamos reescrever a consulta problemática para unir duas
VALUES
e usar o algoritmo de mesclagemO plano de execução desta consulta é simples. Existe o operador Merge Join com duas entradas Constant Scan.
Esses dois Constant Scans são diferentes do otimizador.
O que representa a entrada de linha única tem o nome da coluna prefixado com
Expr
, enquanto o outro que representa a entrada de várias linhas tem o nome da coluna prefixado comUnion
. Os dados de Constant Scan de várias linhas são acessados nos predicados Merge Join como um tipo de "por referência" ([Union1001]
), enquanto os dados de Constant Scan de uma única linha são acessados como "por valor" (veja que@id1
é substituído em vez de[Expr1000]
).Essa substituição "por referência"→"por valor" é o resultado do mapeamento escalar realizado nos estágios iniciais de otimização.
Pode-se ver (usando o sinalizador de rastreamento 8606) que no predicado de junção da Árvore de Entrada é
[Union1001] = [Expr1000]
mas então na Árvore Simplificada torna-se
[Union1001] = @id1
O mapeamento escalar é a parte da lógica de extração de projeção e é realmente executado antes que o estágio de simplificação seja inserido.
Pode-se ter notado anteriormente que o nó Merge Join possui apenas predicado residual e nenhum predicado de igualdade de junção. Isso ocorre porque o predicado de igualdade de junção foi eliminado pelo mapeamento escalar. O
[Union1001] = @id1
predicado é de igualdade, mas não pode servir como um predicado de igualdade de junção . Para ser assim, ele deve referenciar colunas de ambas as entradas, mas@id1
é variável e não uma coluna.Portanto, sendo equijoin
ON d.[Id] = p.[Id]
originalmente, a consulta se transformou em non-equijoin (que é um caso especial e, por causa disso, a propósito, o otimizador não introduziu a classificação abaixo de Merge Join para as entradas Constant Scan não classificadas). Felizmente, no caso do otimizador de algoritmo de mesclagem, há essa alternativa não equijoin.No caso de usar o algoritmo de hash, a alternativa não equijoin não existe e, portanto, a eliminação do predicado de igualdade de junção faz com que o otimizador falhe posteriormente.
Não há sinalizador de rastreamento (*) que impeça o mapeamento escalar, nem alavanca de consulta nem nível de sessão nem inicialização. E não existe uma regra de otimização que possa ser desativada para preveni-la, pois ela não é executada por uma regra.
Eu só consegui executar a consulta problemática definindo o ponto de interrupção na
COptExpr::PexprMapScalar
rotinae modificando o valor do
eax
registro após a chamada paraScaOp_Identifier::ClassNo
fazer o SQL Server pensar que o segundo operando deScaOp_Comp
não é identificador.Aqui está a árvore simplificada da consulta problemática postada na pergunta
e aqui está seu plano obtido.
Na verdade, tem pouco sentido, porque o custo do plano obtido é de 0,0210675 unidades, enquanto a execução da consulta sem
HASH
dica leva ao plano de execução com o Merge Join (observe que não há classificação abaixo do Merge Join novamente)custando 0,0088948 unidades.
(*) Pode existir uma combinação de sinalizadores de rastreamento . Eu acho que não, mas eu não explorei todos os caminhos de código, então não tenho certeza.