vlambda博客
学习文章列表

【SQL】MySQL中窗口函数和单行函数的使用

  今天接着昨天的内容讲解一下 SQL 中函数的使用。其中窗口函数是考察的重点。需要注意的是 MySQL 需要 8.0 的版本才能使用窗口函数。可以点击文章最下方的阅读原文到 CSDN 中阅读体验更佳。

1.窗口函数

  窗口函数也叫OLAP函数(Online Anallytical Processing,联机分析处理),可以对数据进行实时分析处理。窗口函数是面试中考察的重点。窗口函数通常用来解决统计汇总、排名、TopN、连续登录天数等问题。

语法:函数名(字段名) over(partition by <要分列的组> order by <要排序的列> rows between <数据范围>)

数据范围:通过下面的案例来讲解数据范围如何使用。

# 取本行和前面两行
rows between 2 preceding and current row

# 取本行和之前所有的行
rows between unbounded preceding and current row

# 取本行和之后所有的行
rows between current row and unbounded following

# 从前面三行和下面一行,总共五行
rows between 3 preceding and 1 following

# 当order by后面没有rows between时,窗口规范默认是取本行和之前所有的行
# 当order by和rows between都没有时,窗口规范默认是分组下所有行(rows between unbounded preceding and unbounded following)

分类:按照窗口函数的意义大概可以分为下面5类,其中排序函数最为常用。

  • 排序函数:row_number()、rank()、dense_rank()

  • 分布函数:percent_rank()、cume_dist()

  • 相对位置函数:lag(expr,n)、lead(expr,n),用于返回某字段的前 n 行或后 n 行的值。expr 既可以是表达式也可以是列名。

  • 绝对位置函数:first_value(expr)、last_value(expr)、nth_value(expr,n),返回第一个或最后一个或第 n 个 expr的值。

  • 分桶函数:ntile(x)

另外,聚合函数也可以作为窗口函数使用:

  • 聚合函数:avg(),sum(),min(),max()

1.1 排序函数
  • row_number():对每一行分配一个序号,序号连续加1,不会重复。常用于排序。

  • rank():给每行分配一个序号,相同值的序号相同,序号不连续。常用于排序。

  • dense_rank():给每行分配一个序号,相同值的序号相同,序号不连续。常用于排序。

应用场景举例:对日期进行排序:

select
log_date,
row_number() over(order by log_date) as rn,
rank() over(order by log_date) as r,
dense_rank() over(order by log_date) as dr
from tb
order by log_date;

结果如下:

log_date rn r dr
20220401 1 1 1
20220401 2 1 1
20220403 3 3 2
20220406 4 4 3
20220406 5 4 3
20220408 6 6 4
1.2 分布函数
  • percent_rank():每行按照公式(rank-1) / (rows-1)进行计算。其中,rank为RANK()函数产生的序号,rows为当前窗口的记录总行数。

  • cume_dist():分组内小于、等于当前rank值的行数 / 分组内总行数

应用场景举例:

select
score,
rank() over(order by score desc) as rank,
percent_rank() over(order by score desc) as pr,
cume_dist() over(order by score desc) as cd
from tb
order by score desc;

结果如下:

score rank pr cd
100 1 0 0.33333
100 1 0 0.33333
94 3 0.4 0.66666
94 3 0.4 0.66666
80 5 0.8 1
80 5 0.8 1
1.3 相对位置函数
  • lag(expr,n):返回位于当前行的前 n 行的值

  • lead(expr,n):返回位于当前行的后 n 行的值

应用场景举例:求下一次销售时间

select
name,
log_date,
lead(log_date,1) over(partition by name order by log_date) next_ld
from tb

结果如下:

name log_date next_ld
aa 2019-10-01 2020-04-18
aa 2020-04-18 2021-11-06
aa 2021-11-06 NULL
bb 2020-05-08 2020-07-12
bb 2020-07-12 NULL
1.4 绝对位置函数
  • first_value(expr):返回第一个 expr 的值。

  • last_value(expr):返回最后一个 expr 的值。

  • nth_value(expr,n):返回窗口中第 n 个 expr 的值。

应用场景 1 举例:求首次登录和末次登录时间

select
id,
log_dt,
first_value(log_dt) over(partition by id order by log_dt) f_dt,
last_value(log_dt) over(partition by id order by log_dt) l_dt
from tb;

结果如下:

id log_dt f_dt l_dt
1 2020-11-10 2020-11-10 2020-11-10
1 2021-01-20 2020-11-10 2021-01-20
1 2021-08-12 2020-11-10 2021-08-12
2 2021-12-05 2021-12-05 2021-12-05
2 2021-12-29 2021-12-05 2021-12-29

应用场景 2 举例:求部门中工资第二的员工

SELECT
id,
dept_id did,
salary s,
NTH_VALUE(salary,2) over(PARTITION BY dept_id ORDER BY salary DESC) s2
FROM employee;

结果如下:

id did s s2
2 1 200 100
1 1 100 100
4 2 400 200
3 2 300 200
6 3 550 500
5 3 500 500
1.5 分桶函数
  • ntile(n):对每个分区继续分成 n 组,每组的行数为:分区的总行数 / n。不常用。

1.6 窗口的定义

对于反复使用的窗口,可以单独提取出来简化代码,例如:

select
id,
log_dt,
first_value(log_dt) over(partition by id order by log_dt) f_dt,
last_value(log_dt) over(partition by id order by log_dt) l_dt
from tb;

select
id,
log_dt,
first_value(log_dt) over win\
f_dt,
last_value(log_dt) over win l_dt
from tb
window win as (partition by id order by log_dt);
2.日期时间函数
  • 日期时间函数较多,经常考察的用加粗字体标出。

  • CURDATE() 或 CURRENT_DATE() 返回当前日期

  • CURTIME() 或 CURRENT_TIME() 返回当前时间

  • NOW() 返回当前系统日期时间

  • SYSDATE() 同上

  • CURRENT_TIMESTAMP() 同上

  • LOCALTIME() 同上

  • LOCALTIMESTAMP() 同上

  • YEAR(date) 返回年

  • MONTH(date) 返回月

  • DAY(date) 返回日

  • HOUR(time) 返回时

  • MINUTE(time) 返回分

  • SECOND(time) 返回秒

  • WEEK(date) 返回一年中的第几周

  • WEEKOFYEAR(date) 同上

  • DAYOFWEEK() 返回周几,注意:周日是1,周一是2,...周六是7

  • WEEKDAY(date) 返回周几,注意,周1是0,周2是1,...周日是6

  • DAYNAME(date) 返回星期:MONDAY,TUESDAY,...SUNDAY

  • MONTHNAME(date) 返回月份:January,...

  • DATEDIFF(date1,date2) 返回date1 - date2的日期间隔

  • TIMEDIFF(time1, time2) 返回time1 - time2的时间间隔

  • DATE_ADD(datetime, INTERVAL expr type) 返回与给定日期时间相差 INTERVAL 时间段的日期时间。表达式类型:

SELECT DATE_ADD(NOW(), INTERVAL 1 YEAR);
SELECT DATE_ADD(NOW(), INTERVAL -1 YEAR); #可以是负数
SELECT DATE_ADD(NOW(), INTERVAL '1_1' YEAR_MONTH); #需要单引号

#YEAR DAY_HOUR YEAR_MONTH
#MONTH DAY_MINUTE
#DAY DAY_SECOND
#HOUR HOUR_MINUTE
#MINUTE HOUR_SECOND
#SECOND MINUTE_SECOND
  • DATE_FORMAT(datetime ,fmt) 按照字符串 fmt 格式化日期 datetime 值。fmt的格式见下方,常用的格式化用加粗表示。

    • %Y 4位数字表示年份

    • %y 表示两位数字表示年份

    • %M 月名表示月份(January,...)

    • %m 两位数字表示月份(01,02,03,...)

    • %b 缩写的月名(Jan.,Feb.,...)

    • %c 数字表示月份(1,2,3,...)

    • %D 英文后缀表示月中的天数(1st,2nd,3rd,...)

    • %d 两位数字表示月中的天数(01,02...)

    • %e 数字形式表示月中的天数(1,2,3,4,5,...)

    • %H 两位数字表示小时,24小时制(01,02,...)

    • %h和%I 两位数字表示小时,12小时制(01,02,...)

    • %k 数字形式的小时,24小时制(1,2,3,...)

    • %l 数字形式表示小时,12小时制(1,2,3,4,...)

    • %i 两位数字表示分钟(00,01,02,...)

    • %W 一周中的星期名称(Sunday,...)

    • %a 一周中的星期缩写(Sun.,Mon.,Tues.,...)

    • %w 以数字表示周中的天数(0=Sunday,1=Monday....)

    • %j 以3位数字表示年中的天数(001,002,...)

    • %U 以数字表示年中的第几周,(1,2,3,...)其中Sunday为周中第一天

    • %u 以数字表示年中的第几周,(1,2,3,...)其中Monday为周中第一天

    • %T 24小时制

    • %r 12小时制

    • %p AM或PM

    • %% 表示%

DATE_FORMAT(datetime, '%Y-%m')    # ’2020-02‘
DATE_FORMAT(datetime, '%Y%m%d') # ’20200201‘
  • STR_TO_DATE(str, fmt) 按照字符串fmt对str进行解析,解析为一个日期

3.流程函数
  • IF(value,t ,f) 如果value是真,返回t,否则返回f

  • IFNULL(value1, value2) 如果value1不为空,返回value1,否则返回value2

  • CASE WHEN

# 相当于 if else
CASE
WHEN 条件1 THEN result1
WHEN 条件2 THEN result2
....
[ELSE resultn]
END

# 相当于 switch
CASE expr
WHEN 常量值1 THEN1
WHEN 常量值1 THEN1
....
[ELSE 值n]
END
4.数学函数
  • ABS(x) 返回 x 的绝对值

  • CEIL(x) 返回大于 x 的最小整数值

  • FLOOR(x) 返回小于 x 的最大整数值

  • MOD(x,y) 返回 x/y 的模

  • RAND(x) 返回0~1的随机值,x可以不写

  • ROUND(x,y) 返回参数 x 的四舍五入的有 y 位的小数的值

  • TRUNCATE(x,y) 返回数字 x 截断为 y 位小数的结果

  • SQRT(x) 返回 x 的平方根

  • POW(x,y) 返回 x 的 y 次方

5.字符串函数

字符串函数较多,经常考察的用加粗字体标出。

  • CONCAT(S1,S2,......,Sn) 连接 S1, S2, ......, Sn 为一个字符串

  • CONCAT_WS(s, S1, S2, ......, Sn) 同 CONCAT(s1, s2, ...)函数,但是每个字符串之间要加上 s

  • CHAR_LENGTH(s) 返回字符串s的字符数

  • LENGTH(s) 返回字符串s的字节数,和字符集有关

  • INSERT(str, index , len, instr) 将字符串 str 从第 index 位置开始,len 个字符长的子串替换为字符串 instr

  • UPPER(s) 或 UCASE(s) 将字符串 s 的所有字母转成大写字母

  • LOWER(s) 或 LCASE(s) 将字符串 s 的所有字母转成小写字母

  • LEFT(s,n) 返回字符串 s 最左边的 n 个字符

  • RIGHT(s,n) 返回字符串 s 最右边的 n 个字符

  • LPAD(str, len, pad) 用字符串 pad 对 str 最左边进行填充,直到str的长度为len个字符

  • RPAD(str ,len, pad) 用字符串 pad 对 str 最右边进行填充,直到 str 的长度为 len 个字符

  • LTRIM(s) 去掉字符串 s 左侧的空格

  • RTRIM(s) 去掉字符串 s 右侧的空格

  • TRIM(s) 去掉字符串 s 开始与结尾的空格

  • TRIM(【BOTH 】s1 FROM s) 去掉字符串 s 开始与结尾的 s1

  • TRIM(LEADING s1 FROM s) 去掉字符串 s 开始处的 s1

  • TRIM(TRAILING s1 FROM s) 去掉字符串 s 结尾处的 s1

  • REPEAT(str, n) 返回 str 重复 n 次的结果

  • REPLACE(str, a, b) 用字符串 b 替换字符串 str 中所有出现的字符串 a

  • STRCMP(s1,s2) 比较字符串 s1, s2

  • SUBSTRING(s,index,len) 返回从字符串 s 的 index 位置其 len 个字符。index 从 1 开始