Как предикат IN работает в SQL?
После подготовки ответа на этот вопрос я обнаружил, что не могу подтвердить свой ответ.
В моем первом задании программирования мне сказали, что запрос в предикате IN ()
выполняется для каждой строки, содержащейся в родительском запросе, и поэтому следует избегать использования IN
.
Например, с учетом запроса:
SELECT count(*) FROM Table1 WHERE Table1Id NOT IN (
SELECT Table1Id FROM Table2 WHERE id_user = 1)
Table1 Rows | # of "IN" executions
----------------------------------
10 | 10
100 | 100
1000 | 1000
10000 | 10000
Это правильно? Как работает предикат IN
?
Ответы
Ответ 1
Предупреждение, которое вы получили о выполнении подзапросов для каждой строки, истинно - для взаимосвязанных подзапросов.
SELECT COUNT(*) FROM Table1 a
WHERE a.Table1id NOT IN (
SELECT b.Table1Id FROM Table2 b WHERE b.id_user = a.id_user
);
Обратите внимание, что подзапрос ссылается на столбец id_user
внешнего запроса. Значение id_user
для каждой строки Table1
может быть разным. Таким образом, результат подзапроса, вероятно, будет другим, в зависимости от текущей строки во внешнем запросе. СУРБД должна выполнять подзапрос много раз, один раз для каждой строки во внешнем запросе.
Пример, который вы протестировали, - это некоррелированный подзапрос. Большинство современных оптимизаторов RDBMS, достойных их соли, должны иметь возможность сказать, когда результат подзапроса не зависит от значений в каждой строке внешнего запроса. В этом случае RDBMS запускает подзапрос за один раз, кэширует его результат и многократно использует его для предиката во внешнем запросе.
PS: В SQL, IN()
называется "предикатом", а не выражением. Предикат является частью языка, который оценивает либо true, либо false, но не обязательно может выполняться независимо как оператор. То есть вы не можете просто запустить это как запрос SQL: "2 IN (1,2,3);" Хотя это допустимый предикат, это не действительный оператор.
Ответ 2
Он будет полностью зависеть от используемой базы данных и точного запроса.
Оптимизаторы запросов очень умны - в вашем примере запроса я ожидаю, что лучшие базы данных смогут использовать те же методы, что и при соединении. Более наивные базы данных могут просто выполнять один и тот же запрос много раз.
Ответ 3
Это зависит от RDBMS
.
См. подробный анализ здесь:
Короче:
-
MySQL
будет оптимизировать запрос к этому:
SELECT COUNT(*)
FROM Table1 t1
WHERE NOT EXISTS
(
SELECT 1
FROM Table2 t2
WHERE t2.id_user = 1
AND t2.Table1ID = t1.Table2ID
)
и запустите внутренний подзапрос в цикле, используя индексный поиск каждый раз.
-
SQL Server
будет использовать MERGE ANTI JOIN
.
Внутренний подзапрос не будет "выполнен" в общем смысле слова, вместо этого результаты как запроса, так и подзапроса будут получены одновременно.
Подробнее см. ссылку выше.
-
Oracle
будет использовать HASH ANTI JOIN
.
Внутренний подзапрос будет выполняться один раз, а хэш-таблица будет построена из набора результатов.
Значения внешнего запроса будут найдены в хэш-таблице.
-
PostgreSQL
будет использовать NOT (HASHED SUBPLAN)
.
Гораздо больше, чем Oracle
.
Обратите внимание, что переписывание запроса следующим образом:
SELECT (
SELECT COUNT(*)
FROM Table1
) -
(
SELECT COUNT(*)
FROM Table2 t2
WHERE (t2.id_user, t2.Table1ID) IN
(
SELECT 1, Table1ID
FROM Table1
)
)
значительно улучшит производительность во всех четырех системах.
Ответ 4
Зависит от оптимизатора. Проверьте точный план запроса для каждого конкретного запроса, чтобы увидеть, как RDBMS фактически выполнит это.
В Oracle, который будет:
EXPLAIN PLAN FOR «your query»
В MySQL или PostgreSQL
EXPLAIN «your query»
Ответ 5
Большинство SQL-систем в наши дни почти всегда будут создавать один и тот же план выполнения для LEFT JOIN, NOT IN и NOT EXISTS
Я бы сказал, посмотрите на свой план выполнения и узнайте: -)
Также, если у вас есть значения NULL для столбца Table1Id, вы не получите никаких данных назад
Ответ 6
Не совсем. Но это масло, чтобы писать такие запросы, используя JOIN
Ответ 7
Да, но выполнение останавливается, как только обработчик запроса "находит" значение, которое вы ищете... Итак, если, например, первая строка во внешнем select имеет Table1Id = 32, и если Table2 имеет запись с TableId = 32, затем как только подзапрос найдет строку в таблице2, где TableId = 32, она останавливается...