MySQL中的函数是一段可重复使用的代码,可以接受参数并返回一个值。MySQL中提供了很多内置函数,可以用于数据处理、数值计算、字符串操作、日期和时间处理等多个方面。
MySQL中的函数可以分为以下几类:
聚合函数:例如COUNT、SUM、AVG、MAX和MIN等,用于对数据进行汇总和统计。
字符串函数:例如CONCAT、SUBSTRING、LENGTH、TRIM和LOWER等,用于处理字符串数据。
数值函数:例如ABS、ROUND、CEILING、FLOOR和RAND等,用于对数值数据进行计算和处理。
日期和时间函数:例如NOW、YEAR、MONTH、DAY和DATE_FORMAT等,用于对日期和时间数据进行处理。
条件函数:例如IF、CASE和COALESCE等,用于根据特定条件返回不同的结果。
MySQL还支持自定义函数,可以使用CREATE FUNCTION语句创建自定义函数。自定义函数可以接受参数并返回一个值,与内置函数类似。使用自定义函数可以简化复杂的数据处理和计算操作,提高SQL语句的可读性和可维护性。
这个函数在之前的SQL中已经使用过了,函数有两个参数,形式为: IFNULL(field, value)
逻辑: 如果第一个参数field是NULL,则返回第二个参数value的值,否则就返回field本身的值
select ifnull(null, 10); -- 查询结果是10
select ifnull(10, 20); -- 查询结果是10
select ifnull(`id`, 0); -- 如果id字段的值是NULL,就返回0,否则就返回id字段的值
这个函数有点类似于Java中的三目运算符,函数有三个参数,形式为: IF(condition, value1, value2)
逻辑: 如果第一个参数condition条件成立,就返回value1的值,否则就返回value2的值。
select if(10 > 20, 10, 20); -- 因为10 > 20不成立,因此返回20
select if(10 > 5, 10, 5); -- 因为10 > 5成立,因此返回10
select if(`chinese` > `math`, `chinese`, `math`); -- 如果chinese字段的值大于math字段的值,就返回chinese字段的值,否则就返回math字段的值
这个函数是MySQL中非常强大的一个函数,可以使用他来实现类似Java中的分支结构。通过模拟Java的if的实现和switch的实现,来实现一些简单的分支流程控制。case…when有两种写法:
类似Java的if
-- 需求: 已知sc表中有一个字段名为`score`表示一个学生的成绩。通过这个成绩字段的不同范围,查询出不同的结果:
-- < 0 或者 > 100, 等级为"错误成绩"
-- [0, 60), 等级为"不及格"
-- [60, 80), 等级为"良"
-- [80, 100], 等级为"优"
select `sid`, `score`,
case
when `score` < 0 or `score` > 100 then '错误成绩'
when `score` < 60 then '不及格'
when `score` < 80 then '良'
else '优'
end as 'level'
from `sc`;
类似Java的switch
-- 需求: 已知sc表中有一个字段名为`subject`表示成绩的科目。通过这个字段的不同的值,查询出不同的描述信息:
-- 如果是chinese,查询出"语文"
-- 如果是math,查询出"数学"
-- 如果是english,查询出"英语"
-- 其他的保持原样
select `subject`,
case `subject`
when 'chinese' then '语文'
when 'math' then '数学'
when 'english' then '英语'
else `subject`
end as '翻译'
from `sc`
数据准备
sname | subject | score |
---|---|---|
张小三 | 语文 | 78 |
张小三 | 数学 | 77 |
张小三 | 英语 | 90 |
张小三 | 历史 | 89 |
张小三 | 体育 | 80 |
李小四 | 数学 | 90 |
李小四 | 英语 | 80 |
李小四 | 体育 | 88 |
李小四 | 政治 | 88 |
李小四 | 历史 | 78 |
王小五 | 语文 | 90 |
王小五 | 英语 | 80 |
王小五 | 政治 | 89 |
王小五 | 体育 | 90 |
-- 建表
create table `sc` (
`sname` varchar(20),
`subject` varchar(20),
`score` int
);
-- 插入数据
insert into `sc` values
('张小三', '语文', 78),
('张小三', '数学', 77),
('张小三', '英语', 90),
('张小三', '历史', 89),
('张小三', '体育', 80),
('李小四', '数学', 90),
('李小四', '英语', 80),
('李小四', '体育', 88),
('李小四', '政治', 88),
('李小四', '历史', 78),
('王小五', '语文', 90),
('王小五', '英语', 80),
('王小五', '政治', 89),
('王小五', '体育', 90);
需求实现1
姓名 | 语文 | 数学 | 英语 | 历史 | 政治 | 体育 |
---|---|---|---|---|---|---|
张小三 | 78 | 77 | 90 | 89 | 0 | 80 |
李小四 | 0 | 90 | 80 | 78 | 88 | 88 |
王小五 | 90 | 0 | 80 | 0 | 89 | 90 |
-- if实现版本
select `sname` as '姓名',
sum(if(`subject` = '语文', `score`, 0)) as '语文',
sum(if(`subject` = '数学', `score`, 0)) as '数学',
sum(if(`subject` = '英语', `score`, 0)) as '英语',
sum(if(`subject` = '历史', `score`, 0)) as '历史',
sum(if(`subject` = '政治', `score`, 0)) as '政治',
sum(if(`subject` = '体育', `score`, 0)) as '体育'
from `sc` group by `sname`;
-- case实现版本
select `sname` as '姓名',
sum(case `subject` when '语文' then `score` else 0 end) as '语文',
sum(case `subject` when '数学' then `score` else 0 end) as '数学',
sum(case `subject` when '英语' then `score` else 0 end) as '英语',
sum(case `subject` when '历史' then `score` else 0 end) as '历史',
sum(case `subject` when '政治' then `score` else 0 end) as '政治',
sum(case `subject` when '体育' then `score` else 0 end) as '体育'
from `sc` group by `sname`;
需求实现2
姓名 | 语文 | 数学 | 英语 | 历史 | 政治 | 体育 | 总成绩 |
---|---|---|---|---|---|---|---|
张小三 | 78 | 77 | 90 | 89 | 0 | 80 | 414 |
李小四 | 0 | 90 | 80 | 78 | 88 | 88 | 424 |
王小五 | 90 | 0 | 80 | 0 | 89 | 90 | 349 |
-- if实现版本
select `sname` as '姓名',
sum(if(`subject` = '语文', `score`, 0)) as '语文',
sum(if(`subject` = '数学', `score`, 0)) as '数学',
sum(if(`subject` = '英语', `score`, 0)) as '英语',
sum(if(`subject` = '历史', `score`, 0)) as '历史',
sum(if(`subject` = '政治', `score`, 0)) as '政治',
sum(if(`subject` = '体育', `score`, 0)) as '体育',
sum(`score`) as '总成绩'
from `sc` group by `sname`;
-- case实现版本
select `sname` as '姓名',
sum(case `subject` when '语文' then `score` else 0 end) as '语文',
sum(case `subject` when '数学' then `score` else 0 end) as '数学',
sum(case `subject` when '英语' then `score` else 0 end) as '英语',
sum(case `subject` when '历史' then `score` else 0 end) as '历史',
sum(case `subject` when '政治' then `score` else 0 end) as '政治',
sum(case `subject` when '体育' then `score` else 0 end) as '体育',
sum(`score`) as '总成绩'
from `sc` group by `sname`;
需求实现3
姓名 | 语文 | 数学 | 英语 | 历史 | 政治 | 体育 | 总成绩 |
---|---|---|---|---|---|---|---|
张小三 | 78 | 77 | 90 | 89 | 0 | 80 | 414 |
李小四 | 0 | 90 | 80 | 78 | 88 | 88 | 424 |
王小五 | 90 | 0 | 80 | 0 | 89 | 90 | 349 |
总成绩 | 168 | 167 | 250 | 167 | 177 | 258 | 1187 |
-- if实现版本
select `sname` as '姓名',
sum(if(`subject` = '语文', `score`, 0)) as '语文',
sum(if(`subject` = '数学', `score`, 0)) as '数学',
sum(if(`subject` = '英语', `score`, 0)) as '英语',
sum(if(`subject` = '历史', `score`, 0)) as '历史',
sum(if(`subject` = '政治', `score`, 0)) as '政治',
sum(if(`subject` = '体育', `score`, 0)) as '体育',
sum(`score`) as '总成绩'
from `sc` group by `sname`
union
select '总成绩' as '姓名',
sum(if(`subject` = '语文', `score`, 0)) as '语文',
sum(if(`subject` = '数学', `score`, 0)) as '数学',
sum(if(`subject` = '英语', `score`, 0)) as '英语',
sum(if(`subject` = '历史', `score`, 0)) as '历史',
sum(if(`subject` = '政治', `score`, 0)) as '政治',
sum(if(`subject` = '体育', `score`, 0)) as '体育',
sum(`score`) as '总成绩'
from `sc`;
-- case实现版本
select `sname` as '姓名',
sum(case `subject` when '语文' then `score` else 0 end) as '语文',
sum(case `subject` when '数学' then `score` else 0 end) as '数学',
sum(case `subject` when '英语' then `score` else 0 end) as '英语',
sum(case `subject` when '历史' then `score` else 0 end) as '历史',
sum(case `subject` when '政治' then `score` else 0 end) as '政治',
sum(case `subject` when '体育' then `score` else 0 end) as '体育',
sum(`score`) as '总成绩'
from `sc` group by `sname`
union
select '总成绩' as '姓名',
sum(case `subject` when '语文' then `score` else 0 end) as '语文',
sum(case `subject` when '数学' then `score` else 0 end) as '数学',
sum(case `subject` when '英语' then `score` else 0 end) as '英语',
sum(case `subject` when '历史' then `score` else 0 end) as '历史',
sum(case `subject` when '政治' then `score` else 0 end) as '政治',
sum(case `subject` when '体育' then `score` else 0 end) as '体育',
sum(`score`) as '总成绩'
from `sc`;
顾名思义,就是判断数据是否存在的!exists的作用为判断一个表中的数据,是否在另外的一张表中能够查询到与之对应的数据
效率要比连接查询和子查询高!
-- 案例1: 查询有员工的部门
select distinct dept.* from dept left join emp on dept.deptno = emp.deptno where empno is not null;
select * from dept where exists(
select 1 from emp where dept.deptno = emp.deptno
);
-- 案例2: 查询没有员工的部门
select distinct dept.* from dept left join emp on dept.deptno = emp.deptno where empno is null;
select * from dept where not exists(
select 1 from emp where dept.deptno = emp.deptno
);
-- 案例3: 查询有部门的员工
select * from emp where exists (
select 1 from dept where dept.deptno = emp.deptno
);
-- 案例4: 查询没有部门的员工信息
select * from emp where not exists (
select 1 from dept where dept.deptno = emp.deptno
);
-- 案例5: 查询有下属的员工信息
select * from emp A where exists (
select 1 from emp B where B.mgr = A.empno
);
-- 案例6: 查询有领导的员工信息
select * from emp A where exists (
select 1 from emp B where A.mgr = B.empno
);
函数 | 描述 |
---|---|
ADDTIME (date2 ,time_interval ) | 将time_interval加到date2 |
CURRENT_DATE ( ) | 当前日期 |
CURRENT_TIME ( ) | 当前时间 |
CURRENT_TIMESTAMP ( ) | 当前时间戳 |
DATE (datetime ) | 返回datetime的日期部分 |
DATE_ADD (date2 , INTERVAL d_value d_type ) | 在date2中加上日期或时间 |
DATE_SUB (date2 , INTERVAL d_value d_type ) | 在date2上减去一个时间 |
DATEDIFF (date1 ,date2 ) | 两个日期差 |
NOW () | 当前时间 |
YEAR|Month|Day(datetime ) | 年月日 |
函数 | 描述 |
---|---|
CHARSET(str) | 返回字串字符集 |
CONCAT (string2 [… ]) | 连接字串 |
INSTR (string ,substring ) | 返回substring在string中出现的位置,没有返0 |
UCASE (string2 ) | 转换成大写 |
LCASE (string2 ) | 转换成小写 |
LEFT (string2 ,length ) | 从string2中的左边起取length个字符 |
LENGTH (string ) | string长度 |
REPLACE (str ,search_str ,replace_str ) | 在str中用replace_str替换search_str |
STRCMP (string1 ,string2 ) | 逐字符比较两字串大小, |
SUBSTRING (str , position [,length ]) | 从str的position开始,取length个字符 |
LTRIM (string2 ) RTRIM (string2 ) trim | 去除前端空格或后端空格 |
函数 | 描述 |
---|---|
ABS (number2 ) | 绝对值 |
BIN (decimal_number ) | 十进制转二进制 |
CEILING (number2 ) | 向上取整 |
CONV(number2,from_base,to_base) | 进制转换 |
FLOOR (number2 ) | 向下取整 |
FORMAT (number,decimal_places ) | 保留小数位数 |
HEX (DecimalNumber ) | 转十六进制 |
LEAST (number , number2 […]) | 求最小值 |
MOD (numerator ,denominator ) | 求余 |
RAND([seed]) | RAND([seed]) |
-- mysql常用的函数
SELECT ADDTIME('2007-12-30 21:50:50', '1:1:1')
SELECT ADDTIME(NOW(),'1:1:1')
SELECT CURRENT_DATE()
SELECT CURRENT_TIME()
SELECT CURRENT_TIMESTAMP()
SELECT DATE(NOW())-- 获取当前时间的日期部分
SELECT DATE_ADD('2016-6-6' ,INTERVAL -1 DAY)
SELECT DATEDIFF('2016-6-8','2016-6-10') -- 前面的时间-后面的时间的差值
SELECT NOW();
SELECT YEAR|MONTH|DAY(DATETIME )-- 不能这样做
SELECT YEAR(NOW())-- 单独获取年
SELECT MONTH(NOW())-- 单独获取月
SELECT DAY(NOW())-- 单独获取日
-- 对字符串的操作
SELECT CHARSET('hello') -- 当前的编码格式
SELECT CONCAT('he','lo') -- 合并字符串
SELECT *,CONCAT(ename,job) FROM emp;
SELECT INSTR('hello','e')-- 当在原字符串中找不到子字符串时,会返回0
SELECT LEFT('hello',2)-- -- 不能这样做
SELECT REPLACE('hello','h','wo')
SELECT STRCMP('hello','heloo') -- 比较两个字符串 前面大返回1 后面大返回-1 相等返回0
SELECT LTRIM(' hello') -- 去除左边的空格
SELECT RTRIM('hello ') -- 去除右边的空格
SELECT MOD(34,0); -- 返回null
在上方我们已经学习了很多的函数了,但是虽然系统给我们提供的函数再怎么多,依然不能满足我们所有的需求。有时候对于一些较为复杂的逻辑,我们就需要用自定义的函数来实现。
其实自定义函数与Java中的类似,都需要指定函数的名字、函数的参数、函数的返回值和函数需要实现的逻辑功能,只是在语法上有所区别。Java的函数用Java代码来书写,MySQL的函数与后面要学习的存储过程类似,都是一组SQL集。
函数可以return值,存储过程不能直接return,但是有输出参数可以输出多个返回值;
函数可以嵌入到sql语句中使用,而存储过程不能;
函数一般用于实现较简单的有针对性的功能(如求绝对值、返回当前时间等),存储过程用于实现复杂的功能(如复杂的业务逻辑功能);
函数的关键字是function, 存储过程是:procedure
-- 如果之前存在这个函数,先将其删除
drop function if exists `my_abs`;
-- 自定义分隔符,这里定义的分隔符是;; 定义好后,只有遇到;;才会结束
delimiter ;;
-- 创建函数,num为形参,returns为返回值
create function `my_abs`(`num` int) returns int
deterministic
deterministic
-- 函数开始
begin
if `num` >= 0 then
return `num`;
else
return -`num`;
end if;
-- 函数结束
end
;;
-- 将分隔符重新定义为;
delimiter ;
-- 如果之前存在这个函数,先将其删除
drop function if exists `get_level_info`;
-- 自定义分隔符
delimiter ;;
-- 创建函数
create function `get_level_info`(`score` int) returns varchar(20) charset utf8
deterministic
begin
if `score` < 0 or `score` > 100 then
return '错误成绩';
elseif `score` < 60 then
return '不及格';
elseif `score` < 80 then
return '良';
else
return '优'
end if;
end
;;
delimiter ;
-- 如果之前存在这个函数,先将其删除
drop function if exists `get_name`;
-- 自定义分隔符
delimiter ;;
-- 创建函数
create function `get_name`(`name` varchar(50) charset utf8) returns varchar(50) charset utf8
deterministic
begin
declare i int default 0;
declare l int default char_length(`name`);
declare tmp varchar(50) default '';
while i <= l do
if mid(`name`, i, 1) regexp '[\\u4e00-\\u9fa5]' then
set tmp = concat(tmp, mid(`name`, i, 1));
end if;
set i = i + 1;
end while;
return tmp;
end
;;
delimiter ;
MySQL在8.0版本之后,支持了窗口函数。
在了解窗口函数之前,我们先看这个需求应该怎么实现: 查询每一个员工的信息,及其部门的平均薪资。
-- 实现方式1: 通过子查询实现
select A.*, B.avg_sal
from emp A join (select deptno,avg(ifnull(sal,0)) avg_sal from emp group by deptno) B on A.deptno = B.deptno
-- 实现方式2: 通过子查询实现
select emp.*, (select avg(ifnull(sal,0)) from emp B where B.deptno = A.deptno)
from emp A;
通过这个查询可以看到,想要进行明细的查询和聚合统计的查询,需要进行两次查询操作,比较麻烦,而且查询效率也不高。那么,能不能简化这样的流程呢?窗口函数就应运而生了!
那么,窗口函数是什么呢?
使用over()函数,为查询到的每一行数据开一个数据窗口。窗口内的数据为本次查询到的所有的数据。
一般是对窗口内的数据进行聚合统计的操作。
-- 需求: 查询每一个员工的基本信息,及所有的员工的数量
select *, count(*) over() from `emp`;
-- 需求: 查询每一个员工的基本信息,及所有员工的工资和
select *, sum(`sal`) over() from `emp`;
-- 需求: 查询每一个员工的基本信息,及所有员工的平均奖金
select *, avg(ifnull(`comm`, 0)) over() from `emp`;
-- 需求: 查询每一个员工的基本信息,及所有员工的平均工龄
select *, avg(round(datediff(now(), `hiredate`)/365, 1)) over() from `emp`;
通过指定的字段,对数据进行分组,此时窗口内的数据为指定分组的数据。
例如: count(*) over(partition by `deptno`), 此时将数据通过deptno字段进行了分组,窗口内的数据是与本行的deptno相同的数据。
-- 需求: 查询每一个员工的基本信息,及其部门的人数
select *, count(*) over(partition by `deptno`) from `emp`;
-- 需求: 查询每一个员工的基本信息,及其部门的最高工资
select *, max(`sal`) over(partition by `deptno`) from `emp`;
-- 需求: 查询每一个员工的基本信息,及其部门的平均薪资
select *, avg(ifnull(`sal`, 0)) over(partition by `deptno`) from `emp`;
-- 需求: 查询每一个员工的基本信息,及部门的平均工龄
select *, avg(round(datediff(now(), `hiredate`)/365, 1)) over(partition by `deptno`) from `emp`;
-- 需求: 查询每一个员工的基本信息,及当前岗位的人数
select *, count(*) over(partition by `job`) from `emp`;
使用order by子句,可以实现窗口内的数据按照指定的条件进行升序或者降序的排列。此时需要注意的是: 窗口内的数据会逐行递增
select *, sum(`sal`) over(partition by `deptno` order by `sal` desc) from `emp`;
有了窗口函数,就可以对数据进行排名次了。常见的排名函数有三种: row_number()、rank()、dense_rank();
score | row_number | rank | dense_rank |
---|---|---|---|
100 | 1 | 1 | 1 |
99 | 2 | 2 | 2 |
99 | 3 | 2 | 2 |
98 | 4 | 4 | 3 |
98 | 5 | 4 | 3 |
97 | 6 | 6 | 4 |
-- 查询每一个员工的基本信息,及员工在自己部门内的工资排名
select *, rank() over(partition by `deptno` order by `sal` desc) from `emp`;
-- 查询每一个员工的基本信息,及员工在所有人中的工资排名
select *, dense_rank() over(order by `sal` desc) from `emp`;
-- 查询每一个员工的基本信息,及员工在同一个工种内的工资排名
select *, rank() over(partition by `job` order by `sal` desc) from `emp`;
-- 查询每一个员工的基本信息,及员工在所有人中的工龄排名
select *, rank() over(order by `hiredate`) from `emp`;
-- 查询每一个员工的基本信息,及员工在当前部门中的工龄排名
select *, rank() over(partition by `deptno` order by `hiredate`) from `emp`;