Prática recomendada entre usar LEFT JOIN ou NOT EXISTS

67

Existe uma prática recomendada entre usar o formato LEFT JOIN ou NOT EXISTS?

Qual é o benefício de usar um sobre o outro?

Se nenhum, qual deve ser o preferido?

SELECT *
FROM tableA A
LEFT JOIN tableB B
     ON A.idx = B.idx
WHERE B.idx IS NULL

SELECT *
FROM tableA A
WHERE NOT EXISTS
(SELECT idx FROM tableB B WHERE B.idx = A.idx)

Estou usando consultas no Access em um banco de dados do SQL Server.

Michael Richardson
fonte
2
Como um aparte, a abordagem aparentemente idêntica WHERE A.idx NOT IN (...) é não idênticas devido ao comportamento trivalente de NULL(ou seja, NULLnão é igual a NULL(nem desigual), portanto, se você tiver qualquer NULL em tableBque você vai obter resultados inesperados!)
Elaskanator

Respostas:

58

A maior diferença não está na junção vs não existe, é (como está escrito), o SELECT *.

No primeiro exemplo, você obtém todas as colunas de ambos A e B, enquanto no segundo exemplo, obtém apenas colunas de A.

No SQL Server, a segunda variante é um pouco mais rápida em um exemplo artificial muito simples:

Crie duas tabelas de amostra:

CREATE TABLE dbo.A
(
    A_ID INT NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
);

CREATE TABLE dbo.B
(
    B_ID INT NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
);
GO

Insira 10.000 linhas em cada tabela:

INSERT INTO dbo.A DEFAULT VALUES;
GO 10000

INSERT INTO dbo.B DEFAULT VALUES;
GO 10000

Remova a cada quinta linha da segunda tabela:

DELETE 
FROM dbo.B 
WHERE B_ID % 5 = 1;

SELECT COUNT(*) -- shows 10,000
FROM dbo.A;

SELECT COUNT(*) -- shows  8,000
FROM dbo.B;

Execute as duas SELECTvariantes da instrução de teste :

SELECT *
FROM dbo.A
    LEFT JOIN dbo.B ON A.A_ID = B.B_ID
WHERE B.B_ID IS NULL;

SELECT *
FROM dbo.A
WHERE NOT EXISTS (SELECT 1
    FROM dbo.B
    WHERE b.B_ID = a.A_ID);

Planos de execução:

insira a descrição da imagem aqui

A segunda variante não precisa executar a operação de filtro, pois pode usar o operador de junção anti-semifacial esquerda.

Max Vernon
fonte
23

Logicamente, eles são idênticos, mas NOT EXISTSestão mais próximos do AntiSemiJoin que você está solicitando e geralmente são os preferidos. Também destaca melhor que você não pode acessar as colunas em B, porque é usado apenas como um filtro (em vez de disponibilizá-las com valores NULL).

Muitos anos atrás (SQL Server 6.0 ish), LEFT JOINfoi mais rápido, mas esse não é o caso há muito tempo. Hoje em dia, NOT EXISTSé marginalmente mais rápido.


O maior impacto no Access é que o JOINmétodo precisa concluir a associação antes de filtrá-la, construindo o conjunto associado na memória. Usá- NOT EXISTSlo verifica a linha, mas não aloca espaço para as colunas. Além disso, ele para de olhar quando encontra uma linha. O desempenho varia um pouco mais no Access, mas uma regra geral é que NOT EXISTStende a ser um pouco mais rápido. Eu estaria menos inclinado a dizer que é a "melhor prática", pois há mais fatores envolvidos.

Rob Farley
fonte
6

Uma exceção que eu notei ao NOT EXISTSser superior (embora marginal) LEFT JOIN ... WHERE IS NULLé ao usar Servidores Vinculados .

Ao examinar os planos de execução, parece que o NOT EXISTSoperador é executado de maneira aninhada. Pelo qual é executado em uma base por linha (o que suponho que faz sentido).

Exemplo de plano de execução que demonstra esse comportamento: insira a descrição da imagem aqui

robopim
fonte
11
Servidores vinculados são brutais para esse tipo de coisa. Uma abordagem possível para resolver esse problema é copiar os dados remotos pelo link do servidor vinculado usando uma cláusula simples e INSERT INTO #t (a,b,c) SELECT a,b,c FROM LinkedServer.database.dbo.table WHERE x=ydepois executando a NOT EXISTS (...)cópia temporária do banco de dados.
Max Vernon
2
Um pouco tímido agora para obter uma resposta de Max Vernon no meu post! Fanboys de lado. É engraçado você mencionar isso, já que usei essa abordagem exata em várias ocasiões para tirar o máximo proveito dessas situações entre servidores.
robopim
11
Saúde, @pimbrouwers - obrigado pelo seu comentário!
Max Vernon
5

Em geral, o mecanismo criará um plano de execução baseado essencialmente em:

  1. O número de linhas em A e B
  2. Se existe um índice em A e / ou B.
  3. O número esperado de linhas de resultado (e linhas intermediárias)
  4. A forma da consulta de entrada (ou seja, sua pergunta)

Para 4):

O plano "não existe" incentiva um plano baseado em busca na tabela B. Essa é uma boa opção quando a tabela A é pequena e a tabela B é grande (e existe um índice em B).

O plano "antijoin" é uma boa opção quando a tabela A é muito grande ou a tabela B é muito pequena ou nenhum índice em B e retorna um grande conjunto de resultados.

No entanto, é apenas um "incentivo", como uma contribuição ponderada. Um forte (1), (2), (3) geralmente faz a escolha para (4) discussão.

(Ignorando o efeito do seu exemplo retornando colunas diferentes devido ao *, abordado pela resposta @MaxVernon.).

crokusek
fonte