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

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

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

Ответы [ 18 ]

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

Подзапросы обычно используются для возврата одной строки в качестве атомарного значения, хотя они могут использоваться для сравнения значений с несколькими строками с помощью ключевого слова IN. Они допускаются практически в любой значимой точке оператора SQL, включая список целей, предложение WHERE и т. Д. Простой подзапрос может быть использован в качестве условия поиска. Например, между парой таблиц:

   SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo');

Обратите внимание, что использование оператора нормального значения в результатах подзапроса требует, чтобы было возвращено только одно поле. Если вы заинтересованы в проверке существования одного значения в наборе других значений, используйте IN:

   SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]');

Это, очевидно, отличается от, скажем, LEFT-JOIN, где вы просто хотите объединить материал из таблиц A и B, даже если условие соединения не находит подходящей записи в таблице B и т. Д.

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

11 голосов
/ 17 июня 2015

Подзапросы имеют возможность вычислять функции агрегации на лету. Например. Найдите минимальную цену книги и получите все книги, которые продаются по этой цене. 1) Использование подзапросов:

SELECT titles, price
FROM Books, Orders
WHERE price = 
(SELECT MIN(price)
 FROM Orders) AND (Books.ID=Orders.ID);

2) с помощью JOIN

SELECT MIN(price)
     FROM Orders;
-----------------
2.99

SELECT titles, price
FROM Books b
INNER JOIN  Orders o
ON b.ID = o.ID
WHERE o.price = 2.99;
11 голосов
/ 06 июня 2013

Версия MySQL: 5.5.28-0ubuntu0.12.04.2-log

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

Вот мой запрос с 3 подзапросами:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=43) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=55) IS NULL 
 AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

EXPLAIN показывает:

+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| id | select_type        | table    | type   | possible_keys                                       | key          | key_len | ref                                             | rows | Extra                    |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
|  1 | PRIMARY            | vrl      | index  | PRIMARY                                             | moved_date   | 8       | NULL                                            |  200 | Using where              |
|  1 | PRIMARY            | l        | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  1 | PRIMARY            | vrlih    | eq_ref | PRIMARY                                             | PRIMARY      | 9       | ranker.vrl.list_id,ranker.vrl.ontology_id,const |    1 | Using where              |
|  1 | PRIMARY            | lbs      | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                              |    1 | Using where              |
|  4 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  3 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | list_tag | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.l.list_id,const                          |    1 | Using where; Using index |
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+

тот же запрос с JOINs:

EXPLAIN SELECT vrl.list_id,vrl.ontology_id,vrl.position,l.name AS list_name, vrlih.position AS previous_position, vrl.moved_date 
FROM `vote-ranked-listory` vrl 
INNER JOIN lists l ON l.list_id = vrl.list_id 
INNER JOIN `vote-ranked-list-item-history` vrlih ON vrl.list_id = vrlih.list_id AND vrl.ontology_id=vrlih.ontology_id AND vrlih.type='PREVIOUS_POSITION' 
INNER JOIN list_burial_state lbs ON lbs.list_id = vrl.list_id AND lbs.burial_score < 0.5 
LEFT JOIN list_tag lt1 ON lt1.list_id = vrl.list_id AND lt1.tag_id = 43 
LEFT JOIN list_tag lt2 ON lt2.list_id = vrl.list_id AND lt2.tag_id = 55 
INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 
WHERE vrl.position <= 15 AND l.status='ACTIVE' AND l.is_public=1 AND vrl.ontology_id < 1000000000 
AND lt1.list_id IS NULL AND lt2.tag_id IS NULL 
ORDER BY vrl.moved_date DESC LIMIT 200;

и вывод:

+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                       | key          | key_len | ref                                         | rows | Extra                                        |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | lt3   | ref    | list_tag_key,list_id,tag_id                         | tag_id       | 5       | const                                       | 2386 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | l     | eq_ref | PRIMARY,status,ispublic,idx_lookup,is_public_status | PRIMARY      | 4       | ranker.lt3.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | vrlih | ref    | PRIMARY                                             | PRIMARY      | 4       | ranker.lt3.list_id                          |  103 | Using where                                  |
|  1 | SIMPLE      | vrl   | ref    | PRIMARY                                             | PRIMARY      | 8       | ranker.lt3.list_id,ranker.vrlih.ontology_id |   65 | Using where                                  |
|  1 | SIMPLE      | lt1   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index; Not exists         |
|  1 | SIMPLE      | lbs   | eq_ref | PRIMARY,idx_list_burial_state,burial_score          | PRIMARY      | 4       | ranker.vrl.list_id                          |    1 | Using where                                  |
|  1 | SIMPLE      | lt2   | ref    | list_tag_key,list_id,tag_id                         | list_tag_key | 9       | ranker.lt3.list_id,const                    |    1 | Using where; Using index                     |
+----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+

Сравнение столбца rows показывает разницу, а запрос с JOIN использует Using temporary; Using filesort.

Конечно, когда я выполняю оба запроса, первый выполняется за 0,02 секунды, второй не завершается даже через 1 минуту, поэтому EXPLAIN объяснил эти запросы правильно.

ЕслиУ меня нет INNER JOIN для таблицы list_tag, т.е. если я удаляю

AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL  

из первого запроса и соответственно:

INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403

из второго запроса, то EXPLAIN возвращаетодинаковое количество строк для обоих запросов и оба эти запроса выполняются одинаково быстро.

6 голосов
/ 23 сентября 2018
  • Общее правило состоит в том, что объединения в большинстве случаев быстрее (99%).
  • Чем больше таблиц данных, тем медленнее подзапросы .
  • Чем меньше таблиц данных, тем больше у подзапросов эквивалентная скорость при присоединении .
  • Подзапросы проще, понятнее и легче для чтения.
  • Большинство веб-фреймворков и каркасов приложений, а также их "ORM" и "Активная запись" генерируют запросы с подзапросами , потому что с подзапросами легче разделить ответственность, поддерживать код и т. д.
  • Для небольших веб-сайтов или приложений подзапросы в порядке, но для более крупных веб-сайтов и приложений вам часто придется переписывать сгенерированные запросы в объединять запросов, особенно если запрос использует много подзапросов в запросе.

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

3 голосов
/ 10 марта 2014

Разница наблюдается только тогда, когда вторая объединяющая таблица содержит значительно больше данных, чем первичная таблица. У меня был опыт, как показано ниже ...

У нас была таблица пользователей из ста тысяч записей, а их данные о членстве (дружба) - около трехсот тысяч записей. Это было заявление о присоединении, чтобы взять друзей и их данные, но с большой задержкой. Но это работало нормально, когда в таблице участников было только небольшое количество данных. Как только мы изменили его, чтобы использовать подзапрос, он работал нормально.

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

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

2 голосов
/ 02 февраля 2012

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

0 голосов
/ 24 апреля 2019

Если вы хотите ускорить ваш запрос с помощью соединения:

Для «внутреннего соединения / соединения», Не используйте где условие вместо того, чтобы использовать его в состоянии «ВКЛ». Например:

     select id,name from table1 a  
   join table2 b on a.name=b.name
   where id='123'

 Try,

    select id,name from table1 a  
   join table2 b on a.name=b.name and a.id='123'

Для «левого / правого соединения», Не используйте в состоянии «ВКЛ.», Потому что если вы используете соединение влево / вправо, оно получит все строки для любой таблицы. Итак, попробуйте использовать условие «Где»

0 голосов
/ 23 сентября 2018

Я просто думаю о той же проблеме, но я использую подзапрос в части FROM. Мне нужно подключиться и запросить из больших таблиц, в «ведомой» таблице содержится 28 миллионов записей, но результат всего 128, так что малый результат - большие данные! Я использую функцию MAX ().

Сначала я использую LEFT JOIN, потому что я думаю, что это правильный путь, mysql может оптимизировать и т. Д. Второй раз, просто для тестирования, я переписываю суб-выбор против JOIN.

Время выполнения левого соединения: 1.12 с SUB-SELECT время выполнения: 0,06 с

В 18 раз быстрее выбор, чем объединение! Просто в ад Чокито. Подвыбор выглядит ужасно, но результат ...

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