这个自我加入如何运作?

时间:2017-01-10 03:17:17

标签: mysql sql self-join correlated-subquery

这是sqlzoo.net的问题

以下是表格世界:

+-------------+-----------+---------+
|    name     | continent |  area   |
+-------------+-----------+---------+
| Afghanistan | Asia      | 652230  |
| Albania     | Europe    | 2831741 |
| Algeria     | Africa    | 28748   |
| ...         | ...       | ...     |
+-------------+-----------+---------+

问题:

找到每个大洲最大的国家(按地区),显示大陆,名称和地区:

我想要理解的答案是:

SELECT continent, name, area 
FROM world x
WHERE area >= ALL (SELECT area 
                   FROM world y
                   WHERE y.continent=x.continent
                   AND area>0)

此代码给出:

+-------------+-----------+--------+
| continent   |   name    |  area  |
+-------------+-----------+--------+
|Africa       | Algeria   | 2381741|
|Oceania      | Australia | 7692024|
|South America| Brazil    | 8515767|
|North America| Canada    | 9984670|
|Asia         | China     | 9596961|
|Caribbean    | Cuba      |  109884|
|Europe       | France    |  640679|
|Eurasia      | Russia    |17125242|
+-------------+-----------+--------+

我不明白这是如何运作的。我认为内部选择应该产生一个包含所有区域的表,而外部选择只选择最大的(>=)。但它如何过滤到一个似乎已被大陆分组的列表? y.continent=x.continent AND area>0如何运作?

2 个答案:

答案 0 :(得分:1)

SELECT THIS, name, area 
FROM world X
WHERE area >= ALL (SELECT area 
                   FROM world y
                   WHERE y.continent = X.THIS
                   AND area > 0)

请注意大写的XTHIS,它们是将子查询与查询联系在一起的项目。

就功能效果(a)而言,子查询仅返回与在查询级别处理的当前行相关的行。

所以这样想吧。在处理大陆Africa时,子查询基本上是:

SELECT area
FROM   world y
WHERE  y.continent = 'Africa'
  AND  area > 0

并且,因为外部查询中有WHERE area >= ALL [[that_sub_query]],所以它只会为您提供区域至少与该大陆最大区域一样大的行。

或者,更简洁地说,等于到最大值,因为在给定的组中,没有一个项目可以同时小于另一个项目,并且大于或等于它们。

(a)它如何在幕后工作可能非常不同,但效果是我们在这里所关注的。

答案 1 :(得分:1)

为了解释,我假设一个只有5个国家的世界表如下:

+-------------+--------------+--------+
| continent   |   name       |  area  |
+-------------+--------------+--------+
|Africa       | Algeria      | 2381741| >= ALL( /*y.continent='Africa'*/
                                               2381741, /*Alegria*/
                                               1221037, /*South Africa*/
                                               2381741) /*Alegria Twin*/
|Oceania      | Australia    | 7692024| >= ALL( /*y.continent='Oceania'*/
                                               7692024, /*Australia*/
                                               268021)  /*New Zealand*/
|Africa       | South Africa | 1221037| >= ALL( /*y.continent='Africa'*/
                                               2381741, /*Alegria*/
                                               1221037, /*South Africa*/
                                               2381741) /*Alegria Twin*/
|Oceania      | New Zealand  |  268021| >= ALL( /*y.continent='Oceania'*/
                                               7692024, /*Australia*/
                                               268021)  /*New Zealand*/
|Africa       | Algeria Twin | 2381741| >= ALL( /*y.continent='Africa'*/
                                               2381741, /*Alegria*/
                                               1221037, /*South Africa*/
                                               2381741) /*Alegria Twin*/
+-------------+--------------+--------+

子查询与基本查询的每一行匹配,一次一个。这就是所谓的相关子查询。虽然相关的子查询运行良好,但它们通常被认为是危险的,因为如果优化器无法找出更有效,等效的结构,它们往往会产生较差的性能特征。

下表说明了如何评估数据的逻辑视图。请注意,数据库的查询引擎可能能够在内部将计划转换为数学上等效的,但效率更高。

>=

从上面可以看出,第1,2和5行是>=所有子查询区域。所以保留这些,而其他行则被丢弃。

请注意,有几种方法可以编写将产生完全相同结果的子查询。

成为=非洲大陆上的所有区域与非洲大陆上的WHERE area = ( SELECT MAX(y.area) FROM world y WHERE y.continent=x.continent) 区域相同。

WHERE area = ( SELECT y.area
               FROM   world y
               WHERE  y.continent=x.continent
               ORDER BY y.area DESC LIMIT 1)

获得最大值的另一种方法是在按区域DESC排序时获取第一行。

/* The problem here is that only 1 Algeria will happen to be 
   first in the sub-query. Meaning 1 row will be missing from 
   the final result set. */
WHERE name = ( SELECT y.name
               FROM   world y
               WHERE  y.continent=x.continent
               ORDER BY y.area DESC LIMIT 1)

但是,请注意以下似乎相同的内容,但不是

SELECT  x.contient, x.name, x.area
FROM    world x
        INNER JOIN (
            SELECT MAX(y.area) as max_area, y.continent
            FROM   world y
            GROUP BY y.continent
        ) z ON
            x.continent = z.continent
        AND x.area = z.max_area

最后,我提到相关的子查询可能存在性能问题。因此,如果可以的话,通常建议考虑将相关子查询重写为直接连接到FROM子句中的子查询的子查询。 E.g。

{{1}}
相关问题