MySQL:开窗函数

当查询条件需要用到复杂子查询时,聚合函数操作起来非常麻烦,因此使用开窗函数能够轻松实现。

注意:在Oracle中称为分析函数。

           在MySQL中称为开窗函数,使用于MySQL8.0以上版本,sql sever、hive、Oracle等。

1 开窗函数

开窗函数:为将要被操作的行的集合定义一个窗口,它对一组值进行操作,不需要使用GROUP BY子句对数据进行分组,能够在同一行中同时返回基础行的列和聚合列。

语法:函数 + over(partition by <分组用列> order by <排序用列>)

over() 按所有行进行分组

over(partition by xxx)按xxx分组的所有行进行分组

over(partition by xxx order by aaa)按列xxx分组,按列aaa排序

over(order by aaa) 按aaa列排序

括号中的两个关键词partition by 和order by可以只出现一个。

开窗函数表示对数据集按照分组用列进行分区,并且对每个分区按照函数聚合计算,最终将计算结果按照排序用列排序后返回到该行。

无论何种能力,窗口函数都不会影响数据行数,而是将计算平摊在每一行。

1.1 开窗函数分类

名称 描述
cume_dist() 计算一组值中一个值的累积分布
dense_rank() 根据该order by子句为分区中的每一行分配一个等级。它将相同的等级分配给具有相等值的行。如果两行或更多行具有相同的排名,则排名值序列中将没有间隙
first_value() 返回相对于窗口框架第一行的指定表达式的值
lag() 返回分区中当前行之前的第N行的值。如果不存在前一行,则返回NULL
last_value() 返回相对于窗口框架中最后一行的指定表达式的值
lead() 返回分区中当前行之后的第N行的值。如果不存在后续行,则返回NULL
nth_value() 从窗口框架的第N行返回参数的值
ntile() 将每个窗口分区的行分配到指定数量的排名组中
percent_rank() 计算分区域结果集中行的百分数等级
rank() 与dense_rank()函数相似,不同之处在于当两行或更多行具有相同的等级时,等级值序列中存在间隙
row_number() 为分区中的每一行分配一个顺序整数

按照函数功能不同,MySQL支出的开窗函数分为如下几类:

  • 序号函数:row_number() / rank() / dense_rank()
  • 分布函数:percent_rank() / cume_dist()
  • 前后函数:lag() / lead()
  • 头尾函数:first_value() / last_value()
  • 其他函数 :nth_value() / nfile()

2 排序开窗函数和聚合开窗函数

2.1 排序开窗函数

  • row_number(行号)
  • rank(排名)
  • dense_rank(密集排名)
  • ntile(分组排名)

例1 先对所有数据进行排序

select s.sid,s1.sname,s1.gender,c.cname,s.num,   row_number() over 
(partition by c.cname order by num desc) as row_number排名,   
rank() over (partition by c.cname order by num desc) as rank排名,   
dense_rank() over (partition by c.cname order by num desc) as dense_rank排名,   
ntile(6) over (partition by c.cname order by num desc) as ntile排名    
from score s   join student s1 on s.student_id = s1.sid   
left join course c on s.course_id = c.cid

结果如下:

MySQL:开窗函数_第1张图片 row_number

根据课程进行分组,然后对每组内的成绩进行降序排序

又上图可知row_number对于同组内的相同成绩并没有做特殊处理,而仅仅是生成连续的序号。row_number常用于按照某列生成连续序号。

rank

rank函数就是对查询出来的记录进行排名。

与row_number函数不同的是,rank函数考虑到了over子句中排序字段值相同的情况,over子句中排序字段值相同的序号是一样的,后面字段值不相同的序号将跳过相同的排名号排下一个,也就是相关行之前的排名数加一,通过上面的例子我们也可以看出,rank考虑到值相同情况,并且它的排名存在跳跃性。

dense_rank

密集排名,在考虑了值相同时,排名也相同,但是序号不跳跃,紧跟上一个序号。

例如题目中体育成绩有2位同学(张三和刘三)并列第一,如果使用rank排名 ,那钢蛋就是第三名,而如果采用dense_rank 那钢蛋就是第二名,这个很容易理解吧。

ntile

ntile会先根据你的分组依据,本题中是课程名称,然后把每个组的总记录数进行按照你给的ntile()里的数字进行,这个数字就是桶数,相当于是把体育课程总共12条记录,尽量等划分成5桶,然后按照num的排序等级划分,每个桶两条记录,也就是112233445566的排序结果了,很显然,这个排序结果的数字大小只能用于桶与桶之间,而桶内部记录虽然序号相同,但是num不一定相同。

例如:统计各科成绩前三 

select * from 
(select s.sid,s1.sname,s1.gender,c.cname,s.num,dense_rank() 
over (partition by c.cname order by num desc) as dense_rank排名 from score s
join student s1 on s.student_id = s1.sid
left join course c on s.course_id = c.cid) as e
where dense_rank排名 <= 3;

2.2 聚合开窗函数

函数名如果是聚合函数,则称为聚合开窗函数

语法:聚合函数(列) over(partition by 列 order by 列)

常见的聚合函数有:sum()、count()、average()、max()、min()

计算每个学生的及格科目数

select student_id,count(sid) from  score where num>= 60 group by student_id;

MySQL:开窗函数_第2张图片

通过普通的聚合函数分组计算后,数据表结构发生了变化,它会根据分组进行显示,并且,如果你是根据学生ID分组,那你查询的字段应该也是学生ID,不然会影响到分组结果所对应的数值,例如现在查询条件在添加一个Sid

select sid,student_id,count(sid) from  score where num>= 60 group by student_id;

 MySQL:开窗函数_第3张图片

sid的数据并没有实际意义,因为数据表已经根据分组发生了变化。

执行结果

select sid,student_id,count(sid) over(PARTITION by student_id order
 by student_id) 及格数   from score where num>= 60;

 MySQL:开窗函数_第4张图片

 总结:开窗函数不会修改源数据表的结果,也是在表的最后一列添加想要的结果。

实例数据

CREATE TABLE class (
  cid int(11) NOT NULL AUTO_INCREMENT,
  caption varchar(32) NOT NULL,
  PRIMARY KEY (cid)
) ENGINE=InnoDB CHARSET=utf8;

INSERT INTO class VALUES
(1, '三年二班'), 
(2, '三年三班'), 
(3, '一年二班'), 
(4, '二年九班');

CREATE TABLE teacher(
  tid int(11) NOT NULL AUTO_INCREMENT,
  tname varchar(32) NOT NULL,
  PRIMARY KEY (tid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO teacher VALUES
(1, '张磊老师'), 
(2, '李平老师'), 
(3, '刘海燕老师'), 
(4, '朱云海老师'), 
(5, '李杰老师');

CREATE TABLE course(
  cid int(11) NOT NULL AUTO_INCREMENT,
  cname varchar(32) NOT NULL,
  teacher_id int(11) NOT NULL,
  PRIMARY KEY (cid),
  KEY fk_course_teacher (teacher_id),
  CONSTRAINT fk_course_teacher FOREIGN KEY (teacher_id) REFERENCES teacher (tid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO course VALUES
(1, '生物', 1), 
(2, '物理', 2), 
(3, '体育', 3), 
(4, '美术', 2);

CREATE TABLE student(
  sid int(11) NOT NULL AUTO_INCREMENT,
  gender char(1) NOT NULL,
  class_id int(11) NOT NULL,
  sname varchar(32) NOT NULL,
  PRIMARY KEY (sid),
  KEY fk_class (class_id),
  CONSTRAINT fk_class FOREIGN KEY (class_id) REFERENCES class (cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO student VALUES
(1, '男', 1, '理解'), 
(2, '女', 1, '钢蛋'), 
(3, '男', 1, '张三'), 
(4, '男', 1, '张一'), 
(5, '女', 1, '张二'), 
(6, '男', 1, '张四'), 
(7, '女', 2, '铁锤'), 
(8, '男', 2, '李三'), 
(9, '男', 2, '李一'), 
(10, '女', 2, '李二'), 
(11, '男', 2, '李四'), 
(12, '女', 3, '如花'), 
(13, '男', 3, '刘三'), 
(14, '男', 3, '刘一'), 
(15, '女', 3, '刘二'), 
(16, '男', 3, '刘四');

CREATE TABLE score (
  sid int(11) NOT NULL AUTO_INCREMENT,
  student_id int(11) NOT NULL,
  course_id int(11) NOT NULL,
  num int(11) NOT NULL,
  PRIMARY KEY (sid),
  KEY fk_score_student (student_id),
  KEY fk_score_course (course_id),
  CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course (cid),
  CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(sid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO score VALUES
(1, 1, 1, 10),
(2, 1, 2, 9),
(5, 1, 4, 66),
(6, 2, 1, 8),
(8, 2, 3, 68),
(9, 2, 4, 99),
(10, 3, 1, 77),
(11, 3, 2, 66),
(12, 3, 3, 87),
(13, 3, 4, 99),
(14, 4, 1, 79),
(15, 4, 2, 11),
(16, 4, 3, 67),
(17, 4, 4, 100),
(18, 5, 1, 79),
(19, 5, 2, 11),
(20, 5, 3, 67),
(21, 5, 4, 100),
(22, 6, 1, 9),
(23, 6, 2, 100),
(24, 6, 3, 67),
(25, 6, 4, 100),
(26, 7, 1, 9),
(27, 7, 2, 100),
(28, 7, 3, 67),
(29, 7, 4, 88),
(30, 8, 1, 9),
(31, 8, 2, 100),
(32, 8, 3, 67),
(33, 8, 4, 88),
(34, 9, 1, 91),
(35, 9, 2, 88),
(36, 9, 3, 67),
(37, 9, 4, 22),
(38, 10, 1, 90),
(39, 10, 2, 77),
(40, 10, 3, 43),
(41, 10, 4, 87),
(42, 11, 1, 90),
(43, 11, 2, 77),
(44, 11, 3, 43),
(45, 11, 4, 87),
(46, 12, 1, 90),
(47, 12, 2, 77),
(48, 12, 3, 43),
(49, 12, 4, 87),
(52, 13, 3, 87);

你可能感兴趣的:(Mysql8.0,mysql,数据库)