Каков наиболее эффективный способ написать инструкцию select с подзапросом "не в"?
Каков наиболее эффективный способ записи оператора select, подобного приведенному ниже.
SELECT *
FROM Orders
WHERE Orders.Order_ID not in (Select Order_ID FROM HeldOrders)
Суть в том, что вам нужны записи из одной таблицы, когда элемент не находится в другой таблице.
Ответы
Ответ 1
"Самый эффективный" будет отличаться в зависимости от размеров таблиц, индексов и т.д. Другими словами, он будет отличаться в зависимости от конкретного случая, который вы используете.
Есть три способа, которыми я обычно использую, чтобы выполнить то, что вы хотите, в зависимости от ситуации.
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, вы можете фильтровать все, что не соответствует.
Каждый из этих вариантов может работать более или менее эффективно в различных сценариях.
Ответ 2
Во-первых, ссылка на старую статью в моем блоге о том, как предикат 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, никакие записи не найдены, а подзапрос возвращается, но один 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
устраняет дубликаты при построении хеш-таблицы.
Ответ 3
Вы можете использовать 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
Ответ 4
Я не уверен, что является наиболее эффективным, но другие варианты:
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
Ответ 5
Try
SELECT *
FROM Orders
LEFT JOIN HeldOrders
ON HeldOrders.Order_ID = Orders.Order_ID
WHERE HeldOrders.Order_ID IS NULL