Partilhar via


Junções (SQL Server)

Aplica-se a: SQL Server Banco de Dados SQL do Azure Instância Gerenciada de SQL do Azure Azure Synapse Analytics Analytics Platform System (PDW)

O SQL Server executa operações de classificação, interseção, união e diferença usando classificação de memória e a tecnologia de junção hash. Usando esse tipo de plano de consulta, o SQL Server dá suporte ao particionamento de tabela vertical.

O SQL Server implementa operações de junção lógica, conforme determinado pela sintaxe Transact-SQL:

  • Junção interna
  • Junção externa esquerda
  • Junção externa direita
  • Junção externa completa
  • União cruzada

Observação

Para obter mais informações sobre a sintaxe de junção, confira a Cláusula FROM mais JOIN, APPLY, PIVOT (Transact-SQL).

O SQL Server emprega quatro tipos de operações de junção física para realizar as operações de junção lógica:

  • Junções de Loops Aninhados
  • Junções de mesclagem
  • Junções de hash
  • Junções adaptáveis (a partir do SQL Server 2017 [14.x])

Conceitos básicos do recurso de junção

Usando junções, é possível recuperar dados de duas ou mais tabelas com base em relações lógicas entre as tabelas. As junções indicam como o SQL Server deve usar dados de uma tabela para selecionar as linhas em outra tabela.

Uma condição de junção define o modo como duas tabelas são relacionadas em uma consulta por:

  • Especificando a coluna de cada tabela a ser usada para a junção. Uma condição de junção típica especifica uma chave estrangeira de uma tabela e sua chave associada na outra tabela.
  • Especificação de um operador lógico (por exemplo, = ou <>,) a ser usado na comparação de valores das colunas.

As junções são expressas logicamente por meio desta sintaxe Transact-SQL:

  • INNER JOIN
  • LEFT [ OUTER ] JOIN
  • RIGHT [ OUTER ] JOIN
  • FULL [ OUTER ] JOIN
  • CROSS JOIN

As junções internas podem ser especificadas nas cláusulas FROM ou WHERE. As junções externas e as uniões cruzadas podem ser especificadas apenas na cláusula FROM. As condições de junção combinam-se com as condições de pesquisa WHERE e HAVING para controlar as linhas selecionadas das tabelas base referenciadas na cláusula FROM.

A especificação das condições de junção na cláusula FROM ajuda a separá-las de qualquer outro critério de pesquisa que possa ser especificado em uma cláusula WHERE, sendo o método recomendado para a especificação de junções. Uma sintaxe de junção de cláusula ISO FROM simplificada é:

FROM first_table < join_type > second_table [ ON ( join_condition ) ]
  • O join_type especifica que tipo de junção é executado: junção interna, junção externa ou união cruzada. Para obter explicações sobre os diferentes tipos de junções, consulte a cláusula FROM.
  • O elemento join_condition define o predicado a ser avaliado para cada par de linhas da junção.

O seguinte exemplo de código refere-se a uma especificação de junção da cláusula FROM:

FROM Purchasing.ProductVendor INNER JOIN Purchasing.Vendor
     ON ( ProductVendor.BusinessEntityID = Vendor.BusinessEntityID )

O seguinte exemplo de código refere-se a uma instrução SELECT simples que usa esta junção:

SELECT ProductID, Purchasing.Vendor.BusinessEntityID, Name
FROM Purchasing.ProductVendor INNER JOIN Purchasing.Vendor
    ON (Purchasing.ProductVendor.BusinessEntityID = Purchasing.Vendor.BusinessEntityID)
WHERE StandardPrice > $10
  AND Name LIKE N'F%';
GO

A instrução SELECT retorna as informações de produto e de fornecedor para qualquer combinação de partes fornecidas por uma empresa cujo nome começa com a letra F e o preço do produto é maior que US$ 10.

Quando várias tabelas são referenciadas em uma única consulta, todas as referências de coluna devem ser inequívocas. No exemplo anterior, as tabelas ProductVendor e Vendor têm uma coluna chamada BusinessEntityID. Qualquer nome de coluna que seja duplicado entre duas ou mais tabelas referenciadas na consulta deve ser qualificado com o nome da tabela. Todas as referências às colunas Vendor no exemplo estão qualificadas.

Quando um nome de coluna não está duplicado em duas ou mais tabelas usadas na consulta, a referências a ele não precisam ser qualificadas com o nome da tabela. Isso é mostrado no exemplo anterior. Às vezes, uma cláusula SELECT é difícil de ser compreendida, pois não há nada que indique a tabela que forneceu cada coluna. A legibilidade da consulta será aprimorada se todas as colunas estiverem qualificadas com seus nomes de tabela. A legibilidade é aperfeiçoada se aliases de tabela são usados, principalmente quando os nomes de tabelas precisam ser qualificados com nomes de proprietários e de banco de dados. O seguinte código é o mesmo exemplo, com a exceção da atribuição de aliases de tabela e da qualificação das colunas com aliases de tabela para melhorar a legibilidade:

SELECT pv.ProductID, v.BusinessEntityID, v.Name
FROM Purchasing.ProductVendor AS pv 
INNER JOIN Purchasing.Vendor AS v
    ON (pv.BusinessEntityID = v.BusinessEntityID)
WHERE StandardPrice > $10
    AND Name LIKE N'F%';

Os exemplos anteriores especificaram as condições de junção na cláusula FROM, que é o método preferencial. A seguinte consulta contém a mesma condição de junção especificada na cláusula WHERE:

SELECT pv.ProductID, v.BusinessEntityID, v.Name
FROM Purchasing.ProductVendor AS pv, Purchasing.Vendor AS v
WHERE pv.BusinessEntityID=v.BusinessEntityID
    AND StandardPrice > $10
    AND Name LIKE N'F%';

A lista SELECT para uma junção pode referenciar todas as colunas nas tabelas unidas ou qualquer subconjunto das colunas. A lista SELECT não precisa conter colunas de todas as tabelas na junção. Por exemplo, em uma junção de três tabelas, somente uma tabela pode ser usada para ligar uma das tabelas à terceira e, nenhuma das colunas da tabela do meio, precisa ser referenciada na lista de seleção. Isso também é chamado de antisemijunção.

Embora as condições de junção tenham comparações de igualdade (=), outros operadores relacionais ou de comparação podem ser especificados, como também outros predicados. Para obter mais informações, confira Operadores de comparação (Transact-SQL) e WHERE (Transact-SQL).

Quando o SQL Server processa junções, o otimizador de consulta escolhe o método mais eficaz (entre várias possibilidades) de processamento da junção. Isso inclui a escolha do tipo mais eficiente de junção física, a ordem na qual as tabelas serão unidas e, até mesmo, o uso de tipos de operações de junção lógica que não podem ser expressas diretamente com a sintaxe Transact-SQL, como semijunções e antissemijunções. A execução física de várias junções pode usar muitas otimizações diferentes e portanto não pode ser prevista de maneira confiável. Para obter mais informações sobre as semijunções e as antisemijunções, confira Referência de operadores lógicos e físicos do plano de execução.

Colunas usadas em uma condição de junção não precisam ter o mesmo nome ou ter o mesmo tipo de dados. Entretanto, se os tipos de dados não forem idênticos, eles deverão ser compatíveis, ou do tipo que o SQL Server possa converter implicitamente. Se o tipo de dados não puder ser convertido implicitamente, a condição de junção deverá converter explicitamente o tipo de dados usando a função CAST. Veja mais informações sobre conversões implícitas e explícitas em Conversão de tipo de dados (Mecanismo de Banco de Dados).

A maioria das consultas que usam uma junção pode ser regravada usando uma subconsulta (uma consulta aninhada dentro de outra consulta) e a maioria das subconsultas pode ser regravada como junções. Para obter mais informações sobre subconsultas, veja Subconsultas.

Observação

Tabelas não podem ser unidas diretamente em colunas ntext, text ou image. No entanto, as tabelas podem ser unidas indiretamente em colunas ntext, text ou image usando SUBSTRING. Por exemplo, SELECT * FROM t1 JOIN t2 ON SUBSTRING(t1.textcolumn, 1, 20) = SUBSTRING(t2.textcolumn, 1, 20) executa uma junção interna de duas tabelas nos primeiros 20 caracteres de cada coluna de texto em tabelas t1 e t2.
Além disso, outra possibilidade de comparação das colunas ntext ou text de duas tabelas é comparar o comprimento das colunas com a cláusula WHERE, por exemplo: WHERE DATALENGTH(p1.pr_info) = DATALENGTH(p2.pr_info)

Noções básicas sobre junções de loops aninhados

Se uma entrada de junção for pequena (menos que 10 linhas) e a outra entrada de junção for bastante grande e indexada a suas colunas de junção, uma junção de loops aninhados de índice será a operação de junção mais rápida porque eles requerem o mínimo de E/S e comparações.

A junção de loops aninhados, também denominada iteração aninhada, usa uma entrada de junção como a tabela de entrada externa (mostrada como a entrada superior no plano de execução) e outra como a tabela de entrada interna (na parte inferior). O loop externo consome a tabela de entrada externa linha por linha. O loop interno, executado para cada linha externa, pesquisa linhas correspondentes na tabela de entrada interna.

No caso mais simples, a pesquisa examina toda uma tabela ou índice; isto é chamado de junção de loops aninhados naive. Se a pesquisa explorar um índice, será chamado de junção de loops aninhados de índice. Se o índice for criado como parte do plano de consulta (e destruído na conclusão da consulta), será chamado de junção de loops aninhados de índice temporário. Todas essas variantes são consideradas pelo Otimizador de Consulta.

Uma junção de loops aninhados será particularmente eficaz se a entrada externa for pequena e a entrada interna for pré-indexada e grande. Em muitas transações pequenas, como as que afetam apenas um pequeno conjunto de linhas, as junções de loops aninhados de índice são superiores às junções mescladas e junções de hash. Em consultas grandes, contudo, as junções de loops aninhados não são frequentemente a melhor escolha.

Quando o atributo OPTIMIZED de um Operador de junção de loops aninhados é definido como True, isso significa que um Loop aninhado otimizado (ou Classificação em lote) é usado para minimizar a E/S quando a tabela de lado interno é grande, independentemente de ser paralelizada ou não. A presença dessa otimização em um determinado plano pode não ser muito óbvia durante a análise de um plano de execução, uma vez que a própria classificação é uma operação oculta. Porém, ao examinar o XML do plano para o atributo OPTIMIZED, isso indica que a Junção de loops aninhados pode tentar reordenar as linhas de entrada para melhorar o desempenho de E/S.

Junções de mesclagem

Se as duas entradas de junção não são pequenas, mas são classificadas na coluna de junção (por exemplo, se foram obtidas de exames em índices classificados), uma junção de mesclagem será a operação de junção mais rápida. Se ambas as entradas de junção forem grandes e as duas entradas forem de tamanhos semelhantes, uma junção de mesclagem com classificação prévia e uma junção de hash oferecerão desempenho semelhante. Porém, operações de junção de hash são muitas vezes mais rápidas quando os dois tamanhos da entrada diferem significativamente um do outro.

A junção de mescla exige que as duas entradas sejam classificadas nas colunas de mesclagem, que são definidas pelas cláusulas de igualdade (ON) do predicado de junção. O otimizador de consulta geralmente examina um índice, caso exista um no conjunto de colunas, ou coloca um operador de classificação abaixo da junção de mescla. Em casos raros, pode haver diversas cláusulas de igualdade, mas as colunas de mesclagem serão retiradas somente de algumas das cláusulas de igualdade disponíveis.

Uma vez que cada entrada é classificada, o operador Junção de Mesclagem adquire uma linha de cada entrada e as compara. Por exemplo, em operações de junção internas, serão retornadas as linhas que forem iguais. Se elas não forem iguais, será descartada a linha com o menor valor e será obtida outra linha daquela entrada. Esse processo repete-se até que todas as linhas tenham sido processadas.

A operação mesclar junção pode ser uma operação habitual ou uma operação do tipo muitos para muitos. Uma junção de mescla muitos para muitos usa uma tabela temporária para armazenar linhas. Se houver valores duplicados de cada entrada, uma das entradas tem que retroceder ao início das linhas duplicadas à medida que cada linha duplicada da outra entrada é processada.

Se houver um predicado residual presente, todas as linhas que satisfaçam ao predicado de mesclagem avaliarão o predicado residual e serão retornadas somente as linhas que o satisfaçam.

A junção de mescla é muito rápida, mas pode ser uma escolha cara se forem necessárias operações de classificação. Porém, se o volume de dados for grande e os dados desejados puderem ser obtidos pré-classificados de índices da árvore B existentes, frequentemente a junção de mescla será o algoritmo de junção mais rápido disponível.

Junções de hash

Junções de hash podem processar com eficácia grande volume de entradas não classificadas e não indexadas. Elas são úteis para resultados intermediários em consultas complexas por que:

  • Os resultados intermediários não são indexados (a menos que salvos explicitamente no disco e depois indexados) e muitas vezes não são classificados adequadamente para a próxima operação no plano de consulta.
  • Otimizadores de consulta só calculam tamanhos de resultado intermediário. Como as estimativas podem ser muito imprecisas para consultas complexas, os algoritmos para processar resultados intermediários não só devem ser eficientes, mas também devem ser degradados de forma suave se um resultado intermediário for muito maior do que o previsto.

A junção de hash permite reduções no uso da desnormalização. A desnormalização é usada geralmente para obter melhor desempenho e reduzir as operações de junção, apesar dos perigos de redundância, como atualizações inconsistentes. As junções de hash reduzem a necessidade a desnormalização. As junções de hash permitem particionamento vertical (representando grupos de colunas de uma única tabela em arquivos separados ou índices) para se tornar uma opção viável no design do banco de dados físico.

A junção hash tem duas entradas: a entrada build e entrada probe. O otimizador de consulta nomeia estes papéis de forma que a menor das duas entradas é a entrada de construção.

Junções de hash são usadas para muitos tipos de operações para definir correspondente: junção interna; esquerda, direita e junção externa completa; semijunção esquerda e direita ; interseção; união; e diferença. Além disso, uma variante da junção de hash pode fazer remoção e agrupamento duplicados, como SUM(salary) GROUP BY department. Essas modificações usam só uma entrada para os papéis de construção e investigação.

As seções seguintes descrevem tipos diferentes de junções de hash: junção de hash em-memória, junção de hash de cortesia e junção de hash recursiva.

Junção hash em memória

A junção de hash primeiro verifica ou calcula a entrada de construção inteira e então constrói uma tabela de hash em memória. Cada linha é inserida em um compartimento de memória hash que depende do valor de hash computado para a chave hash. Se a entrada de construção inteira for menor que a memória disponível, todas as linhas poderão ser inseridas na tabela de hash. Essa fase de construção é seguida pela fase de investigação. A entrada de investigação inteira é verificada ou calculada uma linha de cada vez e o valor da chave de hash é calculado para cada linha de investigação, o compartimento de hash correspondente é verificado e as correspondências são produzidas.

Junção hash de cortesia

Se a entrada de construção não couber na memória, uma junção de hash continua em vários passos. Isso é conhecido como uma junção hash de cortesia. Cada passo tem uma fase de construção e fase de investigação. Inicialmente, a construção inteira e entradas de investigação são consumidas e particionadas (usando uma função de hash na chave hash) em arquivos múltiplos. Usando a função de hash nas chaves de hash garante que quaisquer dois registros de junção devem estar no mesmo par de arquivos. Portanto, a tarefa de unir duas entradas grandes foi reduzida a instâncias múltiplas, mas menores, das mesmas tarefas. A junção de hash é se aplicada então a cada par de arquivos particionados.

Junção hash recursiva

Se a entrada de construção for tão grande que entradas para uma fusão externa padrão requereriam níveis de fusão múltiplos, serão requeridos passos de particionamentos múltiplos e níveis de particionamentos múltiplos. Se somente algumas das partições forem grandes, passos de particionamentos adicionais serão usados apenas para essas partições específicas. Para fazer todos os passos de particionamento tão rápido quanto possível, operações grandes, assíncronas de I/O são usadas de forma que um único thread pode manter unidades de disco múltiplas ocupadas.

Observação

Se a entrada de construção só for ligeiramente maior que a memória disponível, elementos de junção de hash em-memória e junção de hash de cortesia serão combinados em um único passo, produzindo uma junção de hash híbrida.

Nem sempre é possível durante otimização determinar qual junção de hash é usada. Portanto, o SQL Server começa usando uma junção hash em memória e gradualmente passa para a junção hash de cortesia e para a junção hash recursiva, dependendo do tamanho da entrada de compilação.

Se o Otimizador de Consulta prever incorretamente qual das duas entradas será menor e, portanto, deveria ter sido a entrada de compilação, os papéis de compilação e investigação serão invertidos dinamicamente. A junção de hash garante que usa o menor arquivo com excedente como entrada de construção. Essa técnica é chamada de reversão de papel. A reversão de papel acontece dentro da junção de hash depois de pelo menos um derramamento para o disco.

Observação

A reversão de papel acontece independente de qualquer dica de consulta ou estrutura. A reversão de papel não aparecerá em seu plano de consulta; quando acontecer, é transparente ao usuário.

Esgotamento de hash

O termo de esgotamento de hash às vezes é usado para descrever junções hash de cortesia ou junções hash recursivas.

Observação

Junções de hash recursivas ou abandonos de hash causam desempenho reduzido em seu servidor. Se você vir muitos eventos de Aviso de Hash em um rastreamento, atualize as estatísticas nas colunas que estão sendo unidas.

Para obter mais informações sobre esgotamento de hash, veja Classe de evento de aviso de Hash.

Junções adaptáveis

As Junções adaptáveis de Modo de lote permitem a escolha de um método de Junção Hash ou de Junção de loops aninhados a ser adiado até depois que a primeira entrada for verificada. O operador de Junção Adaptável define um limite que é usado para decidir quando mudar para um plano de Loops aninhados. Portanto, um plano de consulta pode alternar dinamicamente para uma estratégia de junção melhor durante a execução sem precisar ser recompilado.

Dica

As cargas de trabalho com oscilações frequentes entre verificações de entradas de junção pequenas e grandes terão mais benefícios com esse recurso.

A decisão de runtime se baseia nas seguintes etapas:

  • Se a contagem de linhas da entrada de junção de build for pequena o suficiente para que uma Junção de loops aninhados seja mais ideal do que uma Junção Hash, o plano será alternado para um algoritmo de Loops Aninhados.
  • Se a entrada de junção de build exceder um limite de contagem de linhas específico, o plano não mudará e continuará com uma Junção hash.

A consulta a seguir é usada para ilustrar um exemplo de Junção Adaptável:

SELECT [fo].[Order Key], [si].[Lead Time Days], [fo].[Quantity]
FROM [Fact].[Order] AS [fo]
INNER JOIN [Dimension].[Stock Item] AS [si]
       ON [fo].[Stock Item Key] = [si].[Stock Item Key]
WHERE [fo].[Quantity] = 360;

A consulta retorna 336 linhas. Habilitar as Estatísticas de consultas dinâmicas exibe o plano a seguir:

Captura de tela de um plano de execução mostrando o resultado da consulta com 336 linhas no operador de junção adaptável final.

No plano, observe o seguinte:

  1. Uma verificação de índice columnstore usada para fornecer linhas para a fase de build da Junção hash.
  2. O novo operador de Junção Adaptável. Este operador define um limite que é usado para decidir quando mudar para um plano de Loops Aninhados. Para esse exemplo, o limite é de 78 linhas. Tudo que for >= 78 linhas usará uma Junção hash. Quando estiver abaixo do limite, uma junção de Loops aninhados será usada.
  3. Como a consulta retorna 336 linhas, ela excede o limite. Portanto, a segunda branch representa a fase de investigação de uma operação de junção de hash padrão. Estatísticas de consulta dinâmica mostram as linhas que passam pelos operadores – nesse caso, "672 de 672".
  4. E a última branch é uma Busca de índice clusterizado a ser usada pela junção de Loops aninhados que não teve o limite excedido. Vemos "0 de 336" linhas exibidas (o branch não é usado).

Agora compare o plano com a mesma consulta, mas quando o valor de Quantity só tem uma linha na tabela:

SELECT [fo].[Order Key], [si].[Lead Time Days], [fo].[Quantity]
FROM [Fact].[Order] AS [fo]
INNER JOIN [Dimension].[Stock Item] AS [si]
       ON [fo].[Stock Item Key] = [si].[Stock Item Key]
WHERE [fo].[Quantity] = 361;

A consulta retorna uma linha. Habilitar as Estatísticas de consultas dinâmicas exibe o plano a seguir:

Captura de tela de um plano de execução, mostrando a junção adaptável final mostrando uma linha.

No plano, observe o seguinte:

  • Com uma linha retornada, a Busca de índice clusterizado agora tem linhas que passam por ela.
  • E, como a fase de build da Junção Hash não continuou, não há linhas passando pela segunda branch.

Comentários de junção adaptável

As junções adaptáveis apresentam um requisito de memória maior do que um plano equivalente de Junção de Loops Aninhados indexados. A memória adicional é solicitada como se os Loops Aninhados fossem uma Junção hash. Também há sobrecarga para a fase de build como uma operação de “parar e ir” em vez de uma junção equivalente de fluxo de Loops Aninhados. Com esse custo adicional, ganha-se a flexibilidade para cenários em que as contagens de linhas podem flutuar na entrada de build.

As Junções adaptáveis de modo de lote funcionam para a execução inicial de uma instrução. Depois que são compiladas, as próximas execuções permanecem adaptáveis com base no limite de Junção Adaptável compilada e nas linhas de runtime que passam pela fase de build da entrada externa.

Se uma Junção Adaptável alterna para uma operação de Loops Aninhados, ela usa as linhas já lidas pelo build de Junção Hash. O operador não lê novamente as linhas de referência externa novamente.

Controlar a atividade da junção adaptável

O operador de Junção Adaptável tem os seguintes atributos de operador de plano:

Atributo de plano Descrição
AdaptiveThresholdRows Mostra o uso de limite para alternar de uma junção hash para uma junção de loops aninhados.
EstimatedJoinType Qual é o provável tipo de junção.
ActualJoinType Em um plano real, mostra qual algoritmo de junção foi finalmente escolhido com base no limite.

O plano estimado mostra a forma do plano de Junção Adaptável, juntamente com um limite de Junção Adaptável definido e o tipo de junção estimado.

Dica

O Repositório de Consultas captura e é capaz de forçar um plano de Junção Adaptável de modo de lote.

Instruções qualificadas para junção adaptável

Algumas condições tornam uma junção lógica qualificada para uma Junção Adaptável de modo de lote:

  • O nível de compatibilidade do banco de dados é 140 ou superior.
  • A consulta é uma instrução SELECT (as instruções de modificação de dados não são qualificadas no momento).
  • A junção é qualificada para ser executada por uma Junção de loops aninhados indexada ou um algoritmo físico de Junção hash.
  • A junção hash usa o modo em lotes, habilitado pela presença de um Índice columnstore na consulta geral, uma tabela indexada por columnstore referenciada diretamente pela junção ou pelo uso do modo em lotes em rowstore.
  • As soluções alternativas geradas da Junção de loops aninhados e da Junção hash devem ter o mesmo primeiro filho (referência externa).

Linhas de limite adaptável

O gráfico a seguir mostra uma interseção de exemplo entre o custo de uma Junção hash e o custo de uma alternativa de Junção de loops aninhados. Neste ponto de interseção, o limite é determinado e, por sua vez, ele determina o algoritmo real usado para a operação de junção.

Um gráfico de linhas mostrando o limite de Junção Adaptável comparando uma junção de hash com uma junção de loop aninhada. Uma junção de loop aninhada tem um custo menor em contagens de linhas baixas, mas uma contagem de linhas mais alta em linhas mais altas.

Desabilitar junções adaptáveis sem alterar o nível de compatibilidade

Junções adaptáveis podem ser desabilitadas no escopo do banco de dados ou da instrução, mantendo o nível de compatibilidade do banco de dados como 140 e níveis superiores.

Para desabilitar as Junções adaptáveis para todas as execuções de consulta originadas do banco de dados, execute o seguinte dentro do contexto do banco de dados aplicável:

-- SQL Server 2017
ALTER DATABASE SCOPED CONFIGURATION SET DISABLE_BATCH_MODE_ADAPTIVE_JOINS = ON;

-- Azure SQL Database, SQL Server 2019 and later versions
ALTER DATABASE SCOPED CONFIGURATION SET BATCH_MODE_ADAPTIVE_JOINS = OFF;

Quando habilitada, essa configuração é exibida como habilitada em sys.database_scoped_configurations.

Para reabilitar as junções adaptáveis para todas as execuções de consulta originadas do banco de dados, execute o seguinte dentro do contexto do banco de dados aplicável:

-- SQL Server 2017
ALTER DATABASE SCOPED CONFIGURATION SET DISABLE_BATCH_MODE_ADAPTIVE_JOINS = OFF;

-- Azure SQL Database, SQL Server 2019 and later versions
ALTER DATABASE SCOPED CONFIGURATION SET BATCH_MODE_ADAPTIVE_JOINS = ON;

As Junções adaptáveis também podem ser desabilitadas para uma consulta específica designando DISABLE_BATCH_MODE_ADAPTIVE_JOINS como uma dica de consulta USE HINT. Por exemplo:

SELECT s.CustomerID,
       s.CustomerName,
       sc.CustomerCategoryName
FROM Sales.Customers AS s
LEFT OUTER JOIN Sales.CustomerCategories AS sc
       ON s.CustomerCategoryID = sc.CustomerCategoryID
OPTION (USE HINT('DISABLE_BATCH_MODE_ADAPTIVE_JOINS')); 

Observação

Uma dica de consulta USE HINT tem precedência sobre uma configuração no escopo do banco de dados ou uma configuração de sinalizador de rastreamento.

Valores nulos e junções

Quando há valores nulos nas colunas de tabelas sendo associadas, eles não correspondem uns aos outros. A presença de valores nulos em uma coluna de uma das tabelas que estão sendo associadas pode ser retornada apenas usando uma junção externa (a menos que a cláusula WHERE exclua valores nulos).

Veja duas tabelas que contêm NULL na coluna que participará da junção:

table1                          table2
a           b                   c            d
-------     ------              -------      ------
      1        one                 NULL         two
   NULL      three                    4        four
      4      join4

Uma junção que compara os valores na coluna a com os da coluna c não obtém uma correspondência nas colunas com valores NULL:

SELECT *
FROM table1 t1 JOIN table2 t2
   ON t1.a = t2.c
ORDER BY t1.a;
GO

Retorna somente uma linha com o valor de 4 nas colunas a e c:

a           b      c           d      
----------- ------ ----------- ------ 
4           join4  4           four   

(1 row(s) affected)

Os valores nulos retornados de uma tabela base também são difíceis de distinguir dos valores nulos retornados de uma junção externa. Por exemplo, a seguinte instrução SELECT faz uma junção externa esquerda nestas duas tabelas:

SELECT *
FROM table1 t1 LEFT OUTER JOIN table2 t2
   ON t1.a = t2.c
ORDER BY t1.a;
GO

Veja a seguir o conjunto de resultados.

a           b      c           d      
----------- ------ ----------- ------ 
NULL        three  NULL        NULL 
1           one    NULL        NULL 
4           join4  4           four   

(3 row(s) affected)

Os resultados não facilitam a distinção de um NULL nos dados de um NULL que representa uma falha na junção. Quando os valores NULL estão presentes nos dados que estão sendo associados, geralmente é preferível omiti-los nos resultados usando uma junção comum.