Присоединиться против подзапроса - PullRequest
740 голосов
/ 05 апреля 2010

Я - пользователь MySQL старой школы и всегда предпочитал JOIN подзапросу. Но в настоящее время каждый использует подзапрос, и я ненавижу его; Я не знаю почему.

Мне не хватает теоретических знаний, чтобы судить самому, есть ли разница. Подзапрос так же хорош, как JOIN и, следовательно, не о чем беспокоиться?

Ответы [ 18 ]

763 голосов
/ 05 апреля 2010

Подзапросы - это логически правильный способ решения задач в форме «Получить факты из A, при условии наличия фактов из B». В таких случаях логичнее вставлять B в подзапрос, чем объединение. Это также более безопасно в практическом смысле, так как вам не нужно быть осторожным в получении дублированных фактов из A из-за нескольких совпадений с B.

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

Исторически явные объединения обычно выигрывают, поэтому установившаяся мудрость в том, что объединения лучше, но оптимизаторы все время улучшаются, и поэтому я предпочитаю сначала писать запросы логически согласованным образом, а затем реструктурировать, если этого требуют ограничения производительности .

347 голосов
/ 05 апреля 2010

В большинстве случаев JOIN s быстрее, чем подзапросы, и очень редко подзапрос работает быстрее.

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

Хорошая вещь в подзапросах состоит в том, что они более читабельны, чем JOIN s: именно поэтому большинство новых людей SQL предпочитают их; это простой способ; но когда дело доходит до производительности, JOINS лучше в большинстве случаев, хотя их тоже нетрудно прочитать.

147 голосов
/ 20 сентября 2016

Взято из руководства MySQL ( 13.2.10.11 Перезапись подзапросов как объединений ):

LEFT [OUTER] JOIN может быть быстрее, чем эквивалентный подзапрос, потому что сервер может быть в состоянии оптимизировать его лучше - факт, который не относится только к MySQL Server.

Так что подзапросы могут быть медленнее, чем LEFT [OUTER] JOIN, но, на мой взгляд, их сила немного выше читаемости.

122 голосов
/ 05 апреля 2010

Используйте EXPLAIN, чтобы увидеть, как ваша база данных выполняет запрос к вашим данным. В этом ответе огромное "все зависит" ...

PostgreSQL может переписать подзапрос в объединение или в присоединение к подзапросу, когда он считает, что один быстрее другого. Все зависит от данных, индексов, корреляции, количества данных, запросов и т. Д.

41 голосов
/ 28 мая 2010

Прежде всего, чтобы сравнить два первых, вы должны различать запросы с подзапросами:

  1. класс подзапросов, у которых всегда есть соответствующий эквивалентный запрос, написанный с объединениями
  2. класс подзапросов, которые нельзя переписать с помощью объединений

Для первого класса запросов хорошая СУБД будет рассматривать соединения и подзапросы как эквивалентные и будет создавать одинаковые планы запросов.

В наши дни даже MySQL делает это.

Тем не менее, иногда это не так, но это не значит, что объединения всегда будут выигрывать - у меня были случаи, когда использование подзапросов в MySQL улучшало производительность. (Например, если что-то мешает планировщику mysql правильно оценить стоимость, и если планировщик не видит вариант соединения и вариант подзапроса как один и тот же, тогда подзапросы могут превзойти объединения, форсируя определенный путь).

Вывод заключается в том, что вы должны проверить свои запросы как для вариантов соединения, так и для вариантов подзапросов, если вы хотите быть уверены, какой из них будет работать лучше.

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

37 голосов
/ 09 апреля 2018

В 2010 году я присоединился бы к автору этого вопроса и решительно проголосовал бы за JOIN. Но с гораздо большим опытом (особенно в MySQL) я могу сказать: да, подзапросы могут быть лучше. Я прочитал несколько ответов здесь. Некоторые заявили, что подзапросы выполняются быстрее, но им не хватает хорошего объяснения Я надеюсь, что смогу дать один (очень) поздний ответ:

Прежде всего, позвольте мне сказать самое важное: Существуют различные формы подзапросов

И второе важное утверждение: Размер имеет значение

Если вы используете подзапросы, вы должны знать , как DB-сервер выполняет подзапрос. Особенно , если подзапрос оценивается один раз или для каждой строки! С другой стороны, современный DB-сервер способен многое оптимизировать. В некоторых случаях подзапрос помогает оптимизировать запрос, но более новая версия DB-Server может сделать оптимизацию устаревшей.

Подзапросы в полях выбора

SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo

Имейте в виду, что подзапрос выполняется для каждой результирующей строки из foo. Избегайте этого, если это возможно, это может значительно замедлить ваш запрос к огромным наборам данных. Но если подзапрос не имеет ссылки на foo, он может быть оптимизирован DB-сервером как статический контент и может быть оценен только один раз.

Подзапросы в операторе Where

SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me)

Если вам повезет, БД оптимизирует это внутренне до JOIN. Если нет, ваш запрос станет очень, очень медленным для огромных наборов данных, потому что он будет выполнять подзапрос для каждой строки в foo, а не только результаты, как в типе выбора.

Подзапросы в операторе соединения

SELECT moo, bar 
  FROM foo 
    LEFT JOIN (
      SELECT MIN(bar), me FROM wilco GROUP BY me
    ) ON moo = me

Это интересно. Мы объединяем JOIN с подзапросом. И здесь мы получаем реальную силу подзапросов. Представьте себе набор данных с миллионами строк в wilco, но только с несколькими отдельными me. Вместо того, чтобы объединяться с огромным столом, теперь у нас есть меньшая временная таблица, с которой можно соединиться. Это может привести к гораздо более быстрым запросам, в зависимости от размера базы данных. Вы можете получить тот же эффект с CREATE TEMPORARY TABLE ... и INSERT INTO ... SELECT ..., что может обеспечить лучшую читаемость для очень сложных запросов (но может блокировать наборы данных на повторяющемся уровне изоляции для чтения).

Вложенные подзапросы

SELECT moo, bar
  FROM (
    SELECT moo, CONCAT(roger, wilco) AS bar
      FROM foo
      GROUP BY moo
      HAVING bar LIKE 'SpaceQ%'
  ) AS temp_foo
  GROUP BY bar
  ORDER BY bar

Вы можете вкладывать подзапросы на нескольких уровнях. Это может помочь в огромных наборах данных, если вам нужно сгруппировать или отсортировать результаты. Обычно DB-Server создает временную таблицу для этого, но иногда вам не нужно сортировать всю таблицу, а только набор результатов. Это может обеспечить гораздо лучшую производительность в зависимости от размера таблицы.

Заключение

Подзапросы не заменяют JOIN, и вы не должны использовать их таким образом (хотя это возможно). По моему скромному мнению, правильное использование подзапроса - это использование в качестве быстрой замены CREATE TEMPORARY TABLE .... Хороший подзапрос уменьшает набор данных таким способом, которого вы не можете выполнить с помощью ON оператора JOIN. Если подзапрос имеет одно из ключевых слов GROUP BY или DISTINCT и предпочтительно не находится в полях выбора или операторе where, то это может значительно повысить производительность.

22 голосов
/ 16 ноября 2011

Документация MSDN для SQL Server гласит

Многие операторы Transact-SQL, которые включают подзапросы, могут быть альтернативно сформулированы как объединения. Другие вопросы могут быть заданы только с подзапросами. В Transact-SQL обычно нет разницы в производительности между оператором, который включает подзапрос, и семантически эквивалентной версией, которая этого не делает. Однако в некоторых случаях, когда необходимо проверять существование, объединение дает лучшую производительность. В противном случае вложенный запрос должен обрабатываться для каждого результата внешнего запроса, чтобы гарантировать удаление дубликатов. В таких случаях подход объединения даст лучшие результаты.

так что если вам нужно что-то вроде

select * from t1 where exists select * from t2 where t2.parent=t1.id

вместо этого попробуйте использовать соединение. В других случаях это не имеет значения.

Я говорю: создание функций для подзапросов устраняет проблему беспорядка и позволяет реализовать дополнительную логику для подзапросов. Поэтому я рекомендую по возможности создавать функции для подзапросов.

Беспорядок в коде - большая проблема, и отрасль работает над тем, чтобы ее избегать десятилетиями.

20 голосов
/ 19 октября 2016

Я думаю, что в приведенных ответах недооценивается проблема дубликатов и проблематичных результатов, которые могут возникнуть в конкретных (использования) случаях.

(хотя Марсело Кантос упоминает об этом)

Я приведу пример из курсов Lagunita Стэнфорда по SQL.

Студенческий стол

+------+--------+------+--------+
| sID  | sName  | GPA  | sizeHS |
+------+--------+------+--------+
|  123 | Amy    |  3.9 |   1000 |
|  234 | Bob    |  3.6 |   1500 |
|  345 | Craig  |  3.5 |    500 |
|  456 | Doris  |  3.9 |   1000 |
|  567 | Edward |  2.9 |   2000 |
|  678 | Fay    |  3.8 |    200 |
|  789 | Gary   |  3.4 |    800 |
|  987 | Helen  |  3.7 |    800 |
|  876 | Irene  |  3.9 |    400 |
|  765 | Jay    |  2.9 |   1500 |
|  654 | Amy    |  3.9 |   1000 |
|  543 | Craig  |  3.4 |   2000 |
+------+--------+------+--------+

Применить таблицу

(заявки, поданные в конкретные университеты и специальности)

+------+----------+----------------+----------+
| sID  | cName    | major          | decision |
+------+----------+----------------+----------+
|  123 | Stanford | CS             | Y        |
|  123 | Stanford | EE             | N        |
|  123 | Berkeley | CS             | Y        |
|  123 | Cornell  | EE             | Y        |
|  234 | Berkeley | biology        | N        |
|  345 | MIT      | bioengineering | Y        |
|  345 | Cornell  | bioengineering | N        |
|  345 | Cornell  | CS             | Y        |
|  345 | Cornell  | EE             | N        |
|  678 | Stanford | history        | Y        |
|  987 | Stanford | CS             | Y        |
|  987 | Berkeley | CS             | Y        |
|  876 | Stanford | CS             | N        |
|  876 | MIT      | biology        | Y        |
|  876 | MIT      | marine biology | N        |
|  765 | Stanford | history        | Y        |
|  765 | Cornell  | history        | N        |
|  765 | Cornell  | psychology     | Y        |
|  543 | MIT      | CS             | N        |
+------+----------+----------------+----------+

Давайте попробуем найти баллы GPA для студентов, которые подали заявление на CS специальность (независимо от университета)

Использование подзапроса:

select GPA from Student where sID in (select sID from Apply where major = 'CS');

+------+
| GPA  |
+------+
|  3.9 |
|  3.5 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

Среднее значение для этого набора результатов:

select avg(GPA) from Student where sID in (select sID from Apply where major = 'CS');

+--------------------+
| avg(GPA)           |
+--------------------+
| 3.6800000000000006 |
+--------------------+

Использование объединения:

select GPA from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+------+
| GPA  |
+------+
|  3.9 |
|  3.9 |
|  3.5 |
|  3.7 |
|  3.7 |
|  3.9 |
|  3.4 |
+------+

среднее значение для этого набора результатов:

select avg(GPA) from Student, Apply where Student.sID = Apply.sID and Apply.major = 'CS';

+-------------------+
| avg(GPA)          |
+-------------------+
| 3.714285714285714 |
+-------------------+

Очевидно, что вторая попытка дает неверные результаты в нашем случае использования, учитывая, что она подсчитывает дубликаты для вычисления среднего значения. Также очевидно, что использование distinct с оператором на основе соединения не устранит проблему, учитывая, что оно будет ошибочно удерживать одно из трех вхождений 3.9. Правильный случай - учесть ДВА (2) вхождений из 3.9 баллов, учитывая, что у нас на самом деле есть ДВА (2) студентов с таким баллом, которые соответствуют нашим критериям запроса.

Похоже, что в некоторых случаях подзапрос является наиболее безопасным способом, помимо проблем с производительностью.

16 голосов
/ 21 октября 2011

Запуск на очень большой базе данных из старой CMS Mambo:

SELECT id, alias
FROM
  mos_categories
WHERE
  id IN (
    SELECT
      DISTINCT catid
    FROM mos_content
  );

0 секунд

SELECT
  DISTINCT mos_content.catid,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

~ 3 секунды

Объяснение показывает, что они проверяютстолько же строк, но одна занимает 3 секунды, а одна почти мгновенная.Мораль истории?Если производительность важна (когда не так?), Попробуйте ее несколькими способами и посмотрите, какой из них самый быстрый.

И ...

SELECT
  DISTINCT mos_categories.id,
  mos_categories.alias
FROM
  mos_content, mos_categories
WHERE
  mos_content.catid = mos_categories.id;

0 секунд

Опять те же результаты, такое же количество исследованных строк.Я предполагаю, что DISTINCT mos_content.catid требует гораздо больше времени, чем DISTINCT mos_categories.id.

12 голосов
/ 10 апреля 2017

Согласно моим наблюдениям, как в двух случаях, если в таблице менее 100 000 записей, объединение будет работать быстро.

Но в случае, если в таблице содержится более 100 000 записей, наилучшим результатом будет подзапрос.

У меня есть одна таблица, в которой 500 000 записей, которые я создал ниже по запросу, и время ее выполнения равно

SELECT * 
FROM crv.workorder_details wd 
inner join  crv.workorder wr on wr.workorder_id = wd.workorder_id;

Результат: 13,3 секунды

select * 
from crv.workorder_details 
where workorder_id in (select workorder_id from crv.workorder)

Результат: 1,65 секунды

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