活动地址:CSDN21天学习挑战赛
作者简介:大家好我是狂暴于涛侠本侠
个人主页:狂暴于涛侠
概述:
在MySQL中,为了提高代码重用性和隐藏实现细节,MySQL提供了很多函数。函数可以理解为别人封装好的模板代码。
概述:
- 在MySQL中,聚合函数主要由:count,sum,min,max,avg,这些聚合函数我们之前都学过,不再重复。这里我们学习另外一个函数:group_concat ( ),该函数用户实现行的合并
- group_concat() 函数首先根据group by指定的列进行分组,并且用分隔符分隔,将同一个分组中的值连接起来,返回一个字符串结果。
语法:
group_concat([distinct] 字段名 [order by 排序字段 asc/desc] [separator ‘分隔符’])
说明:
(1)使用distinct可以排除重复值;
(2)如果需要对结果中的值进行排序,可以使用order by子句;
(3)separator是一个字符串值,默认为逗号。
首先我们先创建一个表格
create database mydb4;
use mydb4;
create table emp(
emp_id int primary key auto_increment comment '编号',
emp_name char(20) not null default '' comment '姓名',
salary decimal(10,2) not null default 0 comment '工资',
department char(20) not null default '' comment '部门'
);
insert into emp(emp_name,salary,department)
values('张晶晶',5000,'财务部'),('王飞飞',5800,'财务部'),('赵刚',6200,'财务部'),('刘小贝',5700,'人事部'),
('王大鹏',6700,'人事部'),('张小斐',5200,'人事部'),('刘云云',7500,'销售部'),('刘云鹏',7200,'销售部'),
('刘云鹏',7800,'销售部');
-- 将所有员工的名字合并成一行
select group_concat(emp_name) from emp;
-- 指定分隔符合并
select group_concat(emp_name separator ';' ) from emp;
-- 指定排序方式和分隔符
select department,group_concat(emp_name separator ';' ) from emp group by department;
-- 指定排序方式和分隔符
select department,group_concat(emp_name order by salary desc separator ';' ) from emp group by department;
-- 求绝对值
selectabs ( -10);
selectabs (10);
selectabs(表示式或者字段) from 表 ;
-- 向上取整
select ceil(1.1); -- 2
select ceil(1.0); -- 1
-- 向下取整
select floor(1.1); -- 1
select floor(1.9); -- 1
-- 取列表最大值
select greatest(1,2,3); -- 3
-- 取列表最小值
select least(1,2,3); -- 1
-- 取模
select mod ( 5,2);-- 1
-- 取x的y次方
select power(2,3); -- 8
-- 取随机数
select rand( ) ;
-- 取随机数在100以内的整数
select floor(rand() *100);
-- 将小数的四舍五入
select round( 3.5415);-- 4
-- 将小数的四舍五入,保留三位小数
select round( 3.5415);-- 3.542
-- 将小数直接截取到指定位数
select truncate(3.1415,3); -- 3.141
-- 字符串函数
-- 1:获取字符串字符个数
select char_length( ' hello' ); -- 5
select char_length('你好吗'); -- 3
-- length取长度,返回的单位是字节
select length( ' hello'); -- 5
select length('你好吗'); -- 9 每个汉字是3个字符
-- 2:字符串合并
select concat( ' hello' , 'world' );-- helloworld
select concat(c1,c2) from table_name;
-- 2:指定分隔符进行字符串合并
select concat_ws( '-', 'hello ' , ' world ' ) ; -- hello-world
-- 3:返回字符串在列表中第一次出现的位置
select field( 'aaa ' , 'aaa' , ' bbb ' , 'ccc'); -- 1
select field( ' bbb' , 'aaa ' , ' bbb' , 'ccc ' ); -- 2
-- 4:去除字符串左边空格
select ltrim( 'aaaa'); -- 去除左边空格
select rtrim( 'aaaa'); -- 去除右边空格
select trim( 'aaaa' ); -- 去除两端空格
-- 5:字符串截取
select mid("helloworld",2,3); -- 从第二个字符开始截取,截取长度为3
-- 6:获取字符串A在字符串B中第一次出现的位置
select position( 'abc' in 'habcelloabcworld ' );-- 2
-- 7:字符串替换
select replace( 'aaahelloaaaworld' , 'aaa' , 'bbb' );-- bbbhellobbbworld
-- 8:字符串翻转
select reverse( 'hello');-- olleh
-- 9:返回字符串的后几个字符
select right( 'hello',3); -- 返回最后三个字符 llo
-- 10:字符串比较
select strcmp( ' hello' , 'world ' );-- -1 和c语言一样也是先比较第一个不一样的字符然后看字符码
-- 11:字符串截取
select substr( 'hello',2,3); -- ell 从第二个字符开始截取,截取三个字符
select substr( 'hello',2,3); -- ell 从第二个字符开始截取,截取三个字符
-- 12:将小写转大写
select ucase ( "helloworld" );-- HELLOWORD
select upper( "helloworld" ) ;-- HELLOWORD
-- 13:将大写转为小写
select lcase( "HELLOWORD" );-- helloword
select lower("HELLOWORD" );-- helloword
-- 日期函数
-- 1:获取时间戳(毫秒值)
select unix_timestamp();
-- 2:将一个日期字符串转为毫秒值
select unix_timestamp( '2021-12-21 08:08:08');
-- 3:将时间戳毫秒值转为指定格式的日期
select from_unixtime(1640045288, '%Y-%m-%d %H:%i:%s')-- %Y-%m-%d %H:%i:%s年月日时分秒
-- 4:获取当前的年月日
select curdate();
select current_date();
-- 5:获取当前的时分秒
SELECT CURRENT_TIME();SELECT CURTIME();
-- 6:获取年月日和时分秒
SELECT CURRENT_TIMESTAMP();
-- 7:从日期字符串中获取年月日
select date('2022-12-12 12:34:56');
-- 8:获取日期之间的差值
select datediff('2021-12-23','2008-08-08');
select datediff(current_date(),'2008-08-08');
-- 9:获取时间的差值(秒级)
select timediff( '12:12:34', '10:18:56'); -- 01:53:38
-- 10:日期格式化
select date_format('2021-1-1 1:1:1',' %Y-%m-%d %H:%i:%s');-- 2021-01-01 01:01:01
select date_format('2021-12-13 11:11:11','%Y-%m-%d %H:%i:%s');
-- 11:将字符串转为日期
select str_to_date('2021-12-13 11:11:11', '%Y-%m-%d %H:%i:%s');
-- 12:将日期进行减法 -- 日期向前跳转
select date_sub('2021-10-01' ,interval 2 day) ;select date_sub('2021-10-01',interval 2 month);
-- 13:将日期进行加法 -- 日期向后跳转
select date_add('2021-10-01',interval 2 day);
select date_add('2021-10-01',interval 2 month);
-- 14:从日期中获取小时
select extract(hour from '2021-12-13 11:12:13');
select extract(year from '2021-12-13 11:12:13');
select extract(month from '2021-12-13 11:12:13');
-- 15:获取给定日期所在月的最后一天
select last_day ('2021-08-13');-- 2021-08-31
-- 16:获取指定年份和天数的日期
select makedate( ' 2021',53);-- 2021-02-22
-- 17:根据日期获取年月日、时分秒
select year('2021-12-13 11:12:13' );
select month('2021-12-13 11:12:13');
select minute('2021-12-1311:12:13');
select quarter('2021-12-13 11:12:13');-- 4 获取季度
-- 18:根据日期获取信息
SELECT MONTHNAME( '2021-12-13 11:12:13');-- 获取月份的英文
SELECT DAYNAME( '2021-12-13 11:12:13' ); -- 获取周几: Monday
SELECT DAYOFMONTH( '2021-12-13 11:12:13'); -- 当月的第几天
SELECT DAYOFWEEK( '2021-12-13 11:12:13'); -- 1:周日2周一
SELECT DAYOFYEAR( '2021-12-13 11:12:13'); -- 获取一年的第几天
SELECT wEEK( '2021-12-13 11:12:13');
SELECT WEEK( '2021-01-01 11:12:13');
SELECT WEEK( '2021-12-31 11:12:13' );
SELECT YEARWEEK( '2021-3-01');
SELECT NOW();
-- 控制流函数
-- IF
select if(5>3,'大于','小于');
select *,if(score >= 85, '优秀','及格') flag from score;
-- IFNULL
select ifnull(5,0); -- 5
select ifnul1(NULL,0);-- 0
select *,ifnull(comm,0) comm_flag from emp ;
-- ISNULL
select isnull(5); -- 0
select isnul1(NULL); -- 1
-- NULLIF
select nullif(12,12); -- null
select nullif(12,10); -- 12 返回第一个值
下面是case语句:
create table orders(
oid int primary key, -- 订单id
price double, -- 订单价格
payType int -- 支付类型(1:微信支付2:支付宝支付3:银行卡支付4:其他)
);
insert into orders values(1,1200,1);
insert into orders values(2,1000,2);
insert into orders values(3,200,3);
insert into orders values(4,3000,1);
insert into orders values( 5,1500,2);
-- 方式1
select
*,
case payType
when 1 then '微信支付'
when 2 then '支付宝支付'
when 3 then '银行卡支付'
else
'其他支付方式'
end as payTypeStr
from orders;
-- 方式2
select
*,
case
when payType=1 then '微信支付'
when payType=2 then '支付宝支付'
when payType=3 then '银行卡支付'
else '其他支付方式'
end as payTypeStr
from orders;
select -- 输出你好 2>1 对之后不会执行下面的
case
when 2>1 then '你好'
when 2<1 then 'hello'
when 3>2 then '正确'
else'其他'
end as info ;
介绍:
MySQL 8.0 新增窗口函数,窗口函数又被称为开窗函数,与Oracle 窗口函数类似,属于MySQL的一大特点.
非聚合窗口函数是相对于聚函数来说的。聚合函数是对一组数据计算后返回单个值(即分组),非聚合函数一次只会处理一行数据。窗口聚合函数在行记录上计算某个字段的结果时,可将窗口范围内的数据输入到聚合函数中,并不改变行数。
语法结构:
window_function ( expr ) OVER (
PARTITION BY …
ORDER BY …
frame_clause
)
其中,window_function 是窗口函数的名称;expr 是参数,有些函数不需要参数;OVER子句包含三个选项:分区(PARTITION BY)
PARTITION BY选项用于将数据行拆分成多个分区(组),它的作用类似于GROUP BY分组。如果省略了 PARTITION BY,所有的数据作为一个组进行计算
排序(ORDER BY)
OVER 子句中的ORDER BY选项用于指定分区内的排序方式,与 ORDER BY 子句的作用类似
以及窗口大小(frame_clause)。
frame_clause选项用于在当前分区内指定一个计算窗口,也就是一个与当前行相关的数据子集。
序号函数有三个:ROW_NUMBER()、RANK()、DENSE_RANK(),可以用来实现分组排序,并添加序号。
格式:
row_number()|rank()|dense_rank() over (
partition by … - -按照什么分组
order by … - -每组按照什么排序
)
我们先创建一个表:
use mydb4;
create table employee(
dname varchar(20), -- 部门名
eid varchar(20),
ename varchar(20),
hiredate date, -- 入职日期
salary double -- 薪资
);
insert into employee values('研发部','1001','刘备','2021-11-01',3000);
insert into employee values('研发部','1002','关羽','2021-11-02',5000);
insert into employee values('研发部','1003','张飞','2021-11-03',7000);
insert into employee values('研发部','1004','赵云','2021-11-04',7000);
insert into employee values('研发部','1005','马超','2021-11-05',4000);
insert into employee values('研发部','1006','黄忠','2021-11-06',4000);
insert into employee values('销售部','1007','曹操','2021-11-01',2000);
insert into employee values('销售部','1008','许褚','2021-11-02',3000);
insert into employee values('销售部','1009','典韦','2021-11-03',5000);
insert into employee values('销售部','1010','张辽','2021-11-04',6000);
insert into employee values('销售部','1011','徐晃','2021-11-05',9000);
insert into employee values('销售部','1012','曹洪','2021-11-06',6000);
-- 对每个部门的员工按照薪资排序,并给出排名 rn1
-- 对每个部门的员工按照薪资排序,并给出排名 rank rn2
-- 对每个部门的员工按照薪资排序,并给出排名 dense-rank rn3
select
dname,
ename,
salary,
row_number() over(partition by dname order by salary desc) as rn1,-- 就算salary一样还是分出第一第二
rank() over(partition by dname order by salary desc) as rn2,-- 就算salary一样不分出第一第二但是没有第二而是直接第三
dense_rank() over(partition by dname order by salary desc) as rn3-- salary一样并列第一还是会有第二
from employee;
-- 求出每个部门薪资排在前三名的员工- 分组求TOPN
--首先下面这个方法可以么?
select
dname,
ename,
salary,
dense_rank() over(partition by dname order by salary desc) as rn
from employee,
where rn <= 3;
-- 要知道是先执行from employee,后立刻执行where,但是此时根本不知道rn是什么根本没办法编译成功
-- 所以我们应该改成下面这种写法
select
*
from -- 先执行form
(
select
dname,
ename,
salary,
dense_rank() over(partition by dname order by salary desc) as rn,-- 这里只是拿dense_rank()来示范而已
from employee
)t
where t.rn <= 3; -- from后执行where
-- 对所有员工进行全局排序(不分组)
-- 不加partition by表示全局排序
select
dname,
ename,
salary,
dense_rank() over( order by salary desc) as rn
from employee;
概念:
在窗口中每条记录动态地应用聚合函数(SUM()、AVG()、MAX()、MIN()、COUNT()),可以动态计算在指定的窗口内的各种聚合函数值。
select
dname,
ename,
hiredate,
salary,
sum(salary) over(partition by dname order by hiredate) as pv1 -- 默认从第一行加到当前行
from employee;
select
dname,
ename,
hiredate,
salary,
sum(salary) over(partition by dname) as pv3
from employee; -- 如果没有order by排序语句 默认把分组内的所有数据进行sum操作
select
dname,
ename,
salary,
sum(salary) over(partition by dname order by hiredate rows between unbounded preceding and current row) as c1
-- rows between unbounded preceding and current row的意思是从默认开始行加到当前行
from employee;
select
dname,
ename,
salary,
sum(salary) over(partition by dname order by hiredate rows between 3 preceding and current row) as c1
-- rows between 3 preceding and current row从上面三行加到当前行
from employee;
select
dname,
ename,
salary,
sum(salary) over(partition by dname order by hiredate rows between 3 preceding and 1 following) as c1
-- rows between 3 preceding and 1 following上面三行加当前行加下面一行
from employee;
select
dname,
ename,
salary,
sum(salary) over(partition by dname order by hiredate rows between current row and unbounded following) as c1
-- rows between current row and unbounded following当前行加到最后
from employee;
介绍-CUME_DIST
用途:分组内小于、等于当前rank值的行数 / 分组内总行数
应用场景:查询小于等于当前薪资(salary)的比例
select
dname,
ename,
salary,
cume_dist() over(order by salary) as rn1, -- 没有partition语句 所有的数据位于一组
cume_dist() over(partition by dname order by salary) as rn2
from employee;
操作
/*
rn1: 没有partition,所有数据均为1组,总行数为12,
第一行:小于等于3000的行数为3,因此,3/12=0.25
第二行:小于等于4000的行数为5,因此,5/12=0.4166666666666667
rn2: 按照部门分组,dname='研发部'的行数为6,
第一行:研发部小于等于3000的行数为1,因此,1/6=0.16666666666666666
*/
介绍-PERCENT_RANK
用途:每行按照公式(rank-1) / (rows-1)进行计算。其中,rank为RANK()函数产生的序号,rows为当前窗口的记录总行数
应用场景:不常用
select
dname,
ename,
salary,
rank() over(partition by dname order by salary desc ) as rank值,
percent_rank() over(partition by dname order by salary desc ) as rn2
from employee;
/*
当前rank值-1 / 总行数-1
rn2:
第一行: (1 - 1) / (6 - 1) = 0
第二行: (1 - 1) / (6 - 1) = 0
第三行: (3 - 1) / (6 - 1) = 0.4
*/
介绍
用途:返回位于当前行的前n行(LAG(expr,n))或后n行(LEAD(expr,n))的expr的值
应用场景:查询前1名同学的成绩和当前同学成绩的差值
-- lag的用法
select
dname,
ename,
hiredate,
salary,
lag(hiredate,1,'2000-01-01') over(partition by dname order by hiredate) as last_1_time,
-- 2000-01-01是自己设定的默认值,如果不设置默认为null
lag(hiredate,2) over(partition by dname order by hiredate) as last_2_time
from employee;
/*
last_1_time: 指定了往上第1行的值,default为'2000-01-01'
第一行,往上1行为null,因此取默认值 '2000-01-01'
第二行,往上1行值为第一行值,2021-11-01
第三行,往上1行值为第二行值,2021-11-02
last_2_time: 指定了往上第2行的值,为指定默认值
第一行,往上2行为null
第二行,往上2行为null
第四行,往上2行为第二行值,2021-11-01
第七行,往上2行为第五行值,2021-11-02
。。。
*/
-- lead的用法
select
dname,
ename,
hiredate,
salary,
lead(hiredate,1,'2000-01-01') over(partition by dname order by hiredate) as last_1_time,
lead(hiredate,2) over(partition by dname order by hiredate) as last_2_time
from employee;
介绍
用途:返回第一个(FIRST_VALUE(expr))或最后一个(LAST_VALUE(expr))expr的值
应用场景:截止到当前,按照日期排序查询第1个入职和最后1个入职员工的薪资
-- 注意, 如果不指定ORDER BY,则进行排序混乱,会出现错误的结果
select
dname,
ename,
hiredate,
salary,
first_value(salary) over(partition by dname order by hiredate) as first,
last_value(salary) over(partition by dname order by hiredate) as last
from employee;
/*
first_value(salary)是开始时的薪资
last_value(salary)是到本行为止最后一个位置的薪资
*/
介绍-NTH_VALUE(expr,n)
用途:返回窗口中第n个expr的值。expr可以是表达式,也可以是列名
应用场景:截止到当前薪资,显示每个员工的薪资中排名第2或者第3的薪资
-- 查询每个部门截止目前薪资排在第二和第三的员工信息
select
dname,
ename,
hiredate,
salary,
nth_value(salary,2) over(partition by dname order by salary) as second_score,
nth_value(salary,3) over(partition by dname order by salary) as third_score
from employee
介绍–NTILE
用途:将分区中的有序数据分为n个等级,记录等级数
应用场景:将每个部门员工按照入职日期分成3组
-- 根据入职日期将每个部门的员工分成3组
select
dname,
ename,
hiredate,
salary,
ntile(3) over(partition by dname order by hiredate ) as rn ,
ntile(4) over(partition by dname order by hiredate ) as rn
-- 如果分组不能整除会先正常算最后按单个算
from employee;
-- 取出每个部门的第一组员工
select
*
from
(
SELECT
dname,
ename,
hiredate,
salary,
NTILE(3) OVER(PARTITION BY dname ORDER BY hiredate ) AS rn
FROM employee
)t
where t.rn = 1;