GROUP BY 子句中的条件选择

dze*_*kob 2 mysql group-by greatest-n-per-group mysql-8.0

我是一个 SQL 初学者,遇到了以下问题。考虑一个包含国家、该国家内的城市及其人口数据的表格:

CREATE TABLE cities (
    country VARCHAR(20),
    city VARCHAR(20),
    population INT
);

INSERT INTO cities VALUES
   ('Italy', 'Milano', 1000000), 
   ('Italy', 'Rome', 2000000), 
   ('Italy', 'Bologna', 800000), 
   ('Poland', 'Warszawa', 1000000), 
   ('Poland', 'Wroclaw', 700000);
Run Code Online (Sandbox Code Playgroud)

我想编写一个查询,返回国家/地区名称及其最大城市的人口和城市名称本身。前两个字段使用 很简单GROUP BY。但是,我不知道如何包含最大城市的名称。我试过:

SELECT 
    country, MAX(population), city
FROM
    cities
WHERE
    population = (SELECT 
            MAX(population)
        FROM
            cities
        )
GROUP BY country, city;
Run Code Online (Sandbox Code Playgroud)

但这只选择了一项记录(有关罗马的记录)。我希望查询为每个国家/地区组返回一条记录,即本例中为罗马和华沙。条件WHERE不能是:

population = (SELECT 
            MAX(population)
        FROM
            cities
        GROUP BY country)
Run Code Online (Sandbox Code Playgroud)

因为这样子查询返回两行,导致主查询中出现错误1242。

Vér*_*ace 5

执行此操作的一个简单方法是使用ROW_NUMBER()(手动索引和函数描述)窗口函数 - 无需使用JOIN. 下面的所有代码都可以在此处的小提琴上找到:

\n

我设置了表格(见下文 - 添加了两个索引),然后用您的数据填充它(见小提琴)。

\n
CREATE TABLE cities \n(\n    country VARCHAR(20),\n    city VARCHAR(20),\n    population INT,\n\n    CONSTRAINT cities_pk      PRIMARY KEY (country, city),\n    KEY cities_city_ix (city, population)\n  \n);\n
Run Code Online (Sandbox Code Playgroud)\n

然后,第一件事是运行(这是为了解释):

\n
SELECT\n  country, city, population, \n  ROW_NUMBER() OVER (PARTITION BY country ORDER BY country, population DESC) AS rn\nFROM \n  cities;\n
Run Code Online (Sandbox Code Playgroud)\n

结果:

\n
country  city     population  rn\n  Italy  Rome        2000000   1  <--- Italy\'s biggest city\n  Italy  Milano      1000000   2\n  Italy  Bologna      800000   3\n Poland  Warszawa    1000000   1  <--- Poland\'s   "      "\n Poland  Wroclaw      700000   2\n
Run Code Online (Sandbox Code Playgroud)\n

因此,现在运行此 SQL,从上面的查询中挑选出最热门的城市作为“pops”子查询(或派生表):

\n
SELECT\n  country, city, population\nFROM\n(\n  SELECT\n    country, city, population, \n    ROW_NUMBER() OVER (PARTITION BY country ORDER BY country, population DESC) AS rn\n  FROM \n    cities\n) AS pops\nWHERE rn = 1\nORDER BY country;\n
Run Code Online (Sandbox Code Playgroud)\n

结果:

\n
country  city      population\nItaly    Rome         2000000\nPoland   Warszawa     1000000\n
Run Code Online (Sandbox Code Playgroud)\n

窗口函数非常强大,您多次学习它们所付出的努力将会得到回报。比orROW_NUMBER()更强大,因为它不仅允许您选择前 1 个,还允许选择前 2、3...&c。再次,阅读并练习一下,直到您掌握它们!FIRST_VALUE()LAST_VALUE()

\n

顺便说一句,我会将这些行附加到表定义的底部 - PK 和INDEX(或KEY- 少输入 :-) )。

\n
CONSTRAINT cities_pk      PRIMARY KEY (country, city),\nKEY cities_city_ix (city, population)\n
Run Code Online (Sandbox Code Playgroud)\n

这些似乎产生了一个很好的计划 - 所有索引,没有表扫描!参见小提琴。

\n
EXPLAIN\n-> Sort: pops.country  (cost=0.00..0.00 rows=0)\n\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87-> Index lookup on pops using <auto_key0> (rn=1)\n\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87-> Materialize  (cost=0.00..0.00 rows=0)\n\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87-> Window aggregate: row_number() OVER (PARTITION BY cities.country ORDER BY cities.country,cities.population desc ) \n\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87-> Sort: cities.country, cities.country, cities.population DESC  (cost=0.75 rows=5)\n\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87\xe2\x80\x87-> Index scan on cities using cities_city_ix\n
Run Code Online (Sandbox Code Playgroud)\n

警告:EXPLAIN是在最小的数据集上完成的 - 我建议您在您的硬件和数据上测试此处所有答案,以便您利用最佳解决方案。

\n