我是一个老派的MySQL用户,并且始终喜欢JOIN
子查询。但是如今,每个人都使用子查询,而我讨厌它。我不知道为什么
我缺乏理论知识来自行判断是否存在差异。子查询是否与a一样好JOIN
,因此不必担心吗?
我是一个老派的MySQL用户,并且始终喜欢JOIN
子查询。但是如今,每个人都使用子查询,而我讨厌它。我不知道为什么
我缺乏理论知识来自行判断是否存在差异。子查询是否与a一样好JOIN
,因此不必担心吗?
Answers:
取自MySQL手册(13.2.10.11将子查询重写为Joins):
LEFT [OUTER] JOIN可以比同等子查询更快,因为服务器可能可以更好地对其进行优化-这并非仅针对MySQL Server。
因此子查询的速度可能比慢LEFT [OUTER] JOIN
,但我认为子查询的强度是可读性稍高的。
Join
和sub query
语法有所不同,因此可读性我们无法比拟,只要您精通SQL语法,两者的可读性就更高。性能更重要。
子查询是解决以下形式的问题的逻辑上正确的方法:“从A获取事实,以从B获取事实为条件”。在这种情况下,将B留在子查询中比执行联接更具逻辑意义。从实际意义上讲,它也是更安全的,因为您不必因与B的多次比赛而从A获得重复的事实时保持谨慎。
但是,实际上,答案通常取决于性能。一些优化器在进行联接与子查询时会吸柠檬,而另一些则以另一种方式吸柠檬,这是特定于优化器,特定于DBMS版本和特定于查询的。
从历史上看,显式联接通常会获胜,因此联接的既定常识是更好的,但优化器一直都在改进,因此我更喜欢先以逻辑上一致的方式编写查询,然后在性能约束允许的情况下进行重组。
select custid from cust join bought using (custid) where price > 500
。如果客户购买了多件昂贵的商品,您将获得双倍优惠。要解决此问题,select custid from cust where exists (select * from bought where custid = cust.custid and price > 500)
。您可以改用select distinct …
它,但是对于优化器或评估器,通常要做更多的工作。
在大多数情况下,JOIN
s比子查询快,而子查询也要快得多。
在JOIN
RDBMS中,RDBMS可以创建一个更适合您的查询的执行计划,并且可以预测应加载哪些数据以进行处理并节省时间,这与子查询不同,子查询将运行所有查询并加载所有数据以进行处理。
子查询的优点是它们比JOIN
s 更易读:这就是为什么大多数新的SQL人士更喜欢它们的原因;这是简单的方法;但是就性能而言,JOINS在大多数情况下都更好,即使它们也不难阅读。
select * from a where a.x = (select b.x form b where b.id = a.id)
相比,小的子选择的查询时间就非常小。这是一个非常具体的问题,但是在某些情况下,它使您从几个小时转变为几分钟。
使用EXPLAIN可以查看数据库如何对数据执行查询。这个答案有一个很大的“取决于” ...
当PostgreSQL认为一个子查询比另一个查询快时,可以将它重写为一个联接或一个子查询的联接。这完全取决于数据,索引,相关性,数据量,查询等。
在2010年,我会加入这个问题的作者的行列,并会投赞成票JOIN
,但有了更多的经验(尤其是在MySQL中),我可以说:是的,子查询会更好。我在这里阅读了多个答案;一些规定的子查询速度更快,但是缺少很好的解释。我希望我可以提供这个(非常)较晚的答案:
首先,让我说最重要的:子查询有不同形式
第二个重要声明:大小很重要
如果使用子查询,则应了解 DB-Server如何执行子查询。特别是如果子查询被评估一次或每一行! 另一方面,现代的数据库服务器可以进行很多优化。在某些情况下,子查询有助于优化查询,但是较新版本的DB-Server可能会使优化过时。
SELECT moo, (SELECT roger FROM wilco WHERE moo = me) AS bar FROM foo
请注意,会针对的每个结果行执行子查询foo
。
尽可能避免这种情况;这可能会极大地降低您对庞大数据集的查询速度。但是,如果子查询没有引用,foo
则DB服务器可以将其优化为静态内容,并且只能进行一次评估。
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
ORDER BY bar
您可以将子查询嵌套在多个级别中。如果必须对结果进行分组或排序,这可以对庞大的数据集有所帮助。通常,DB-Server为此创建一个临时表,但是有时您不需要对整个表进行排序,而只需要对结果集进行排序。根据表的大小,这可能会提供更好的性能。
子查询不能代替a JOIN
,您不应该这样使用它们(尽管可以)。以我的拙见,正确使用子查询就是快速替换CREATE TEMPORARY TABLE ...
。一个好的子查询会减少您无法在ON
语句中完成的数据集JOIN
。如果子查询具有关键字GROUP BY
或之一,DISTINCT
并且最好不在选择字段或where语句中,则它可能会大大提高性能。
Sub-queries in the Join-statement
:(1)从子查询本身生成派生表可能需要很长时间。(2)结果派生表未建立索引。仅这两个因素就可能大大降低SQL的速度。
10
记录中,因为没有索引,这仍然意味着在联接其他表时查询的数据记录比不包含临时表的查询多9倍。顺便说一句,我之前使用db(MySQL)遇到了这个问题,在我的情况下,使用子查询SELECT list
可能会更快。
EXPLAIN
是,在优化之前,应在查询上使用。set profiling=1
如果使用临时表是瓶颈,那么使用旧表很容易看到。甚至索引也需要处理时间,B树可优化记录查询,但是10条记录表可能比索引数百万条记录的速度快得多。但这取决于多个因素,例如字段大小和类型。
首先,要比较两者,您应该将带有子查询的查询区分开来:
对于头等舱查询,一个好的RDBMS会将联接和子查询视为等效项,并将产生相同的查询计划。
这些天连mysql都可以做到。
尽管如此,有时还是不能,但这并不意味着联接将永远获胜-在mysql中使用子查询时,我的情况有所改善。(例如,如果某些因素阻止mysql计划者正确估计成本,并且如果计划者看不到join-variant和subquery-variant相同,则子查询可以通过强制某个路径来胜过联接)。
结论是,如果您想确保哪个查询的性能更好,则应该针对联接和子查询变量测试您的查询。
对于第二类,该比较没有意义,因为无法使用联接重写那些查询,在这种情况下,子查询是完成所需任务的自然方法,因此您不应歧视它们。
我认为所引用答案中未充分强调的是特定(使用)情况下可能出现的重复和有问题的结果。
(尽管Marcelo Cantos确实提到了它)
我将引用斯坦福大学关于SQL的Lagunita课程中的示例。
+------+--------+------+--------+
| 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 |
+------+----------+----------------+----------+
让我们尝试找到已申请CS
专业的学生的GPA分数(不考虑大学)
使用子查询:
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)具有该分数的学生符合我们的查询条件,因此正确的情况是考虑该分数的两次出现(2)。3.9
似乎在某些情况下,除了任何性能问题之外,子查询是最安全的方法。
可以将许多包含子查询的Transact-SQL语句替换为联接。其他问题只能与子查询一起提出。在Transact-SQL中,包含子查询的语句与不包含子查询的语义等效版本之间通常没有性能差异。但是,在某些情况下必须检查是否存在,联接会产生更好的性能。否则,必须为外部查询的每个结果处理嵌套查询,以确保消除重复项。在这种情况下,联接方法将产生更好的结果。
所以如果你需要类似的东西
select * from t1 where exists select * from t2 where t2.parent=t1.id
尝试改用join。在其他情况下,它没有区别。
我说:创建函数为子查询消除了混乱的问题,并允许您对子查询实施其他逻辑。因此,我建议尽可能为子查询创建函数。
代码混乱是一个大问题,数十年来,业界一直致力于避免代码混乱。
NOT EXISTS
。A 之所以NOT EXISTS
胜出是LEFT OUTER JOIN
因为各种原因:性能,故障安全性(在可空列的情况下)和可读性。sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
在旧的Mambo CMS的大型数据库上运行:
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需要更长的时间。
id
,而不应被命名为catid
?尝试优化我的数据库访问,您的学习会有所帮助。
根据我对两种情况的观察,如果一个表的记录少于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秒
子查询通常用于返回单行作为原子值,尽管它们可以用于通过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不同,在LEFT-JOIN中,即使联接条件在表B中找不到任何匹配记录,您也只想从表A和B中联接东西,等等。
如果您只是担心速度,则必须检查数据库并编写良好的查询,看看性能是否有任何显着差异。
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;
说明显示:
+----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+
| 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的相同查询是:
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正确解释了这些查询。
如果我list_tag
桌上没有INNER JOIN,即我移除了
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为两个查询返回相同数量的行,并且这两个查询的运行速度均相同。
子查询具有即时计算聚合函数的能力。例如,找到这本书的最低价格,并获得以此价格出售的所有书籍。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;
GROUP BY
具有不同表的多个:stackoverflow.com/questions/11415284/…子查询似乎严格地更通用。另请参见MySQL手册:dev.mysql.com/doc/refman/5.7/en/optimizing-subqueries.html | dev.mysql.com/doc/refman/5.7/en/rewriting-subqueries.html
有人说:“某些RDBMS 认为子查询比另一个查询快时,可以重写子查询到联接或联接到子查询。”但是此语句适用于简单情况,当然不适用于带有子查询的复杂查询,而这些查询实际上会导致性能问题。
我只是在考虑相同的问题,但是我在FROM部分使用了子查询。我需要从大表进行连接和查询,“从”表有2800万条记录,但结果只有128个,所以小结果大数据!我在上面使用MAX()函数。
首先,我使用LEFT JOIN是因为我认为这是正确的方法,MySQL可以优化等。第二次只是为了测试,我针对JOIN重写了子选择。
LEFT JOIN运行时:1.12s SUB-SELECT运行时:0.06s
子选择比联接快18倍!就在副词adv。子选择看起来很糟糕,但结果是...
如果要使用join加快查询速度:
对于“内部联接/联接”,请不要使用where条件,而应在“ ON”条件下使用它。例如:
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”中使用它。因此,尝试使用“在哪里”条件