我是一个老派的MySQL用户,总是更喜欢JOIN而不是子查询。但是现在每个人都用子查询,我讨厌它;我不知道为什么。

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


当前回答

根据我的观察,就像两种情况,如果一个表的记录少于10万条,那么连接将工作得很快。

但是如果一个表有超过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秒

其他回答

根据我的观察,就像两种情况,如果一个表的记录少于10万条,那么连接将工作得很快。

但是如果一个表有超过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秒

首先,为了比较这两个,首先你应该区分查询和子查询:

一个子查询类,它总是使用连接编写相应的等效查询 不能使用连接重写的子查询类

对于第一类查询,一个好的RDBMS将把联接查询和子查询视为等效的,并将产生相同的查询计划。

现在甚至mysql也这么做了。

尽管如此,有时它并不会,但这并不意味着连接总是会赢-我有在mysql中使用子查询提高性能的情况。(例如,如果有一些东西阻止mysql计划器正确估计成本,如果计划器没有看到连接变量和子查询变量相同,那么子查询可以通过强制某个路径来优于连接)。

结论是,如果您想确定哪一种查询性能更好,就应该同时测试连接和子查询变量。

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

我认为在引用的答案中没有强调的是重复的问题和可能由特定(使用)案例引起的有问题的结果。

(尽管马塞洛·坎托斯提到过)

我将引用斯坦福大学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        |
+------+----------+----------------+----------+

让我们试着找出申请计算机科学专业的学生的平均绩点(不论大学)

使用子查询:

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

It is obvious that the second attempt yields misleading results in our use case, given that it counts duplicates for the computation of the average value. It is also evident that usage of distinct with the join - based statement will not eliminate the problem, given that it will erroneously keep one out of three occurrences of the 3.9 score. The correct case is to account for TWO (2) occurrences of the 3.9 score given that we actually have TWO (2) students with that score that comply with our query criteria.

在某些情况下,除了性能问题,子查询似乎是最安全的方法。

在旧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。

只有当第二个连接表的数据明显多于主表时,才会出现这种差异。我有过这样的经历……

我们有一个用户表,有10万个条目,他们的会员数据(友谊)大约有30万个条目。这是一个join语句,目的是获取好友及其数据,但有很大的延迟。但是当成员表中只有少量数据时,它工作得很好。一旦我们将其更改为使用子查询,它就可以正常工作。

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

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