MySQL Sakila 与 World 练习答案(二):W01~W16
对应题目:mysql-sakila-world-query-exercises.md。
分册导航:Sakila S01~S24 · 性能与综合接口
W01 国家详情及首都
SELECT
co.Code AS country_code,
co.Name AS country_name,
co.Continent AS continent,
co.Region AS region,
co.Population AS population,
co.LifeExpectancy AS life_expectancy,
ci.Name AS capital_city
FROM country AS co
LEFT JOIN city AS ci ON ci.ID = co.Capital
WHERE co.Code = :country_code;
Capital 指向 city.ID;LEFT JOIN 保留 Capital 为 NULL 的国家。
W02 国家城市分页
SELECT ID, Name, District, Population
FROM city
WHERE CountryCode = :country_code
ORDER BY Population DESC, ID DESC
LIMIT :page_size OFFSET :offset;
mysql2 是否允许绑定 LIMIT 参数取决于所用调用方式和 prepared statement;不要为此拼接任意字符串。页大小和 offset 应先验证为非负整数。深分页见 W16/P03。
W03 洲内人口区间国家
SELECT Code, Name, Continent, Region, Population
FROM country
WHERE Continent = :continent
AND Population BETWEEN :minimum_population AND :maximum_population
ORDER BY Population DESC, Code;
BETWEEN 包含两端,Population 参数必须按数字绑定。
W04 全球人口最多城市
SELECT
ci.ID AS city_id,
ci.Name AS city_name,
co.Code AS country_code,
co.Name AS country_name,
ci.Population AS population
FROM city AS ci
JOIN country AS co ON co.Code = ci.CountryCode
ORDER BY ci.Population DESC, ci.ID
LIMIT 20;
W05 国家名称前缀
SELECT Code, Name, Continent, Region
FROM country
WHERE Name LIKE CONCAT(:name_prefix, ''%'')
ORDER BY Name, Code
LIMIT 20;
若百分号和下划线应作为普通字符,应用层需按约定转义 LIKE 元字符。
W06 国家官方语言
SELECT
CountryCode AS country_code,
Language AS language,
Percentage AS percentage
FROM countrylanguage
WHERE CountryCode = :country_code
AND IsOfficial = ''T''
ORDER BY Percentage DESC, Language;
World 的 IsOfficial 是 ENUM(‘‘T’’,‘‘F’’),不是数字布尔值。
W07 没有官方语言的国家
NOT EXISTS:
SELECT co.Code, co.Name
FROM country AS co
WHERE NOT EXISTS (
SELECT 1
FROM countrylanguage AS cl
WHERE cl.CountryCode = co.Code
AND cl.IsOfficial = ''T''
)
ORDER BY co.Code;
LEFT JOIN:
SELECT co.Code, co.Name
FROM country AS co
LEFT JOIN countrylanguage AS cl
ON cl.CountryCode = co.Code
AND cl.IsOfficial = ''T''
WHERE cl.CountryCode IS NULL
ORDER BY co.Code;
IsOfficial 条件必须放在 ON;放 WHERE 会破坏保留空匹配的语义。
W08 没有城市记录的国家
SELECT co.Code, co.Name
FROM country AS co
WHERE NOT EXISTS (
SELECT 1
FROM city AS ci
WHERE ci.CountryCode = co.Code
)
ORDER BY co.Code;
W09 每洲国家数量和人口
SELECT
Continent AS continent,
COUNT(*) AS country_count,
SUM(Population) AS total_population,
AVG(Population) AS average_population
FROM country
GROUP BY Continent
HAVING COUNT(*) >= :minimum_country_count
ORDER BY total_population DESC, continent;
AVG 返回精确类型/小数展示取决于输入和客户端映射,不应手工用整数除法替代。
W10 每国城市统计
SELECT
co.Code AS country_code,
co.Name AS country_name,
COUNT(ci.ID) AS city_count,
COALESCE(SUM(ci.Population), 0) AS city_population,
co.Population AS country_population
FROM country AS co
LEFT JOIN city AS ci ON ci.CountryCode = co.Code
GROUP BY co.Code, co.Name, co.Population
ORDER BY co.Code;
COUNT(ci.ID) 不会把外连接产生的 NULL 占位行算作城市。
W11 最大城市人口占比
WITH ranked_cities AS (
SELECT
ci.CountryCode,
ci.ID,
ci.Name,
ci.Population,
ROW_NUMBER() OVER (
PARTITION BY ci.CountryCode
ORDER BY ci.Population DESC, ci.ID
) AS position_in_country
FROM city AS ci
)
SELECT
co.Code AS country_code,
co.Name AS country_name,
rc.Name AS largest_city_name,
rc.Population AS largest_city_population,
co.Population AS country_population,
ROUND(rc.Population * 100.0 / NULLIF(co.Population, 0), 2)
AS population_ratio_percent
FROM country AS co
LEFT JOIN ranked_cities AS rc
ON rc.CountryCode = co.Code
AND rc.position_in_country = 1
ORDER BY co.Code;
乘 100.0 明确得到百分比;NULLIF 防止除零。无城市国家返回 NULL 比例。
W12 至少两种官方语言
SELECT
co.Code AS country_code,
co.Name AS country_name,
COUNT(cl.Language) AS official_language_count
FROM country AS co
JOIN countrylanguage AS cl
ON cl.CountryCode = co.Code
AND cl.IsOfficial = ''T''
GROUP BY co.Code, co.Name
HAVING COUNT(cl.Language) >= 2
ORDER BY official_language_count DESC, co.Code;
countrylanguage 的主键包含 CountryCode 和 Language,所以无需 DISTINCT。
W13 估算各语言使用人口
SELECT
cl.Language AS language,
COUNT(*) AS country_count,
ROUND(SUM(co.Population * cl.Percentage / 100.0)) AS estimated_speaker_population
FROM countrylanguage AS cl
JOIN country AS co ON co.Code = cl.CountryCode
GROUP BY cl.Language
ORDER BY estimated_speaker_population DESC, language;
先汇总高精度表达式,最后再 ROUND,避免逐国提前舍入放大误差。这是基于样本比例的估算值。
W14 每国人口最多的三座城市
WITH ranked_cities AS (
SELECT
ci.ID,
ci.Name,
ci.CountryCode,
ci.Population,
ROW_NUMBER() OVER (
PARTITION BY ci.CountryCode
ORDER BY ci.Population DESC, ci.ID
) AS position_in_country
FROM city AS ci
)
SELECT
CountryCode AS country_code,
ID AS city_id,
Name AS city_name,
Population AS population,
position_in_country
FROM ranked_cities
WHERE position_in_country <= 3
ORDER BY CountryCode, position_in_country;
窗口别名不能在同层 WHERE 过滤,所以先放进 CTE。
W15 高于所在洲平均人口的国家
WITH countries_with_average AS (
SELECT
Code,
Name,
Continent,
Population,
AVG(Population) OVER (
PARTITION BY Continent
) AS continent_average_population
FROM country
)
SELECT
Code AS country_code,
Name AS country_name,
Continent AS continent,
Population AS population,
continent_average_population
FROM countries_with_average
WHERE Population > continent_average_population
ORDER BY Continent, Population DESC, Code;
W16 游标分页城市
第一页:
SELECT ID, Name, CountryCode, District, Population
FROM city
ORDER BY Population DESC, ID DESC
LIMIT 20;
下一页把上一页最后一行的 Population 和 ID 作为游标:
SELECT ID, Name, CountryCode, District, Population
FROM city
WHERE (Population, ID) < (:last_population, :last_id)
ORDER BY Population DESC, ID DESC
LIMIT 20;
因为两个排序方向相同,行比较可直接使用小于。若排序方向混合,应展开成:
WHERE Population < :last_population
OR (Population = :last_population AND ID < :last_id)
游标必须携带所有排序键,且数据在翻页期间变化时仍需接受“实时列表”语义或引入快照边界。