按省份分组,找出 2025 年销售额排名前 3 的省份;对于这 3 个省份,分别找出其下属销售额排名前 3 的城市
面试题:2025年省份及下属城市销售额双重 Top 3 分析
题目描述:
有一张销售明细表 sales_records,记录了每笔订单的省份、城市、销售额和订单日期。请编写 SQL 查询,找出 2025 年总销售额排名前 3 的省份;并针对这 3 个省份,分别找出其下属销售额排名前 3 的城市。
1. 示例数据
输入表:sales_records
| order_id | province | city | sales_amount | order_date |
|---|---|---|---|---|
| 1 | 广东 | 深圳 | 10000 | 2025-01-15 |
| 2 | 广东 | 深圳 | 5000 | 2025-02-10 |
| 3 | 广东 | 广州 | 8000 | 2025-01-20 |
| 4 | 广东 | 广州 | 4000 | 2025-03-05 |
| 5 | 广东 | 东莞 | 3000 | 2025-02-28 |
| 6 | 广东 | 佛山 | 2000 | 2025-04-12 |
| 7 | 广东 | 惠州 | 1000 | 2025-05-18 |
| 8 | 浙江 | 杭州 | 12000 | 2025-01-10 |
| 9 | 浙江 | 杭州 | 6000 | 2025-03-15 |
| 10 | 浙江 | 宁波 | 8000 | 2025-02-20 |
| 11 | 浙江 | 温州 | 4000 | 2025-04-05 |
| 12 | 浙江 | 绍兴 | 1000 | 2025-05-01 |
| 13 | 江苏 | 苏州 | 11000 | 2025-01-22 |
| 14 | 江苏 | 南京 | 9000 | 2025-02-18 |
| 15 | 江苏 | 无锡 | 5000 | 2025-03-12 |
| 16 | 江苏 | 常州 | 2000 | 2025-04-19 |
| 17 | 四川 | 成都 | 15000 | 2025-01-05 |
| 18 | 四川 | 绵阳 | 3000 | 2025-02-15 |
| 19 | 四川 | 宜宾 | 1000 | 2025-03-20 |
| 20 | 广东 | 深圳 | 9000 | 2024-12-25 |
2. 期望输出结果
| province | total_province_sales | city | total_city_sales | city_rank |
|---|---|---|---|---|
| 广东 | 25000 | 深圳 | 15000 | 1 |
| 广东 | 25000 | 广州 | 12000 | 2 |
| 广东 | 25000 | 东莞 | 3000 | 3 |
| 浙江 | 31000 | 杭州 | 18000 | 1 |
| 浙江 | 31000 | 宁波 | 8000 | 2 |
| 浙江 | 31000 | 温州 | 4000 | 3 |
| 江苏 | 27000 | 苏州 | 11000 | 1 |
| 江苏 | 27000 | 南京 | 9000 | 2 |
| 江苏 | 27000 | 无锡 | 5000 | 3 |
数据说明:
- 2024年的数据(如订单20)被排除。
- 2025年省份总销售额:浙江 (31000) > 江苏 (27000) > 广东 (25000) > 四川 (19000)。因此四川被剔除。
- 各省内部筛选出销售额前3的城市。
3. SQL 解决方案 (基于 MySQL 8.0+ / Standard SQL)
sql
WITH city_sales AS (
-- Step 1: 计算2025年每个城市和省份的基础总销售额
SELECT
province,
city,
SUM(sales_amount) AS total_city_sales
FROM sales_records
WHERE order_date >= '2025-01-01' AND order_date <= '2025-12-31'
GROUP BY province, city
),
top_provinces AS (
-- Step 2: 汇总省份销售额,并找出排名前 3 的省份
SELECT
province,
SUM(total_city_sales) AS total_province_sales
FROM city_sales
GROUP BY province
ORDER BY total_province_sales DESC
LIMIT 3
),
ranked_cities AS (
-- Step 3: 在各省份内部,对城市的销售额进行排名
SELECT
province,
city,
total_city_sales,
ROW_NUMBER() OVER (
PARTITION BY province
ORDER BY total_city_sales DESC
) AS city_rank
FROM city_sales
)
-- Step 4: 关联省份 Top 3 和城市排名,过滤出目标数据
SELECT
tp.province,
tp.total_province_sales,
rc.city,
rc.total_city_sales,
rc.city_rank
FROM top_provinces tp
INNER JOIN ranked_cities rc ON tp.province = rc.province
WHERE rc.city_rank <= 3
ORDER BY tp.total_province_sales DESC, rc.city_rank ASC;
4. 面试官视角:SQL 深度剖析与评价标准
此题是标准的“组内 Top N”问题的升级版,融合了多层聚合与过滤依赖。面试官可以通过此题考察候选人以下维度:
核心考点解析
公用表表达式(CTE)的设计与可读性:
不推荐使用多层嵌套子查询,使用WITH语法将复杂逻辑拆解为city_sales、top_provinces、ranked_cities,能极大展现候选人编写工程级 SQL 的规范性与结构化思维。日期过滤的性能考量(SARGability):
- 反例:
WHERE YEAR(order_date) = 2025 - 正例:
WHERE order_date >= '2025-01-01' AND order_date <= '2025-12-31' - 面试加分项:主动向面试官解释,避免在索引列上使用函数(如
YEAR()),因为这会导致索引失效从而全表扫描。使用范围查询能有效利用order_date上的索引。
- 反例:
窗口函数的选择(
ROW_NUMBERvsRANKvsDENSE_RANK):
在ranked_cities中,我们使用了ROW_NUMBER()。- 追问准备:面试官可能会问:“如果遇到并列第三名的城市,该如何处理?”
- 若要并列保留,应使用
RANK()或DENSE_RANK()。 - 若要严格返回3条,则用
ROW_NUMBER()。展示对这三个窗口函数细节差异的掌握是高级工程师的标配。
- 若要并列保留,应使用
- 追问准备:面试官可能会问:“如果遇到并列第三名的城市,该如何处理?”
双重 Top N 逻辑的闭环:
不能直接对所有城市进行全局ROW_NUMBER()排名。必须先计算省份总额挑出 Top 3 省份,再在省份内部对城市进行 Rank,最后通过INNER JOIN剔除不属于 Top 3 省份的城市。这考查了候选人处理“局部与整体”数据流向的清晰度。
右滑查看面试常问