mysql - 这个自加入是如何工作的?

标签 mysql sql self-join correlated-subquery

这是来自 sqlzoo.net 的问题

这是桌面世界:

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

The Question:

Find the largest country (by area) in each continent, show the continent, the name and the area:

The answer I am trying to understand is:

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.Continental=x.Continental AND Area>0 是如何工作的?

最佳答案

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

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

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

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

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

关于mysql - 这个自加入是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41560580/

相关文章:

sql - 如何对 PostgreSQL JSON 字段进行排序

mysql - 根据表中的其他条目更新表的列

php - Laravel 多个 where 条件在 whereHas 回调

java - 值没有输入到 mysql 数据库中

php - Laravel 获取我的好友列表查询

r - 如何对包含 AND 和 OR 运算符的语句执行条件连接?

MySQL - 正确的事件计数方法

MySQL 查询 - 获取未插入的 ID 以将新行插入表中

mysql - sql使用union和orderby查询一张表

php - 如何从n个表中只获取n条记录