MySQL Sakila 与 World 高频查询练习

本文基于 MySQL 官方常见示例数据库 sakilaworld 编写,面向后端初学者练习日常高频查询。

本文只提供题目、结果要求、提示和易错点,不提供答案。完成某一道题后,可以携带题号和 SQL 进行求证。

练习目标

使用约定

  1. 除非题目明确要求,不使用 SELECT *

  2. 列表查询必须有确定的排序规则。

  3. LIMIT 查询遇到相同排序值时,应增加主键作为第二排序字段。

  4. 日期区间优先使用左闭右开:

    >= 开始时间
    < 结束时间
    
  5. 完成查询后,建议执行:

    EXPLAIN
    SELECT ...;
    
  6. MySQL 8.0 可以在测试环境使用:

    EXPLAIN ANALYZE
    SELECT ...;
    

    EXPLAIN ANALYZE 会真实执行查询,不要对未知成本的查询在生产环境随意使用。

常用表关系速查

Sakila

customer.address_id      → address.address_id
address.city_id          → city.city_id
city.country_id          → country.country_id

rental.customer_id       → customer.customer_id
rental.inventory_id      → inventory.inventory_id
rental.staff_id          → staff.staff_id

inventory.film_id        → film.film_id
inventory.store_id       → store.store_id

payment.customer_id      → customer.customer_id
payment.rental_id        → rental.rental_id
payment.staff_id         → staff.staff_id

film_actor.film_id       → film.film_id
film_actor.actor_id      → actor.actor_id

film_category.film_id    → film.film_id
film_category.category_id → category.category_id

World

city.CountryCode             → country.Code
country.Capital              → city.ID
countrylanguage.CountryCode  → country.Code

第一部分:Sakila 基础业务查询

S01 查询客户详情

场景

后台根据 customer_id 查询客户详情。

结果至少包含

customer_id
first_name
last_name
email
active
address
district
city
country
postal_code
phone

提示

易错点

S02 查询启用状态的客户列表

场景

后台分页查询处于启用状态的客户,支持按照姓氏前缀搜索。

条件

提示

易错点

S03 查询电影筛选列表

场景

电影列表接口需要按照评级、租金和时长筛选。

条件

结果至少包含

film_id
title
rating
rental_rate
length

易错点

S04 查询某个分类下的电影

场景

根据分类名称查询电影列表。

提示

易错点

S05 查询演员参演电影

场景

根据 actor_id查询该演员参演的全部电影。

结果至少包含

actor_id
actor_name
film_id
title
release_year

提示

易错点

S06 查询客户当前未归还的电影

场景

根据 customer_id查询尚未归还的租赁记录。

条件

return_date为空

结果至少包含

rental_id
rental_date
film_id
title
store_id

提示

易错点

S07 查询租赁记录详情

场景

根据 rental_id返回一次租赁的完整详情。

结果至少包含

rental_id
rental_date
return_date
customer_name
film_title
store_id
staff_name

易错点

S08 查询指定时间范围的支付记录

场景

查询某个时间段内的支付流水。

条件

提示

日期范围使用:

payment_date >= ?
AND payment_date < ?

易错点

S09 计算客户累计消费金额

场景

根据 customer_id查询客户累计支付金额和支付次数。

结果

customer_id
payment_count
total_amount

提示

易错点

S10 查询消费超过阈值的客户

场景

查询指定时间范围内累计消费超过指定金额的客户。

提示

结果至少包含

customer_id
customer_name
payment_count
total_amount

易错点


第二部分:Sakila 高频统计与存在性查询

S11 查询没有任何租赁记录的客户

场景

运营需要找出注册后从未租过电影的客户。

提示

尝试分别使用:

  1. NOT EXISTS
  2. LEFT JOIN ... IS NULL

然后使用 EXPLAIN比较。

易错点

S12 查询没有库存的电影

场景

找出电影目录中存在,但两个门店都没有库存副本的电影。

提示

易错点

S13 查询某门店当前可出租的电影库存

场景

查询指定门店中当前没有被借出的库存副本。

业务定义

一份库存如果不存在 return_date IS NULL的租赁记录,就认为当前可出租。

结果至少包含

inventory_id
film_id
title
store_id

提示

易错点

S14 统计每个门店的库存与可出租数量

场景

门店库存看板需要显示:

store_id
total_inventory_count
available_inventory_count
rented_inventory_count

提示

易错点

S15 查询逾期未归还记录

场景

电影的应归还时间为:

rental_date + film.rental_duration天

查询当前仍未归还且已经超过应归还时间的租赁记录。

提示

结果至少包含

rental_id
customer_id
customer_name
film_title
rental_date
expected_return_at
overdue_days

易错点

S16 按天统计营业额

场景

统计指定时间范围内每天的支付笔数和支付总额。

结果

payment_day
payment_count
total_amount

提示

易错点

S17 按月统计门店营业额

场景

按月份、门店统计支付总额。

提示

易错点

S18 统计电影出租次数排行榜

场景

查询出租次数最多的10部电影。

结果至少包含

film_id
title
rental_count

提示

易错点

S19 统计分类营业额

场景

统计每个电影分类产生的支付金额。

提示

关系路径较长:

category
→ film_category
→ film
→ inventory
→ rental
→ payment

易错点

完成后建议挑选一个分类,逐层查询验证总额。

S20 统计员工和门店的收款金额

场景

统计指定时间范围内每名员工的收款笔数和收款金额,并显示所属门店。

提示

易错点


第三部分:Sakila 进阶查询

S21 查询每名客户最近一次租赁

场景

客户列表需要展示每名客户最近一次租赁时间和电影名称。

要求

提示

易错点

S22 查询每个分类出租次数最多的3部电影

场景

每个分类分别显示Top 3电影,而不是全局Top 3。

提示

易错点

S23 查询高于分类平均租金的电影

场景

查询租金高于其所属分类平均租金的电影。

提示

易错点

S24 比较客户消费排名

场景

计算每名客户的累计支付金额,并按照金额排名。

结果至少包含

customer_id
customer_name
total_amount
amount_rank

提示

易错点


第四部分:World 高频业务查询

W01 查询国家详情及首都

场景

根据国家代码查询国家详情,同时显示首都城市名称。

结果至少包含

country_code
country_name
continent
region
population
life_expectancy
capital_city

提示

易错点

W02 查询国家下的城市分页列表

场景

根据国家代码查询城市,按人口倒序分页。

排序

Population DESC
ID DESC

结果

ID
Name
District
Population

易错点

W03 查询人口区间内的国家

场景

查询指定洲、人口位于指定范围的国家。

条件

易错点

W04 查询人口最多的城市

场景

查询全球人口最多的20座城市,并显示所属国家。

结果至少包含

city_id
city_name
country_code
country_name
population

易错点

W05 按名称前缀搜索国家

场景

国家搜索接口根据名称前缀返回最多20条结果。

提示

使用:

LIKE CONCAT(?, '%')

易错点

W06 查询国家官方语言

场景

根据国家代码查询所有官方语言及使用比例。

条件

IsOfficial = 'T'

排序

Percentage倒序、Language升序。

易错点

W07 查询没有官方语言的国家

场景

找出 country中不存在官方语言记录的国家。

提示

尝试:

  1. NOT EXISTS
  2. LEFT JOIN ... IS NULL

易错点

W08 查询没有城市记录的国家

场景

查询 country中不存在任何 city记录的国家。

提示

W09 统计每个洲的国家数量和人口

结果

continent
country_count
total_population
average_population

条件

只保留国家数量不少于指定值的洲。

提示

易错点

W10 统计每个国家的城市数量和城市人口

场景

列出所有国家,包括没有城市记录的国家。

结果至少包含

country_code
country_name
city_count
city_population
country_population

提示

易错点

W11 计算最大城市人口占国家人口比例

场景

对每个国家找出人口最多的城市,并计算其人口占国家总人口的比例。

结果至少包含

country_code
country_name
largest_city_name
largest_city_population
country_population
population_ratio

提示

易错点

W12 查询拥有多种官方语言的国家

场景

查询官方语言数量不少于2种的国家。

结果

country_code
country_name
official_language_count

提示

W13 估算每种语言的使用人口

场景

根据国家人口和语言使用比例,估算每种语言在所有国家中的使用人口。

计算逻辑

国家人口 × Percentage / 100

结果

language
country_count
estimated_speaker_population

提示

易错点

W14 查询每个国家人口最多的3座城市

场景

每个国家分别查询Top 3城市。

提示

易错点

W15 查询人口高于所在洲平均值的国家

场景

找出人口高于该洲国家平均人口的国家。

提示

可以使用:

易错点

W16 使用游标分页查询城市

场景

按人口倒序查询全球城市,每页20条,不使用大偏移量。

排序

Population DESC
ID DESC

提示

下一页需要携带上一页最后一条记录的:

Population
ID

可以使用行比较:

(Population, ID) < (?, ?)

易错点


第五部分:查询效率专项练习

P01 比较时间函数过滤与范围过滤

分别为下面两种查询执行 EXPLAIN

SELECT payment_id, payment_date
FROM payment
WHERE DATE(payment_date) = '2005-08-01';

以及:

SELECT payment_id, payment_date
FROM payment
WHERE payment_date >= '2005-08-01 00:00:00'
  AND payment_date < '2005-08-02 00:00:00';

思考

P02 比较 WHERE 与 HAVING

分别编写两份“只统计指定客户支付总额”的查询:

  1. 客户条件放在 WHERE
  2. 客户条件放在 HAVING

执行 EXPLAIN并比较。

思考

P03 深分页与游标分页

分别查询城市人口排序的深分页:

LIMIT 4000, 20

以及使用上一页最后一条记录作为游标的分页。

思考

P04 SELECT * 与明确字段

对比:

SELECT *
FROM film
WHERE film_id BETWEEN 1 AND 100;

和只选择:

film_id
title
rating
rental_rate

思考

P05 检查多表JOIN的重复放大

从一部指定电影开始,依次关联:

film
inventory
rental
payment

每增加一个JOIN,都统计一次总行数。

思考

P06 EXISTS 与 JOIN

使用以下两种方式查询有租赁记录的客户:

  1. EXISTS
  2. JOIN

要求

思考

P07 检查低选择性字段

分别统计 customer.active = 1customer.active = 0的数量。

思考

P08 使用 EXPLAIN ANALYZE 检查估算偏差

选择一条包含JOIN、过滤和排序的查询,在测试环境执行:

EXPLAIN ANALYZE
SELECT ...;

记录

预计行数
实际行数
actual time
loops
使用的索引

思考


第六部分:综合接口查询练习

C01 客户管理列表接口

需求

实现一个客户管理列表查询,支持:

易错点

提示

可以先分别在 CTE 中计算:

客户支付汇总
客户最近租赁

再与客户主表关联。

C02 电影详情接口

需求

根据 film_id查询:

易错点

提示

后端详情接口不一定必须用一条SQL完成。可以评估:

一条复杂SQL
vs
少量职责明确的批量查询

C03 国家统计列表接口

需求

查询国家列表,展示:

支持按洲筛选、按国家人口倒序分页。

易错点

提示

先分别聚合:

城市统计
官方语言统计
最大城市

再关联国家表。


完成每道题后的自查清单

[ ] 查询是否只返回必要字段?
[ ] JOIN条件是否完整?
[ ] 查询结果的“一行”代表什么业务对象?
[ ] 是否因为一对多JOIN产生重复?
[ ] 普通条件是否放在WHERE?
[ ] 聚合结果条件是否放在HAVING?
[ ] NULL是否使用IS NULL或IS NOT NULL判断?
[ ] 日期范围是否使用左闭右开?
[ ] 排序是否稳定?
[ ] 分页是否可能出现重复或遗漏?
[ ] 是否存在大OFFSET?
[ ] 是否在索引字段上使用函数或隐式类型转换?
[ ] 是否可以用EXISTS表达存在性?
[ ] 是否无意义地使用了DISTINCT?
[ ] EXPLAIN显示使用了哪个索引?
[ ] 预计扫描行数和返回行数是否合理?

建议练习顺序

S01~S10
→ W01~W10
→ S11~S20
→ W11~W16
→ S21~S24
→ P01~P08
→ C01~C03

建议一次只完成一道题,并同时记录:

自己的SQL
查询结果行数
EXPLAIN结果
遇到的问题
修改前后的差异