Junções (SQL Server)

Aplica-se a:SQL ServerBanco de Dados SQL do AzureInstância Gerenciada de SQL do AzureAzure Synapse AnalyticsBanco de Dados SQL no Microsoft Fabric

O SQL Server usa junções para recuperar dados de várias tabelas com base nas relações lógicas entre elas. As junções são fundamentais para operações de banco de dados relacionais e permitem que você combine dados de duas ou mais tabelas em um único conjunto de resultados.

O SQL Server implementa operações de junção lógica (definidas por Transact-SQL sintaxe) e operações de junção física (os algoritmos reais usados para executar as junções). Entender ambos os aspectos ajuda você a escrever consultas eficientes e otimizar o desempenho do banco de dados.

As operações de junção lógica incluem:

  • Junções internas
  • Junções externas à esquerda, à direita e completas
  • Junções cruzadas

As operações de junção física incluem:

  • Junções por Laços Aninhados
  • Mesclar junções
  • Junções de hash
  • Junções adaptáveis (aplica-se a: SQL Server 2017 (14.x) e versões posteriores)

Este artigo explica como as junções funcionam, quando usar diferentes tipos de junção e como o Otimizador de Consultas seleciona o algoritmo de junção mais eficiente com base em fatores como tamanho da tabela, índices disponíveis e distribuição de dados.

Note

Para obter mais informações sobre a sintaxe de junção, consulte a cláusula FROM e JOIN, APPLY, PIVOT.

Unir conceitos básicos

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.
  • A join_condition define o predicado a ser avaliado para cada par de linhas unidas.

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 é duplicado em duas ou mais tabelas usadas na consulta, as referências a ela não precisam ser qualificadas com o nome da tabela. Isso é mostrado no exemplo anterior. Às vezes, essa SELECT cláusula é difícil de entender porque 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 melhora ainda mais com o uso de aliases de tabelas, especialmente quando os próprios nomes das tabelas precisam ser qualificados com os nomes do banco de dados e do proprietário. 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 da 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 anti semi join.

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, consulte Operadores de Comparação e WHERE.

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 escolher o tipo mais eficiente de junção física, a ordem em que as tabelas serão associadas e até mesmo usar tipos de operação de junção lógica que não podem ser expressos diretamente com a sintaxe Transact-SQL, como semijunções e junções antissemi. A execução física de várias junções pode usar muitas otimizações diferentes e, portanto, não pode ser prevista de forma confiável. Para obter mais informações sobre semijunções e antisemijunções, consulte Referência de operadores de showplan lógico e físico.

As colunas usadas em uma condição de junção não precisam ter o mesmo nome nem o mesmo tipo de dado. No entanto, se os tipos de dados não forem idênticos, eles deverão ser compatíveis ou serem tipos que o SQL Server pode converter implicitamente. Se os tipos de dados não puderem ser convertidos implicitamente, a condição de junção deverá converter explicitamente o tipo de dados usando a CAST função. Para obter mais informações sobre conversões implícitas e explícitas, consulte 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, consulte Subconsultas (SQL Server).

Note

As tabelas não podem ser unidas diretamente em colunas de ntext, texto ou imagem. No entanto, as tabelas podem ser associadas indiretamente por meio de 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)

Entenda as junções de loop aninhado

Se uma entrada de junção for pequena (menos de 10 linhas) e a outra entrada de junção for relativamente grande e indexada nas colunas de junção, uma junção de loops aninhados com índice é a operação de junção mais rápida, porque requer a menor E/S e o menor número de 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 busca varre uma tabela ou um índice inteiro; isso é chamado de junção ingênua de laços aninhados. Se a pesquisa usar um índice, ela será chamada de junção por laços aninhados com índice. Se o índice for criado como parte do plano de consulta (e destruído após a conclusão da consulta), ele 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 operações pequenas, como as que afetam apenas um pequeno conjunto de linhas, as junções por loops aninhados com índice são superiores tanto às junções por mesclagem quanto às junções por 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 Nested Loops é definido como True, isso significa que um Nested Loops otimizado (ou ordenação em lote) é usado para minimizar a E/S quando a tabela do lado interno é grande, independentemente de estar paralelizada ou não. A presença dessa otimização em um plano específico pode não ser muito óbvia ao analisar um plano de execução, já que a ordenação em si é uma operação oculta. Mas, ao observar no XML do plano o atributo OPTIMIZED, isso indica que a junção Nested Loops pode tentar reordenar as linhas de entrada para melhorar o desempenho de E/S.

Mesclar junções

Se as duas entradas da junção não forem pequenas, mas estiverem ordenadas pela coluna de junção (por exemplo, se tiverem sido obtidas por meio da varredura de índices ordenados), uma junção por mesclagem será a operação de junção mais rápida. Se ambas as entradas de junção forem grandes e tiverem tamanhos semelhantes, uma junção de mesclagem com ordenação prévia e uma junção de hash oferecerão desempenho semelhante. No entanto, operações de junção por hash são, com frequência, muito mais rápidas quando os tamanhos das duas entradas diferem significativamente entre si.

A junção por mesclagem requer que ambas as entradas estejam ordenadas pelas 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, a linha de valor inferior será descartada e outra linha será obtida dessa entrada. Esse processo repete-se até que todas as linhas tenham sido processadas.

A operação de junção por mesclagem é uma operação regular ou muitos-para-muitos. Uma junção de intercalação 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 por mesclagem em si é muito rápida, mas pode ser uma opção cara se forem necessárias operações de ordenação. Porém, se o volume de dados for grande e os dados desejados puderem ser obtidos pré-ordenados a partir de índices B-tree existentes, frequentemente a junção por mesclagem 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 sejam salvos explicitamente no disco e indexados) e geralmente 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 por hash reduzem a necessidade de 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 atribui essas funções de modo que a menor das duas entradas seja a entrada de build.

Junções por hash são usadas para muitos tipos de operações de correspondência de conjuntos: junção interna; junção externa à esquerda, à direita e completa; semijunção à esquerda e à direita; interseção; união; e diferença. Além disso, uma variante da junção por hash pode fazer remoção de duplicatas e agrupamento, como SUM(salary) GROUP BY department. Essas modificações usam apenas uma entrada para as funções de build e probe.

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 bucket de hash de acordo com o valor de hash calculado para a chave de 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. Toda a entrada de sondagem é percorrida ou computada uma linha de cada vez e, para cada linha de sondagem, o valor da chave de hash é calculado, o bucket de hash correspondente é percorrido e as correspondências são produzidas.

Junção hash de cortesia

Se a entrada de build não couber na memória, uma junção hash será executada em várias etapas. Isso é conhecido como uma junção hash de cortesia. Cada passo tem uma fase de construção e fase de investigação. Inicialmente, todas as entradas de build e probe são consumidas e particionadas em vários arquivos (usando uma função de hash nas chaves de hash). Aplicar a função de hash às chaves de hash garante que quaisquer dois registros a serem unidos estarão 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 hash é então aplicada a cada par de arquivos particionados.

Junção hash recursiva

Se a entrada da compilação for tão grande que as entradas para uma intercalação externa padrão exijam múltiplos níveis de intercalação, serão necessárias múltiplas etapas de particionamento e múltiplos níveis de particionamento. 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.

Note

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 a otimização determinar qual junção de hash é usada. Portanto, o SQL Server começa com uma junção hash em memória e gradualmente passa para grace hash join e para junção hash recursiva, dependendo do tamanho da entrada de construção.

Se o Otimizador de Consultas estimar incorretamente qual das duas entradas é a menor e, portanto, deveria ter sido a entrada de build, os papéis de build e probe sã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 inversão de papéis. A inversão de papéis ocorre dentro da junção hash após pelo menos um despejo em disco.

Note

A inversão de papéis ocorre independentemente de quaisquer pistas da consulta ou de sua estrutura. A reversão de função não é exibida em seu plano de consulta; quando ocorre, é transparente para o usuário.

Resgate de hash

O termo hash bailout às vezes é usado para se referir a grace hash joins ou recursive hash joins.

Note

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 em modo de lote permitem que a escolha de um método de junção Hash Join ou Nested Loops seja adiada até depois que a primeira entrada tiver sido examinada. O operador de Junção Adaptativa define um valor limite usado para decidir quando alternar para um plano Nested Loops. Portanto, um plano de consulta pode alternar dinamicamente para uma estratégia de junção melhor durante a execução sem precisar ser recompilado.

Tip

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 o número de linhas da entrada de compilação da junção for pequeno o suficiente para que uma junção de loops aninhados seja mais eficiente do que uma junção hash, o plano muda para um algoritmo de loops aninhados.
  • Se a entrada de build da junção exceder um limite específico de número de linhas, não ocorrerá nenhuma mudança e seu plano continuará usando 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 varredura de índice columnstore usada para fornecer linhas para a fase de construção da junção por hash.
  2. O novo operador de Junção Adaptativa. Este operador define um limiar usado para decidir quando alternar para um plano de Laços Aninhados. Para esse exemplo, o limite é de 78 linhas. Qualquer coisa com >= 78 linhas usará uma junção por hash. Se for menor que o limite, uma junção por 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 ramificação é uma busca em índice clusterizado para uso pela junção Nested Loops, caso o limite não tivesse sido excedido. Vemos "0 de 336" linhas exibidas (a ramificação não é usada).

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 construção do Hash Join não continuou, não há linhas passando pelo segundo ramo.

Comentários sobre Junção Adaptativa

As junções adaptáveis exigem mais memória do que um plano equivalente de junção de loops aninhados indexados. A memória adicional é solicitada como se o Nested Loops fosse uma junção de hash. Também há sobrecarga na fase de construção, por ser uma operação de para e segue, em comparação com uma junção equivalente de Loops Aninhados em streaming. Com esse custo adicional, isso traz flexibilidade para cenários em que o número de linhas varia nos dados de entrada da compilação.

As junções adaptativas no modo de lote funcionam na execução inicial de uma instrução e, depois de compiladas, as execuções subsequentes permanecem adaptativas com base no limiar compilado da Junção Adaptativa e nas linhas em tempo de execução que fluem pela fase de construção da entrada externa.

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

Acompanhar a atividade da junção adaptativa

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

Atributo do plano Description
AdaptiveThresholdRows Mostra o limiar usado para alternar de uma junção por hash para uma junção por 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.

Tip

O Repositório de Consultas captura e pode forçar um plano de Junção Adaptativa em modo de lote.

Declarações qualificadas a junção adaptativa

Algumas condições fazem com que uma junção lógica seja candidata a uma Junção Adaptável em 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 elegíveis no momento).
  • A junção pode ser executada tanto por uma junção Nested Loops indexada quanto por uma junção por hash.
  • A operação Hash Join usa o modo em lotes, habilitado pela presença de um índice columnstore na consulta como um todo, por uma tabela com índice columnstore referenciada diretamente pela junção ou pelo uso de Batch mode on rowstore.
  • As soluções alternativas geradas para a junção de loops aninhados e a junção por hash devem ter o mesmo primeiro filho (referência externa).

Linhas de limiar adaptativo

O gráfico a seguir mostra um exemplo do ponto de interseção entre o custo de um Hash Join e o custo da alternativa de junção Nested Loops. 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 desativadas no escopo do banco de dados ou da instrução, mantendo, ainda assim, o nível de compatibilidade do banco de dados em 140 ou superior.

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 desativadas para uma consulta específica especificando DISABLE_BATCH_MODE_ADAPTIVE_JOINS como a 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'));

Note

Uma USE HINT dica de consulta 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 das tabelas que estão sendo unidas, os valores nulos não correspondem entre si. 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 a coluna c não obtém uma correspondência nas colunas que têm valores de 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 nessas 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 tornam fácil distinguir um NULL nos dados de um NULL que representa uma falha ao ingressar. Quando NULL os valores estão presentes nos dados que estão sendo unidos, geralmente é preferível omitê-los dos resultados usando uma junção regular.