Join vs. sub-query
我是一个老门派的MySQL用户,总是喜欢使用
如果有什么不同,我缺乏自己判断的理论知识。子查询是否与
子查询是解决"从A中获取事实,以B中的事实为条件"形式问题的逻辑正确方法。在这种情况下,将B插入子查询比进行联接更有逻辑意义。从实际意义上讲,它也更安全,因为您不必担心由于与B的多次比赛而从A中得到重复的事实。
但实际上,答案通常归结于表演。有些乐观主义者在给出一个连接与子查询时会吸吮柠檬,而有些乐观主义者则相反,这是乐观主义者特有的、DBMS版本特有的和查询特有的。
从历史上看,显式连接通常是成功的,因此已经确立的连接更好的智慧,但是乐观主义者一直都在变得更好,所以我更喜欢先以逻辑一致的方式编写查询,然后在性能约束允许的情况下重新构造查询。
在大多数情况下,
在
子查询的好处在于,它们比
摘自mysql手册(13.2.10.11将子查询重写为join):
A LEFT [OUTER] JOIN can be faster than an equivalent subquery because the server might be able to optimize it better—a fact that is not specific to MySQL Server alone.
号
所以子查询可能比
使用explain查看数据库如何对数据执行查询。在这个答案中有一个巨大的"它取决于"…
PostgreSQL可以将子查询重写为join或join-to-subquery,前提是它认为一个比另一个快。这一切都取决于数据、索引、相关性、数据量、查询等。
首先,要比较这两个查询,首先应将查询与子查询区分为:
对于第一类查询,一个好的RDBMS将把联接和子查询视为等价的,并将生成相同的查询计划。
现在连MySQL都这样做了。
尽管如此,有时也不会,但这并不意味着连接总是会赢——我在MySQL中使用子查询时遇到过一些情况,这提高了性能。(例如,如果有什么东西阻碍了MySQL Planner正确估计成本,并且计划器没有看到连接变量和子查询变量相同,那么子查询可以通过强制某个路径来优于连接)。
结论是,如果您想确定哪一个查询的性能更好,那么应该同时测试您的查询的联接和子查询变体。
对于第二个类,比较没有意义,因为这些查询不能使用联接重写,在这些情况下,子查询是完成所需任务的自然方式,您不应该歧视它们。
在2010年,我将加入这个问题的作者,并将强烈投票支持
首先,让我说最重要的一点:子查询有不同的形式
第二个重要声明:规模至关重要
如果使用子查询,您应该知道DB服务器是如何执行子查询的。特别是如果子查询被计算一次或每行一次!另一方面,现代数据库服务器能够进行大量优化。在某些情况下,子查询有助于优化查询,但更新版本的DB服务器可能会使优化过时。
选择字段中的子查询1 | SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo |
请注意,对于来自
1 | SELECT moo FROM foo WHERE bar = (SELECT roger FROM wilco WHERE moo = me) |
号
如果幸运的话,数据库会在内部将其优化为一个
1 2 3 4 5 | SELECT moo, bar FROM foo LEFT JOIN ( SELECT MIN(bar), me FROM wilco GROUP BY me ) ON moo = me |
这很有趣。我们将
1 2 3 4 5 6 7 8 9 | 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服务器为此创建一个临时表,但有时您不需要对整个表进行排序,只需要对结果集进行排序。根据表的大小,这可能提供更好的性能。
结论子查询不能代替
SQL Server的msdn文档显示
Many Transact-SQL statements that include subqueries can be alternatively formulated as joins. Other questions can be posed only with subqueries. In Transact-SQL, there is usually no performance difference between a statement that includes a subquery and a semantically equivalent version that does not. However, in some cases where existence must be checked, a join yields better performance. Otherwise, the nested query must be processed for each result of the outer query to ensure elimination of duplicates. In such cases, a join approach would yield better results.
号
所以如果你需要类似的东西
1 | SELECT * FROM t1 WHERE EXISTS SELECT * FROM t2 WHERE t2.parent=t1.id |
。
尝试改用join。在其他情况下,这没有区别。
我说:为子查询创建函数可以消除混乱的问题,并允许您为子查询实现额外的逻辑。因此,我建议尽可能为子查询创建函数。
代码的混乱是一个大问题,业界几十年来一直在努力避免它。
我认为在引用的答案中被低估的是可能由特定(使用)案例引起的重复和有问题的结果问题。
(尽管马塞洛·坎托斯确实提到过)
我将引用斯坦福大学关于SQL的Lagunita课程中的例子。
学生桌1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | +------+--------+------+--------+ | 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 | +------+--------+------+--------+ |
应用表格
(针对特定大学和专业的申请)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | +------+----------+----------------+----------+ | 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 | +------+----------+----------------+----------+ |
号
让我们试着找出申请过
使用子查询:
1 2 3 4 5 6 7 8 9 10 11 | 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 | +------+ |
此结果集的平均值为:
1 2 3 4 5 6 7 | SELECT avg(GPA) FROM Student WHERE sID IN (SELECT sID FROM Apply WHERE major = 'CS'); +--------------------+ | avg(GPA) | +--------------------+ | 3.6800000000000006 | +--------------------+ |
。
使用联接:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 | +------+ |
此结果集的平均值:
1 2 3 4 5 6 7 | SELECT avg(GPA) FROM Student, Apply WHERE Student.sID = Apply.sID AND Apply.major = 'CS'; +-------------------+ | avg(GPA) | +-------------------+ | 3.714285714285714 | +-------------------+ |
。
很明显,第二次尝试会在我们的用例中产生误导性的结果,因为在计算平均值时,它会计算重复项。很明显,在基于连接的语句中使用
在某些情况下,除了性能问题外,子查询似乎是最安全的方式。
从一个老的mambo cms运行在一个非常大的数据库上:
1 2 3 4 5 6 7 8 9 | SELECT id, alias FROM mos_categories WHERE id IN ( SELECT DISTINCT catid FROM mos_content ); |
0秒
1 2 3 4 5 6 7 | SELECT DISTINCT mos_content.catid, mos_categories.alias FROM mos_content, mos_categories WHERE mos_content.catid = mos_categories.id; |
号
~3秒
一个解释表明,他们检查的行数完全相同,但一个需要3秒,一个接近于瞬间。故事的寓意?如果绩效很重要(不是什么时候?),尝试多种方法,看看哪种方法最快。
还有…
1 2 3 4 5 6 7 | SELECT DISTINCT mos_categories.id, mos_categories.alias FROM mos_content, mos_categories WHERE mos_content.catid = mos_categories.id; |
0秒
同样,相同的结果,相同的行数被检查。我的猜测是,不同的mos_content.catid要比不同的mos_categories.id花更长的时间来计算。
子查询通常用于返回一行作为原子值,尽管它们可以用于使用in关键字将值与多行进行比较。它们在SQL语句中几乎任何有意义的点都可以使用,包括目标列表、WHERE子句等。一个简单的子查询可以用作搜索条件。例如,在一对表之间:
1 | SELECT title FROM books WHERE author_id = (SELECT id FROM authors WHERE last_name = 'Bar' AND first_name = 'Foo'); |
请注意,对子查询的结果使用普通值运算符只需要返回一个字段。如果您有兴趣检查一组其他值中是否存在单个值,请使用:
1 | SELECT title FROM books WHERE author_id IN (SELECT id FROM authors WHERE last_name ~ '^[A-E]'); |
号
这显然不同于左联接,即您只想联接表A和B中的内容,即使联接条件在表B中找不到任何匹配的记录,等等。
如果您只是担心速度,那么您必须检查数据库并编写一个好的查询,看看性能是否有任何显著的差异。
根据我的观察,就像两个例子,如果一个表的记录少于100000条,那么连接将很快工作。
但是,如果一个表有超过100000条记录,那么子查询是最好的结果。
我在下面的查询中创建了一个有500000条记录的表,其结果时间如下
1 2 3 | SELECT * FROM crv.workorder_details wd INNER JOIN crv.workorder wr ON wr.workorder_id = wd.workorder_id; |
。
Result : 13.3 Seconds
号
1 2 3 | SELECT * FROM crv.workorder_details WHERE workorder_id IN (SELECT workorder_id FROM crv.workorder) |
Result : 1.65 Seconds
号
mysql版本:5.5.28-0ubuntu0.12.04.2-log
我也觉得join总是比mysql中的子查询好,但是explain是一种更好的判断方法。下面是一个例子,其中子查询比联接工作得更好。
以下是我的3个子查询:
1 2 3 4 5 6 7 8 9 10 | 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; |
解释显示:
1 2 3 4 5 6 7 8 9 10 11 | +----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+ | 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 | +----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+ |
。
使用join的相同查询是:
1 2 3 4 5 6 7 8 9 10 11 | 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; |
。
输出为:
1 2 3 4 5 6 7 8 9 10 11 | +----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+ | 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 | +----+-------------+-------+--------+-----------------------------------------------------+--------------+---------+---------------------------------------------+------+----------------------------------------------+ |
通过比较
当然,当我同时运行这两个查询时,第一个查询是在0.02秒内完成的,第二个查询即使在1分钟后也不会完成,所以请解释清楚这些查询。
如果我在
1 | AND (SELECT list_id FROM list_tag WHERE list_id=l.list_id AND tag_id=246403) IS NOT NULL |
。
从第一个查询开始,相应地:
1 | INNER JOIN list_tag lt3 ON lt3.list_id = vrl.list_id AND lt3.tag_id = 246403 |
在第二个查询中,explain为两个查询返回相同数量的行,并且这两个查询的运行速度相同。
子查询能够即时计算聚合函数。例如,找到该书的最低价格,并获取以该价格出售的所有书籍。1)使用子查询:
1 2 3 4 5 | SELECT titles, price FROM Books, Orders WHERE price = (SELECT MIN(price) FROM Orders) AND (Books.ID=Orders.ID); |
。
2)使用连接
1 2 3 4 5 6 7 8 9 10 | 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; |
号
- 一般来说,连接在大多数情况下更快(99%)。
- 数据表越多,子查询就越慢。
- 数据表的数量越少,子查询的速度与联接速度相同。
- 子查询更简单、更易于理解和阅读。
- 大多数web和app框架及其"orm"和"active record"使用子查询生成查询,因为使用子查询更容易划分责任、维护代码等。
- 对于较小的网站或应用程序,子查询是可以的,但是对于较大的网站和应用程序,您通常需要重写生成的查询来联接查询,特别是如果查询中使用了许多子查询。
有人说"有些RDBMS认为一个子查询比另一个子查询快时,就可以将它重写为一个join或一个join到一个子查询。"但这句话适用于简单的情况,当然不适用于带有子查询的复杂查询,因为子查询实际上会导致性能问题。
只有当第二个联接表的数据明显多于主表时,才会看到这种差异。我有过如下的经历…
我们有一个用户表,有10万个条目,他们的成员数据(友谊)大约有30万个条目。这是一个连接语句,用来接收朋友和他们的数据,但是有很大的延迟。但在成员表中只有少量数据的情况下,它工作得很好。一旦我们将其更改为使用子查询,它就工作得很好。
但同时,联接查询正在处理的其他表的条目数比主表少。
所以我认为join和sub-query语句工作得很好,这取决于数据和情况。
现在,许多DBS可以优化子查询和联接。因此,您只需使用explain检查查询,并查看哪个查询更快。如果在性能上没有太大的差异,我更喜欢使用子查询,因为它们简单易懂。
我只是在想同样的问题,但是我在from部分使用了子查询。我需要从大表进行连接和查询,"从"表有2800万条记录,但结果只有128条那么小的结果大数据!我正在对它使用max()函数。
首先,我使用左连接,因为我认为这是正确的方法,MySQL可以优化等等。第二次只是为了测试,我重写以针对连接进行子选择。
左连接运行时:1.12s子选择运行时间:0.06s
比加入快18倍!就在"乔基托"副词中,副词看起来很糟糕,但结果…
如果要使用join加快查询速度:
对于"内部联接/联接",不要使用Where条件,而是在"on"条件下使用它。如:
1 2 3 4 5 6 7 8 | 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' |
对于"左/右连接",不要在"on"条件下使用,因为如果使用左/右联接,它将获取任何一个表的所有行。因此,在"on"中不使用它。所以,尝试使用"Where"条件