Найкраща практика між використанням лівого приєднання або відсутності


67

Чи є найкраща практика між використанням лівого приєднання або формату НЕ ІСНУЮТЬСЯ?

Яка користь від використання одного над іншим?

Якщо ні, то що слід віддати перевагу?

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

Видаліть кожен другий ряд з другої таблиці:

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

Плани виконання:

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

У другому варіанті не потрібно виконувати операцію фільтра, оскільки він може використовувати лівий оператор анти-напівз'єднання.


23

Логічно вони ідентичні, але 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оператор виконує вкладений цикл. При цьому він виконується на основі рядків (що, напевно, має сенс).

Приклад плану виконання, що демонструє таку поведінку: введіть тут опис зображення


1
Пов'язані сервери жорстокі для подібних речей. Можливий підхід до вирішення цієї проблеми полягає в копіюванні віддалених даних через пов'язане серверне посилання за допомогою простого, INSERT INTO #t (a,b,c) SELECT a,b,c FROM LinkedServer.database.dbo.table WHERE x=yа потім запущеного NOT EXISTS (...)пункту проти цієї тимчасової копії бази даних.
Макс Вернон

2
Трохи ганебний зараз, щоб отримати відповідь від Макса Вернона на мій пост! Фанбойінг убік. Смішно ви це згадуєте, оскільки я кілька разів використовував такий точний підхід, щоб отримати максимум користі з цих ситуацій між серверами.
robopim

1
Будьте здорові, @pimbrouwers - дякую за добрий коментар!
Макс Вернон

5

Загалом, двигун створить план виконання, заснований по суті на:

  1. Кількість рядків в А і В
  2. Чи є індекс на A та / або B.
  3. Очікувана кількість рядків результатів (і проміжних рядків)
  4. Форма вхідного запиту (тобто запитання)

Для (4):

План "не існує" заохочує план, заснований на пошуку в таблиці B. Це хороший вибір, коли таблиця A мала і таблиця B велика (а індекс існує на B).

План "антиз'єднання" є хорошим вибором, коли таблиця A дуже велика або таблиця B дуже мала або немає індексу на B і повертає великий набір результатів.

Однак це лише "заохочення", як зважений внесок. Сильний (1), (2), (3) часто робить вибір для (4) суперечок.

(Ігнорування ефекту вашого прикладу, що повертає різні стовпці через *, адресований відповіддю @MaxVernon.)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.