以牛客的SQL144题数据为例子
此处不再介绍聚合函数/窗口函数的具体函数功能
例如row_number()over(partition by xxx,xxx,...order by xxx,xxx desc,...);
-- 原表数据
1、聚合函数的使用
当进行分组的时候,SELECT的字段必须属于GROUP BY的字段,或者为聚合函数;否则报错!
SUM() / MAX()...代表将分组后的组内所有数据进行相应计算
-- EX1. 查询用户各科目的平均分
-- 错误使用
SELECT uid,exam_id,round(avg(score),2) avg_score FROM exam_record GROUP BY uid
-- 正确使用
SELECT uid,exam_id,round(avg(score),2) avg_score FROM exam_record GROUP BY uid,exam_id
-- 原理示意
2、窗口函数的使用
谨记:窗口函数不改变原数据表解构(在当前表上的滑动计算!可以分区、排序;甚至可以和窗口函数嵌套使用!!!)
-- EX1. 不改变原表结构(所有成绩排名)
SELECT id,uid,exam_id,dense_rank()over(ORDER BY score DESC) FROM exam_record
-- EX2. 可以分区(各科目内的成绩排名)
SELECT id,uid,exam_id,dense_rank()over(PARTITION BY exam_id ORDER BY score DESC) FROM exam_record
3、嵌套使用
窗口函数可以和聚合函数嵌套使用,但是请注意执行顺序:
先分组--->组内做聚合运算--->聚合后对当前表进行窗口函数。
练习:牛客SQL144
Q: 请输出自从有用户作答记录以来,每月的试卷作答记录中月活用户数、新增用户数、截止当月的单月最大新增用户数、截止当月的累积用户数。结果按月份升序输出。
STEP1. 判断用户是否是新用户
-- 通过加入新的一列做标记(使用min()over()窗口函数)
-- 每月新增,可以根据该uid的start_time是否等于该uid的最小时间
WITH t AS
(
SELECT uid,
DATE_FORMAT(start_time,'%Y%m') start_month,
IF(start_time = min(start_time)over(PARTITION BY uid),1,0) new_account
FROM exam_record
)
STEP2. 此表基础上分组聚合、窗口函数嵌套
1、月活用户:按月分组的count(distinct user_id)
2、每月新增用户:按月分组的sum(new_account)
3、截至当月单月最大新增:求出每月新增;此表基础上进行窗口函数使用(滑动)--分区,最大
4、截至当月单月累计新增:求出每月新增;此表基础上进行窗口函数使用(滑动)--分区,求和
SELECT start_month,
COUNT(DISTINCT uid) mau,
SUM(new_account) month_add_uv, -- 每月新增
-- max(sum(A))over(order by B),over中使用order by 则是整体的max;partition by则是组内的max
MAX(SUM(new_account))OVER(ORDER BY start_month) max_month_add_uv,
SUM(SUM(new_account))OVER(ORDER BY start_month) cum_sum_uv -- 注意两层使用时候的()问题
FROM t
GROUP BY start_month
ORDER BY start_month
结构示意(MAX(SUM(new_account))OVER(ORDER BY start_month))
中间图是group by之后的表结构
右图是窗口函数滑动之后的表结构
整体代码
-- 1、每月新增:通过加入新的一列做标记(使用min()over()窗口函数)
-- 可以根据该uid的start_time是否等于该uid的最小时间
WITH t AS
(
SELECT uid,
DATE_FORMAT(start_time,'%Y%m') start_month,
IF(start_time = min(start_time)over(PARTITION BY uid),1,0) new_account
FROM exam_record
)
-- 2、在当前表上进行聚合与窗口的嵌套使用
SELECT start_month,
COUNT(DISTINCT uid) mau,
SUM(new_account) month_add_uv, -- 每月新增
-- max(sum(A))over(order by B),over中使用order by 则是整体的max;partition by则是组内的max
MAX(SUM(new_account))OVER(ORDER BY start_month) max_month_add_uv,
SUM(SUM(new_account))OVER(ORDER BY start_month) cum_sum_uv -- 注意两层使用时候的()问题
FROM t
GROUP BY start_month
ORDER BY start_month