以牛客的SQL144题数据为例子

此处不再介绍聚合函数/窗口函数的具体函数功能

例如row_number()over(partition by xxx,xxx,...order by xxx,xxx desc,...);

-- 原表数据

mysql使用聚合函数可以不用group by吗_mysql

1、聚合函数的使用

当进行分组的时候,SELECT的字段必须属于GROUP BY的字段,或者为聚合函数;否则报错!

SUM() / MAX()...代表将分组后的组内所有数据进行相应计算

-- EX1. 查询用户各科目的平均分

-- 错误使用

SELECT uid,exam_id,round(avg(score),2) avg_score FROM exam_record GROUP BY uid

mysql使用聚合函数可以不用group by吗_窗口函数_02

-- 正确使用

SELECT uid,exam_id,round(avg(score),2) avg_score FROM exam_record GROUP BY uid,exam_id

mysql使用聚合函数可以不用group by吗_窗口函数_03

-- 原理示意

mysql使用聚合函数可以不用group by吗_mysql_04

2、窗口函数的使用

谨记:窗口函数不改变原数据表解构(在当前表上的滑动计算!可以分区、排序;甚至可以和窗口函数嵌套使用!!!)

-- EX1. 不改变原表结构(所有成绩排名)

SELECT id,uid,exam_id,dense_rank()over(ORDER BY  score DESC) FROM exam_record

mysql使用聚合函数可以不用group by吗_聚合函数_05

-- EX2. 可以分区(各科目内的成绩排名)

SELECT id,uid,exam_id,dense_rank()over(PARTITION BY exam_id ORDER BY  score DESC) FROM exam_record

mysql使用聚合函数可以不用group by吗_mysql_06

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之后的表结构

右图是窗口函数滑动之后的表结构

mysql使用聚合函数可以不用group by吗_数据库_07

整体代码

-- 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