题目描述
问题:计算2021年里每个创作者每月的涨粉率及截止当月的总粉丝量
注:
涨粉率=(加粉量 - 掉粉量) / 播放量。结果按创作者ID、总粉丝量升序排序。
if_follow-是否关注为1表示用户观看视频中关注了视频创作者,为0表示此次互动前后关注状态未发生变化,为2表示本次观看过程中取消了关注。
用户-视频互动表tb_user_video_log:
id | uid | video_id | start_time | end_time | if_follow | if_like | if_retweet | comment_id |
---|---|---|---|---|---|---|---|---|
1 | 101 | 2001 | 2021-10-01 10:00:00 | 2021-10-01 10:00:20 | 0 | 1 | 1 | NULL |
2 | 102 | 2001 | 2021-10-01 10:00:00 | 2021-10-01 10:00:15 | 0 | 0 | 1 | NULL |
3 | 103 | 2001 | 2021-10-01 11:00:50 | 2021-10-01 11:01:15 | 0 | 1 | 0 | 1732526 |
4 | 102 | 2002 | 2021-09-10 11:00:00 | 2021-09-10 11:00:30 | 1 | 0 | 1 | NULL |
5 | 103 | 2002 | 2021-10-01 10:59:05 | 2021-10-01 11:00:05 | 1 | 0 | 0 | NULL |
- uid:用户ID
- video_id:视频ID
- start_time:开始观看时间
- end_time:结束观看时间
- if_follow:是否关注
- if_like:是否点赞
- if_retweet:是否转发
- comment_id:评论ID
短视频信息表tb_video_info:
id | video_id | author | tag | duration | release_time |
---|---|---|---|---|---|
1 | 2001 | 901 | 影视 | 30 | 2021-01-01 07:00:00 |
2 | 2002 | 901 | 美食 | 60 | 2021-01-01 07:00:00 |
3 | 2003 | 902 | 旅游 | 90 | 2020-01-01 07:00:00 |
- video_id:视频ID
- author:创作者ID
- tag:类别标签
- duration:视频时长
- release_time:发布时间
最终输出样式:
author | month | fans_growth_rate | total_fans |
---|---|---|---|
901 | 2021-09 | 0.500 | 1 |
901 | 2021-10 | 0.250 | 2 |
题目分析
题目理解:
- 统计纬度:在2021年内,按照每个作者且是每个月份进行统计:
- 2021年内:筛选start_time 在2021年内;
- 每个作者:需要连接2张表找到tb_video_info里的author;
- 每个月份:按照start_time把timestamp格式的数据转化为年份+月份(yy-mm)格式; - 统计指标:每月的涨粉率以及截止当月的总粉丝量
- 每月涨粉率:涨粉率=(加粉量 - 掉粉量) / 播放量;
- 截止当月的总粉丝量; - 排序格式:
- 结果按创作者ID、总粉丝量升序排序
实现方式:
- 统计纬度:
- 用YEAR()函数获取日期中年份作为筛选条件:WHERE YEAR(start_time) = 2021
- 用JOIN关联表以便获得author数据:JOIN tb_video_info USING(video_id)
- 用DATE_FORMAT()函数获得每个月份的数据:DATE_FORMAT(start_time, "%Y-%m")
- 统计指标:
- 涨粉量 = (加粉量 - 掉粉量) ,如果if_follow = 2代表掉粉,if_follow = 0 代表没发生变化,所以用SUM()和IF()函数能计算涨粉量:SUM(IF if_follow = 2 , -1,if_follow)
- 播放量指的是在tb_user_video_log表里有记录的行数:用COUNT()计算:COUNT(id)
- 每月涨粉率 = 涨粉量/播放量:SUM(IF if_follow = 2 , -1,if_follow) /COUNT(id)
- 截止当月的总粉丝量:要用到聚合窗口函数,按照作者分类,月份升序排序后,累积求和该条数据之前的总粉丝量情况:sum(fans_add_cnt) over(partition by author order by month)
- 排序格式:
-ORDER BY author,total_fans
SQL实现
SELECT author,
month,
ROUND(fans_add_count / pv_count,3) AS fans_growth_rate,
SUM(fans_add_count) over(partition by author order by month) AS total_fans
FROM
(
SELECT b.author,
DATE_FORMAT(a.start_time,'%Y-%m') AS month,
SUM(IF(a.if_follow = 2,-1,a.if_follow)) AS fans_add_count,
COUNT(a.video_id) AS pv_count
FROM tb_user_video_log AS a
JOIN
tb_video_info b
USING(video_id)
WHERE YEAR(a.start_time) = 2021
GROUP BY b.author,month
) AS author_monthly_fans_play_cnt
ORDER BY author,total_fans;
Knowledge Point 知识点
1.DATE_FORMAT() 函数
定义:DATE_SUB() 函数用于以不同的格式显示日期/时间数据.
语法:DATE_FORMAT(date,format);
date:是要格式化的有效日期值
format:是由预定义的说明符组成的格式字符串,每个说明符前面都有一个百分比字符(%)。有关预定义说明符的列表,请参见下表。
限定符 | 含义 |
---|---|
%a | 三个字符缩写的工作日名称,例如:Mon, Tue, Wed,等 |
%b | 三个字符缩写的月份名称,例如:Jan,Feb,Mar等 |
%c | 以数字表示的月份值,例如:1, 2, 3…12 |
%D | 英文后缀如:0th, 1st, 2nd等的一个月之中的第几天 |
%d | 如果是1个数字(小于10),那么一个月之中的第几天表示为加前导加0, 如:00, 01,02, …31 |
%e | 没有前导零的月份的日子,例如:1,2,… 31 |
%f | 微秒,范围在000000…999999 |
%H | 24小时格式的小时,前导加0,例如:00,01…23 |
%h | 小时,12小时格式,带前导零,例如:01,02 … 12 |
%I | 与%h相同 |
%i | 分数为零,例如:00,01,… 59 |
%j | 一年中的的第几天,前导为0,例如,001,002,… 366 |
%k | 24小时格式的小时,无前导零,例如:0,1,2 … 23 |
%l | 12小时格式的小时,无前导零,例如:0,1,2 … 12 |
%M | 月份全名称,例如:January, February,…December |
%m | 具有前导零的月份名称,例如:00,01,02,… 12 |
%p | AM或PM,取决于其他时间说明符 |
%r | 表示时间,12小时格式hh:mm:ss AM或PM |
%S | 表示秒,前导零,如:00,01,… 59 |
%s | 与%S相同 |
%T | 表示时间,24小时格式hh:mm:ss |
%U | 周的第一天是星期日,例如:00,01,02 … 53时,前导零的周数 |
%u | 周的第一天是星期一,例如:00,01,02 … 53时,前导零的周数 |
%V | 与%U相同,它与%X一起使用 |
%v | 与%u相同,它与%x一起使用 |
%W | 工作日的全称,例如:Sunday, Monday,…, Saturday |
%w | 工作日,以数字来表示(0 = 星期日,1 = 星期一等) |
%X | 周的四位数表示年份,第一天是星期日; 经常与%V一起使用 |
%x | 周的四位数表示年份,第一天是星期日; 经常与%v一起使用 |
%Y | 表示年份,四位数,例如2000,2001,…等。 |
%y | 表示年份,两位数,例如00,01,…等。 |
%% | 将百分比(%)字符添加到输出 |
以下是一些常用的日期格式字符串:
DATE_FORMAT字符串 | 格式化日期 |
---|---|
%Y-%m-%d | 2017/4/30 |
%e/%c/%Y | 4/7/2013 |
%c/%e/%Y | 7/4/2013 |
%d/%m/%Y | 4/7/2013 |
%m/%d/%Y | 7/4/2013 |
%e/%c/%Y %H:%i | 4/7/2013 11:20 |
%c/%e/%Y %H:%i | 7/4/2013 11:20 |
%d/%m/%Y %H:%i | 4/7/2013 11:20 |
%m/%d/%Y %H:%i | 7/4/2013 11:20 |
%e/%c/%Y %T | 4/7/2013 11:20 |
%c/%e/%Y %T | 7/4/2013 11:20 |
%d/%m/%Y %T | 4/7/2013 11:20 |
%m/%d/%Y %T | 7/4/2013 11:20 |
%a %D %b %Y | Thu 4th Jul 2013 |
%a %D %b %Y %H:%i | Thu 4th Jul 2013 11:20 |
%a %D %b %Y %T | Thu 4th Jul 2013 11:20:05 |
%a %b %e %Y | Thu Jul 4 2013 |
%a %b %e %Y %H:%i | Thu Jul 4 2013 11:20 |
%a %b %e %Y %T | Thu Jul 4 2013 11:20:05 |
%W %D %M %Y | Thursday 4th July 2013 |
%W %D %M %Y %H:%i | Thursday 4th July 2013 11:20 |
%W %D %M %Y %T | Thursday 4th July 2013 11:20:05 |
%l:%i %p %b %e, %Y | 7/4/2013 11:20 |
%M %e, %Y | 4-Jul-13 |
%a, %d %b %Y %T | Thu, 04 Jul 2013 11:20:05 |
2.窗口函数
-
什么是窗口函数?
- 窗口函数,也称为OLAP函数(Online Analytical Processing)的简称,代表对于数据库数据进行实时分析处理。
- 窗口的概念:它可以理解为记录集合,窗口函数也就是在满足某种条件的记录集合上执行的特殊函数,对于每条记录都要在此窗口内执行函数。
- 窗口的大小都是固定的,这种属于静态窗口;不同的记录对应着不同的窗口,这种动态变化的窗口叫滑动窗口。 -
窗口函数的基本语法:
-函数名 ( [expr] ) OVER ( 子句 )
- 其中,over是关键字,用来指定函数执行的窗口范围,它会包含三个分析子句,分组(PARTITION BY)子句,排序(ORDER BY)子句,窗口(ROWS)子句,如果后面的括号中什么都不写,代表窗口包含满足where条件的所有行,窗口函数会基于所有行进行计算;如果不为空,则支持以下语法来设置窗口:
-函数名 ( [expr] ) OVER ( PARTITION BY <要分列的组> ORDER BY <要排序的列> ROWS BETWEEN <数据范围> )
- ROWS 子句的几种适用形式:包括本行和之前所有的行: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 包括本行和之后所有的行: ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING 包括本行和前面三行: ROWS BETWEEN 3 PRECEDING AND CURRENT ROW 包括本行和后面三行: ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING 从前面三行包括本行再取后面一行,总共五行: ROWS BETWEEN 3 PRECEDING AND 1 FOLLOWING 当 ORDER BY 后面缺少窗口从句条件时,窗口规范默认是: ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW (也就是取之前的截止到本行的数) 当 ORDER BY 和窗口从句都缺失,窗口规范默认是: ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING (也就是全部都取)
-
窗口函数分类:
- 聚合类窗口函数: SUM()、AVG()、COUNT()、MAX()、MIN()
- 专用窗口函数:RANK()、DENSE_RANK()、ROW_NUMBER() -
聚合类窗口函数和普通场景下的聚合函数的区别:
- 普通场景下,聚合函数往往和GROUP BY 一起使用,但是在窗口环境下,聚合函数可以应用进来,不同在于:聚合函数将多条记录聚合为一条记录了 (多到一);窗口函数是每条记录都会执行,但是记录执行完还是那么多条记录 (多到多)。(e.g.比如我们想象有一张水果销售明细,里面记录是每一笔成交数据;想看每个种类的水果的销售量,使用普通聚合函数聚类后是 xx水果,销售多少;而使用聚合类窗口函数作查询,是在每一种销售水果的该种类后面加了一列数据,是该种类的销量总和(COUNT))。
- 分组(PARTITION BY):窗口按照字段进行分组,窗口函数会在不同分组上分别执行。
- 排序(ORDER BY):按照哪些字段进行排序,窗口函数将按照排序后的记录顺序进行编号,可以和PARTITION子句配合使用,也可以单独使用。如果每月PARTITION子句,数据范围就是整个表的数据行。
- 窗口(ROWS):就是进行函数分析时要处理的数据范围,数据当前分区的一个子集,通常用来作为滑动窗口使用。比如要根据每个订单动态计算包括本订单和时间顺序前后两个订单的移动平均支付金额,则可以设置ROWS子句来创建滑动窗口(ROWS)。 - -
专用窗口函数的种类:
函数名称 | 函数别名 | 函数意义 |
---|---|---|
RANK函数 | 跳跃排序 | 计算排序时,如果存在相同位次的记录,则会跳过之后的位次;(例如有3条记录排在第1位时:1位、1位、1位、4位…) |
DENSE_RANK函数 | 连续排序 | 计算排序时,即使存在相同位次的记录,也不会跳过之后的排名;(例如有3条记录排在第1位时:1位、1位、1位、2位…) |
RANK_NUMBER函数 | 连续唯一排序 | 计算排序时,赋予唯一的连续位次(例如有3条记录排在第1位时:1位、2位、3位、4位…) |
Reference
牛客网练习
博客园 SQL DATE_FORMAT() 函数
CSDN MySQL date_format()函数
B站 窗口函数讲解 拉勾教育