join与子查询

我是一个老派的MySQL用户,并一直首选JOIN子查询。 但是现在每个人都使用子查询,我讨厌它,我不知道为什么。

我缺乏理论知识来判断自己是否有任何区别。 子查询和JOIN一样好,因此没有什么可担心的?

从手册中获取( 13.2.10.11将子查询重写为Joins ):

LEFT [OUTER] JOIN可以比等效的子查询更快,因为服务器可能能够更好地优化它 – 这个事实并不是仅针对MySQL服务器。

所以子查询可以比左[OUTER] JOINS慢,但在我看来,它们的强度稍高一些。

子查询是解决“从A得到事实,以B事实为条件”的forms问题的逻辑正确的方法。 在这种情况下,在子查询中粘贴B比在连接中更合理。 从实际意义上来说,这也是比较安全的,因为您不必因为与B的多重匹配而获得A的重复事实而保持谨慎。

但实际上,答案通常归结为性能。 有些优化器在给出一个连接和一个子查询的时候吸引了柠檬,有些则相反,柠檬是吸引人的,这是针对特定优化器的,特定于DBMS的版本和针对查询的。

从历史上看,显式连接通常是赢的,因此连接的已有智慧总是比较好,但是优化器总是变得越来越好,所以我宁愿先以逻辑上一致的方式编写查询,然后在性能约束条件下进行重构。

在大多数情况下, JOIN比子查询更快,子查询速度更快。

JOIN RDBMS可以创build一个更适合您的查询的执行计划,并且可以预测应该加载哪些数据进行处理并节省时间,与子查询不同,子查询将运行所有查询并加载所有数据以执行处理。

子查询中的好处在于它们比JOIN更具可读性:这就是为什么大多数新SQL人员更喜欢它们; 这是简单的方法; 但是当涉及到性能时,JOINS在大多数情况下都更好,尽pipe它们也不难读取。

使用EXPLAIN来查看数据库如何对数据执行查询。 有一个巨大的“这取决于”在这个答案…

当PostgreSQL认为某个子查询比另一个更快时,它可以重写一个子查询到一个子查询。 这一切都取决于数据,索引,相关性,数据量,查询等。

首先,为了比较两者,首先应该将查询与子查询区分开来:

  1. 一类子查询总是具有用连接书写的相应的等价查询
  2. 一类不能用连接重写的子查询

对于第一类查询,一个好的RDBMS将会看到连接和子查询是等价的,并且会产生相同的查询计划。

这些天甚至MySQL做到这一点。

不过,有时候并不是这样,但是这并不意味着连接总是会赢 – 我曾经在mysql中使用子查询的时候提高了性能。 (例如,如果有某些东西阻止mysql规划师正确估计成本,并且规划者没有看到连接variables和子查询variables相同,那么通过强制某个path,子查询可以胜过连接。

结论是,如果你想确定哪一个更好,你应该testing你的查询来join和子查询变种。

对于第二类 ,比较是没有意义的,因为这些查询不能使用连接重写,在这种情况下,子查询是自然的方式来完成所需的任务,你不应该歧视他们。

SQL Server的MSDN文档说

包含子查询的许多Transact-SQL语句可以另外定义为连接。 其他问题只能用子查询来提出。 在Transact-SQL中,包含子查询的语句与不包含语义的语义版本之间通常没有性能差异。 但是,在一些需要检查存在的情况下,联接会产生更好的性能。 否则,必须为外部查询的每个结果处理嵌套查询,以确保消除重复。 在这种情况下,join方式会产生更好的结果。

所以如果你需要类似的东西

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

尝试使用连接。 在其他情况下,这没有什么区别。

我说:为子查询创build函数消除了cluttter的问题,并允许您为子查询实现额外的逻辑。 所以我build议尽可能为子查询创build函数。

代码混乱是一个大问题,业界已经努力避免了几十年。

从一个旧的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秒

一个EXPLAIN显示他们检查完全相同的行数,但是一个需要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所花费的时间要长得多。

子查询能够快速计算聚合函数。 例如寻找书的最低价格,并获得与这个价格出售的所有书籍。 1)使用子查询:

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

2)使用JOINs

 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; 

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 | +----+--------------------+----------+--------+-----------------------------------------------------+--------------+---------+-------------------------------------------------+------+--------------------------+ 

与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 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将为这两个查询返回相同数量的行,并且这两个查询的运行速度也是相同的。

我认为在引用的答案中被忽视的是具体(使用)案例可能产生的重复和有问题的结果的问题。

(尽pipeMarcelo Cantos确实提到过)

我将举一个斯坦福大学的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 | +------+----------+----------------+----------+ 

让我们试图找出适用于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 | +-------------------+ 

很显然,第二次尝试会在我们的用例中产生令人误解的结果,因为它计算的是平均值的计算重复。 同样显而易见的是,使用与基于连接的语句不同的特征并不能消除这个问题,因为它将错误地保留3.9分中的三分之一。 如果我们实际上有两(2)名学生的分数符合我们的查询标准,那么正确的例子是考虑到3.9分的两(2)次出现。

看来在某些情况下,除了任何性能问题之外,子查询是最安全的方法。

子查询通常用于将单个行作为primefaces值返回,尽pipe它们可能用于使用IN关键字将值与多行进行比较。 它们几乎允许在SQL语句中的任何有意义的位置,包括目标列表,WHERE子句等等。 一个简单的子查询可以用作search条件。 例如,在一对表之间:

  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 ~ '^[AE]'); 

这与LEFT-JOIN明显不同,即使连接条件在表B中找不到任何匹配的logging,也只是想join表A和B中的内容。

如果你只是担心速度问题,那么你需要检查你的数据库并写出一个好的查询,看看是否有任何显着的性能差异。

根据我的观察,就像两个案例一样,如果一张表的数量less于10万条,那么这个连接就会工作的很快。

但是如果一个表有超过10万个表,那么子查询是最好的结果。

我有一个表有500,000条logging,我创build下面的查询,其结果时间就像

 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秒

现在,许多dbs可以优化子查询和连接。 因此,您只需使用解释来检查您的查询,并查看哪一个更快。 如果性能差别不大,我更喜欢使用子查询,因为它们简单易懂。

只有在第二个连接表具有比主表更多的数据时,才会看到差异。 我有一个像下面的经验…

我们有一个用户表十万条和他们的会员资料(友谊)约三十万条目。 这是一个join声明,以便收集朋友和他们的数据,但很拖延。 但是,在成员资格表中只有less量数据的地方工作正常。 一旦我们改变它使用子查询它工作正常。

但与此同时,连接查询正在处理其他具有比主表更less的条目的表。

所以我认为连接和子查询语句工作正常,这取决于数据和情况。

连接和子查询都用于将来自不同表的数据合并为一个结果。 它们有许多相似之处和不同之处。可以使用子查询来返回标量(单个)值或行集; 而连接则用于返回行。子查询的常用用途可能是计算查询中使用的汇总值。 比如我们可以使用子查询来帮助我们获得所有产品的平均产品价格大于平均水平。 例如:

 SELECT ProductID, Name, ListPrice, (SELECT AVG(ListPrice) FROM Production.Product ) AS AvgListPrice FROM Production.Product WHERE ListPrice > (SELECT AVG(ListPrice) FROM Production.Product ) 

在这个SELECT语句中有两个子查询。 第一个目的是显示所有产品的平均清单价格,第二个目的是过滤掉小于或等于平均清单价格的产品。 与连接的主要目的是基于匹配条件组合来自一个或多个表的行的连接进行对比。 比如我们可以使用一个连接显示产品名称和型号。

 SELECT Product.Name, ProductModel.Name AS ModelName FROM Production.product INNER JOIN Production.ProductModel ON Product.ProductModelID = ProductModel.ProductModelID 

在这个语句中,我们使用INNER JOIN来匹配来自Product和ProductModel表的行。 请注意,ProducModel.Name列可在整个查询中使用。然后,select语句可使用组合的行集来显示,筛选或按列组合。这与子查询不同。 在那里子查询返回一个结果,立即使用。注意他join是select语句的一个组成部分。 它不能作为一个子查询可以自己站立。