Лучшая практика между использованием LEFT JOIN или NOT EXISTS

68

Есть ли лучшая практика между использованием LEFT JOIN или NOT EXISTS формата?

Какая польза от использования одного над другим?

Если нет, что должно быть предпочтительным?

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)

Я использую запросы в Access к базе данных SQL Server.

Майкл Ричардсон
источник
2
Как и в стороне, казалось бы , идентичный подход WHERE A.idx NOT IN (...) является не тождествен вследствие трехвалентного поведения NULL(т.е. NULLне равен NULL( и не неравен), поэтому если у вас есть какой - нибудь NULL в tableBвас получат неожиданные результаты!)
Elaskanator

Ответы:

58

Самая большая разница не в объединении против не существует, то (как написано), то SELECT *.

В первом примере вы получаете все столбцы из обоих A и B, тогда как во втором примере вы получаете только столбцы из A.

В SQL Server второй вариант немного быстрее в очень простом надуманном примере:

Создайте две таблицы примеров:

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

Вставьте 10 000 строк в каждую таблицу:

INSERT INTO dbo.A DEFAULT VALUES;
GO 10000

INSERT INTO dbo.B DEFAULT VALUES;
GO 10000

Удалить каждый 5-й ряд из второй таблицы:

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;

Выполните два SELECTварианта теста :

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);

Планы выполнения:

введите описание изображения здесь

Во втором варианте не требуется выполнять операцию фильтрации, поскольку он может использовать левый оператор анти-полусоединения.

Макс Вернон
источник
24

По логике они идентичны, но NOT EXISTSближе к AntiSemiJoin, который вы запрашиваете, и, как правило, предпочтительнее. Это также подчеркивает, что вы не можете получить доступ к столбцам в B, потому что он используется только как фильтр (в отличие от того, чтобы они были доступны со значениями NULL).

Много лет назад (SQL Server 6.0 ish) это LEFT JOINбыло быстрее, но это не имело место в течение очень долгого времени. В наши дни NOT EXISTSэто немного быстрее.


Самое большое влияние в Access состоит в том, что JOINметод должен завершить соединение перед его фильтрацией, создавая объединенный набор в памяти. Его использование NOT EXISTSпроверяет строку, но не выделяет место для столбцов. Плюс, он перестает смотреть, как только находит строку. Производительность в Access немного различается, но общее правило, NOT EXISTSкак правило, немного быстрее. Я был бы менее склонен сказать, что это «лучшая практика», так как здесь задействовано больше факторов.

Роб Фарли
источник
6

Исключение, которое я заметил для NOT EXISTSпревосходства (хотя и незначительного), LEFT JOIN ... WHERE IS NULLэто при использовании связанных серверов .

Из рассмотрения планов выполнения выясняется, что NOT EXISTSоператор выполняется в режиме вложенного цикла. При этом он выполняется для каждой строки (что, я думаю, имеет смысл).

Пример плана выполнения, демонстрирующий это поведение: введите описание изображения здесь

robopim
источник
1
Связанные серверы жестоки для такого рода вещей. Возможный подход к решению этой проблемы состоит в том, чтобы скопировать удаленные данные по ссылке на связанный сервер, используя простое, INSERT INTO #t (a,b,c) SELECT a,b,c FROM LinkedServer.database.dbo.table WHERE x=yзатем запустив NOT EXISTS (...)предложение для этой временной копии базы данных.
Макс Вернон
2
Немного застенчивый прямо сейчас, чтобы получить ответ от Макс Вернон на мой пост! Fanboy'ing в сторону. Забавно, что вы упомянули об этом, поскольку я несколько раз использовал этот точный подход, чтобы максимально эффективно использовать эти межсерверные ситуации.
Робопим
1
Ура, @pimbrouwers - спасибо за ваш добрый комментарий!
Макс Вернон
5

В общем, движок создаст план выполнения, основанный главным образом на:

  1. Количество рядов в А и Б
  2. Есть ли Индекс на А и / или Б.
  3. Ожидаемое количество строк результатов (и промежуточных строк)
  4. Форма входного запроса (т. Е. Ваш вопрос)

Для (4):

План «не существует» поощряет план на основе поиска для таблицы B. Это хороший выбор, когда таблица A мала, а таблица B велика (и индекс B существует).

План «противодействия» является хорошим выбором, когда таблица A очень велика или таблица B очень мала, или индекс B отсутствует, и возвращается большой набор результатов.

Однако это просто «поощрение», как взвешенный вклад. Сильный (1), (2), (3) часто делает выбор для (4) спорным.

(Не обращая внимания на эффект вашего примера, возвращающего разные столбцы из-за *, адресованного ответом @MaxVernon.).

crokusek
источник