DeepSeek 牛客网 SQL40 每个月Top3的周杰伦歌曲 MySQL 分组TopN查询
连接与过滤:通过INNER JOIN关联多表,并用WHERE筛选出18-25岁用户、周杰伦歌曲、2022年记录。分组聚合:用GROUP BY按月、歌曲分组,用COUNT(*)计算每月每首歌的播放次数。窗口计算与排名:使用ROW_NUMBER() OVER(PARTITION BY 月份 ORDER BY 播放量 DESC)为每月歌曲生成排名。外层筛选:在子查询外,用WHERE 排名 <= 3获取最
牛客网SQL40题要求我们从2022年的听歌记录中,找出18-25岁用户每月播放量排名前三的周杰伦歌曲。这道题的核心是考察分组TopN查询,需要综合运用多表连接、数据过滤、分组聚合以及窗口函数。
以下是基于题目要求的详细解法和说明。
解题思路
- 多表连接:题目数据分布在三个表中,需要将它们连接起来。
play_log表通过user_id连接user_info表,以获取用户年龄。play_log表通过song_id连接song_info表,以获取歌曲名称和歌手信息。
- 数据过滤:在连接后的数据集中,筛选出符合所有条件的数据。
- 用户年龄在18岁到25岁之间(包含18和25)。
- 歌曲的演唱者是
周杰伦。 - 播放日期在2022年之内。
- 分组统计播放次数:以月份和歌曲为维度进行分组,统计每首歌在每个月的总播放量(
play_pv)。这里需要使用MONTH()函数从日期中提取月份,并用COUNT()函数统计播放记录。 - 窗口函数排名:使用
ROW_NUMBER()窗口函数,对每个月份分区,在分区内按照播放量(play_pv)进行降序排列,生成每个月份内部的排名(ranking)。 - 筛选Top 3:在外层查询中,筛选出排名小于或等于3的记录,得到最终结果。
参考代码
SELECT
sub.month,
sub.ranking,
sub.song_name,
sub.play_pv
FROM (
-- 子查询:先完成连接、过滤、分组统计和排名
SELECT
MONTH(pl.fdate) AS month,
ROW_NUMBER() OVER (
PARTITION BY MONTH(pl.fdate)
ORDER BY COUNT(*) DESC, si.song_id ASC -- 先按播放量降序,若相同则按歌曲ID升序(题目隐含规则)
) AS ranking,
si.song_name,
COUNT(*) AS play_pv
FROM
play_log pl
-- 连接用户表,筛选年龄
INNER JOIN user_info ui ON pl.user_id = ui.user_id AND ui.age BETWEEN 18 AND 25
-- 连接歌曲表,筛选周杰伦歌曲
INNER JOIN song_info si ON pl.song_id = si.song_id AND si.singer_name = '周杰伦'
WHERE
YEAR(pl.fdate) = 2022 -- 筛选2022年的记录
GROUP BY
MONTH(pl.fdate), si.song_name, si.song_id -- 按月份和歌曲分组
) AS sub
WHERE
sub.ranking <= 3 -- 筛选每个月份的前三名
ORDER BY
sub.month ASC, sub.ranking ASC; -- 按月份和排名排序输出
关键点解析
-
为什么用
ROW_NUMBER()而不是RANK()或DENSE_RANK()?
题目要求取Top 3,且从提供的示例输出来看,排名列(ranking)是连续且不重复的(1,2,3)。ROW_NUMBER()会为每一行分配一个唯一且连续的整数排名,即使播放量相同,也会根据ORDER BY中的次要条件(如歌曲ID)强制分出先后,确保每个月稳定地输出3条记录 。如果使用RANK(),在播放量并列时会产生相同的排名(如1,1,3),可能导致结果超过3条 。 -
GROUP BY中为什么同时有song_name和song_id?
虽然在这个业务场景下,song_name和song_id应该是一一对应的,但在SQL的GROUP BY逻辑中,song_name的取值依赖于song_id。为了保证分组结果的唯一性和准确性,避免潜在的错误,最佳的实践是将所有非聚合的查询列都包含在GROUP BY子句中 。 -
为什么不能直接用
LIMIT 3?LIMIT子句是对整个结果集进行限制,只能取出全局的前3条记录。要得到“每个月的Top 3”,必须使用窗口函数配合PARTITION BY按月分区,才能实现“分组内排序并取前N”的效果 。
总结
本题是窗口函数在分组TopN场景下的经典应用,主要包含以下几个步骤:
- 连接与过滤:通过
INNER JOIN关联多表,并用WHERE筛选出18-25岁用户、周杰伦歌曲、2022年记录。 - 分组聚合:用
GROUP BY按月、歌曲分组,用COUNT(*)计算每月每首歌的播放次数。 - 窗口计算与排名:使用
ROW_NUMBER() OVER(PARTITION BY 月份 ORDER BY 播放量 DESC)为每月歌曲生成排名。 - 外层筛选:在子查询外,用
WHERE 排名 <= 3获取最终结果。
更多推荐




所有评论(0)