Каков наиболее эффективный способ написать оператор выбора с подзапросом «не в»? - PullRequest
13 голосов
/ 31 августа 2009

Какой самый эффективный способ написать оператор выбора, подобный приведенному ниже.

SELECT *
FROM Orders
WHERE Orders.Order_ID not in (Select Order_ID FROM HeldOrders)

Суть в том, что вам нужны записи из одной таблицы, когда элемент отсутствует в другой таблице.

Ответы [ 5 ]

20 голосов
/ 31 августа 2009

Для начала, ссылка на старую статью в моем блоге о том, как предикат NOT IN работает в SQL Server (и в других системах тоже):


Вы можете переписать его следующим образом:

SELECT  *
FROM    Orders o
WHERE   NOT EXISTS
        (
        SELECT  NULL
        FROM    HeldOrders ho
        WHERE   ho.OrderID = o.OrderID
        )

однако большинство баз данных будут обрабатывать эти запросы одинаково.

Оба эти запроса будут использовать какой-то ANTI JOIN.

Это полезно для SQL Server, если вы хотите проверить два или более столбцов, поскольку SQL Server не поддерживает этот синтаксис:

SELECT  *
FROM    Orders o
WHERE   (col1, col2) NOT IN
        (
        SELECT  col1, col2
        FROM    HeldOrders ho
        )

Обратите внимание, однако, что NOT IN может быть сложным из-за способа обработки NULL значений.

Если Held.Orders обнуляется, записи не найдены и подзапрос возвращает, кроме одного NULL, весь запрос ничего не даст (и IN, и NOT IN приведут к NULL в этом случае) .

Рассмотрим эти данные:

Orders:

OrderID
---
1

HeldOrders:

OrderID
---
2
NULL

Этот запрос:

SELECT  *
FROM    Orders o
WHERE   OrderID NOT IN
        (
        SELECT  OrderID
        FROM    HeldOrders ho
        )

вернет ничего , что, вероятно, не то, что вы ожидаете.

Однако вот этот:

SELECT  *
FROM    Orders o
WHERE   NOT EXISTS
        (
        SELECT  NULL
        FROM    HeldOrders ho
        WHERE   ho.OrderID = o.OrderID
        )

вернет строку с OrderID = 1.

Обратите внимание, что LEFT JOIN решения, предложенные другими, далеко не являются наиболее эффективным решением.

Этот запрос:

SELECT  *
FROM    Orders o
LEFT JOIN
        HeldOrders ho
ON      ho.OrderID = o.OrderID
WHERE   ho.OrderID IS NULL

будет использовать условие фильтра, которое необходимо будет оценить и отфильтровать все совпадающие строки, которые могут быть нумерацией

Метод ANTI JOIN, используемый как IN, так и EXISTS, просто должен убедиться, что запись не существует один раз для каждой строки в Orders, поэтому он устранит все Сначала возможны дубликаты:

  • NESTED LOOPS ANTI JOIN и MERGE ANTI JOIN будут просто пропускать дубликаты при оценке HeldOrders.
  • A HASH ANTI JOIN удалит дубликаты при построении хеш-таблицы.
8 голосов
/ 31 августа 2009

«Наиболее эффективный» будет различным в зависимости от размеров таблиц, индексов и т. Д. Другими словами, он будет отличаться в зависимости от конкретного случая, который вы используете.

В зависимости от ситуации я обычно использую три способа достижения желаемого.

1. Ваш пример работает нормально, если Orders.order_id проиндексирован, а HeldOrders довольно маленький.

2. Другой метод - это «коррелированный подзапрос», который представляет собой небольшое изменение того, что у вас есть ...

SELECT *
FROM Orders o
WHERE Orders.Order_ID not in (Select Order_ID 
                              FROM HeldOrders h 
                              where h.order_id = o.order_id)

Обратите внимание на добавление предложения where. Это имеет тенденцию работать лучше, когда HeldOrders имеет большое количество строк. Order_ID должен быть проиндексирован в обеих таблицах.

3. Другой метод, который я иногда использую - это левое внешнее соединение ...

SELECT *
FROM Orders o
left outer join HeldOrders h on h.order_id = o.order_id
where h.order_id is null

Когда используется левое внешнее соединение, h.order_id будет иметь значение, соответствующее o.order_id, когда есть соответствующая строка. Если соответствующей строки нет, h.order_id будет иметь значение NULL. Проверяя значения NULL в предложении where, вы можете фильтровать все, что не соответствует.

Каждый из этих вариантов может работать более или менее эффективно в различных сценариях.

4 голосов
/ 31 августа 2009

Вы можете использовать LEFT OUTER JOIN и проверить NULL в правой таблице.

SELECT O1.*
FROM Orders O1
LEFT OUTER JOIN HeldOrders O2
ON O1.Order_ID = O2.Order_Id
WHERE O2.Order_Id IS NULL
1 голос
/ 31 августа 2009

Я не уверен, что является наиболее эффективным, но другие варианты:

1. Use EXISTS

SELECT * 
FROM ORDERS O 
WHERE NOT EXISTS (SELECT 1 
                  FROM HeldOrders HO 
                  WHERE O.Order_ID = HO.OrderID)

2. Use EXCEPT

SELECT O.Order_ID 
FROM ORDERS O 
EXCEPT 
SELECT HO.Order_ID 
FROM HeldOrders
0 голосов
/ 31 августа 2009

Попробуйте

SELECT *
FROM Orders
LEFT JOIN HeldOrders
ON HeldOrders.Order_ID = Orders.Order_ID
WHERE HeldOrders.Order_ID IS NULL
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...