这是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)
注意大写的X
和THIS
,它们是将子查询与查询联系在一起的项。
就函数效果(a)而言,子查询只返回与在查询级别处理的当前行相关的行。
所以这样想吧。在处理大陆Africa
时,子查询基本上是:
SELECT area
FROM world y
WHERE y.continent = 'Africa'
AND area > 0
而且,因为外部查询中有WHERE area >= ALL [[that_sub_query]]
,所以它只会给那些面积至少与该大陆最大面积一样大的行。
或者,更简洁地说,等于最大值,因为在给定的组中,没有一个项目可以同时小于另一个项目,并且大于或等于所有项目。
(a)它在封面下的工作方式可能有很大的不同,但效果是我们在这里关注的。