Как предикат IN работает в SQL? - PullRequest
19 голосов
/ 17 апреля 2009

После подготовки ответа для на этот вопрос Я обнаружил, что не могу подтвердить свой ответ.

В моей первой работе по программированию мне сказали, что запрос в предикате 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?

Ответы [ 7 ]

17 голосов
/ 17 апреля 2009

Вы получили предупреждение о выполнении подзапросов для каждой строки - для коррелированных подзапросов.

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 может отличаться. Таким образом, результат подзапроса, вероятно, будет другим, в зависимости от текущей строки во внешнем запросе. СУБД должна выполнить подзапрос много раз, по одному разу для каждой строки во внешнем запросе.

Пример, который вы протестировали, - некоррелированный подзапрос . Большинство современных оптимизаторов СУБД, достойных их внимания, должны знать, когда результат подзапроса не зависит от значений в каждой строке внешнего запроса. В этом случае СУБД запускает подзапрос один раз, кэширует его результат и многократно использует его для предиката во внешнем запросе.

PS: В SQL IN() называется «предикатом», а не оператором. Предикат - это часть языка, которая оценивается как истина или ложь, но не обязательно может выполняться независимо как оператор. То есть вы не можете просто выполнить это как запрос SQL: "2 IN (1,2,3);" Хотя это допустимый предикат, он не является допустимым утверждением.

8 голосов
/ 17 апреля 2009

Это будет полностью зависеть от базы данных, которую вы используете, и от точного запроса.

Оптимизаторы запросов иногда очень умны - в вашем примере запроса я ожидаю, что лучшие базы данных смогут использовать те же методы, что и при объединении. Более наивные базы данных могут выполнять один и тот же запрос много раз.

5 голосов
/ 17 апреля 2009

Это зависит от RDBMS, о котором идет речь.

Смотрите подробный анализ здесь:

Короче говоря:

  1. 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 голосов
/ 17 апреля 2009

Зависит от оптимизатора. Проверьте точный план запроса для каждого конкретного запроса, чтобы увидеть, как СУБД фактически выполнит это.

В Oracle это будет:

EXPLAIN PLAN FOR «your query»

В MySQL или PostgreSQL

EXPLAIN «your query»
3 голосов
/ 17 апреля 2009

В настоящее время большинство механизмов SQL почти всегда создают один и тот же план выполнения для LEFT JOIN, NOT IN и NOT EXISTS

Я бы сказал, посмотрите на ваш план выполнения и выясните: -)

Также, если у вас есть значения NULL для столбца Table1Id, вы не получите никаких данных обратно

0 голосов
/ 17 апреля 2009

Да, но выполнение останавливается, как только обработчик запросов «находит» искомое значение ... Так, если, например, первая строка во внешнем выборе имеет Table1Id = 32, и если в Table2 есть запись с TableId = 32, затем как только подзапрос находит строку в Table2, где TableId = 32, он останавливается ...

0 голосов
/ 17 апреля 2009

Не совсем. Но писать такие запросы просто без проблем, используя JOIN

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...