这种自我加入是如何工作的



这是sqlzoo.net 的一个问题

这是桌面世界:

+-------------+-----------+---------+|名称|大陆|地区|+-------------+-----------+---------+|阿富汗|亚洲|652230||阿尔巴尼亚|欧洲|2831741||阿尔及利亚|非洲|28748||…|…||+-------------+-----------+---------+

问题:

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

我试图理解的答案是:

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

此代码给出:

+-------------+-----------+--------+|大陆|名称|地区|+-------------+-----------+--------+|非洲|阿尔及利亚| 2381741||大洋洲|澳大利亚|7692024||南美洲|巴西|8515767||北美|加拿大|9984670||亚洲|中国|9596961||加勒比海|古巴|109884||欧洲|法国|640679||欧亚大陆|俄罗斯| 17125242|+-------------+-----------+--------+

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

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

Algeria      2381741
Australia    7692024
South Africa 1221037
New Zealand   268021
/*And to make it a little interesting:*/
Algeria Twin 2381741

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

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

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

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

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

然而,请注意以下内容,这些内容看似等效,但并非如此。

/* 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)

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

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
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)它在封面下的工作方式可能有很大的不同,但效果是我们在这里关注的。

最新更新