1. 函数的理解
函数在计算机语言的使用中贯穿始终,函数的作用是什么呢?它可以把我们经常使用的代码封装起来,需要的时候直接调用即可。这样既提高了代码效率,又提高了可维护性。在 SQL 中我们也可以使用函数对检索出来的数据进行函数操作。使用这些函数,可以极大地提高用户对数据库的管理效率。
从函数定义的角度出发,我们可以将函数分成内置函数和自定义函数。在 SQL 语言中,同样也包括了内置函数和自定义函数。内置函数是系统内置的通用函数,而自定义函数是我们根据自己的需要编写的,本章及下一章讲解的是 SQL 的内置函数。
2. 不同DBMS之间函数的差异性
我们在使用 SQL 语言的时候,不是直接和这门语言打交道,而是通过它使用不同的数据库软件,即 DBMS。DBMS 之间的差异性很大,远大于同一个语言不同版本之间的差异。实际上,只有很少的函数是被 DBMS 同时支持的。比如,大多数 DBMS 使用(||)或者(+)来做拼接符,而在 MySQL 中的字符串拼接函数为concat()。大部分 DBMS 会有自己特定的函数,这就意味着采用 SQL 函数的代码可移植性是很差的,因此在使用函数的时候需要特别注意。
3. MySQL函数及分类
MySQL提供了丰富的内置函数,这些函数使得数据的维护与管理更加方便,能够更好地提供数据的分析与统计功能,在一定程度上提高了开发人员进行数据分析与统计的效率。
MySQL提供的内置函数从实现的功能角度可以分为数值函数、字符串函数、日期和时间函数、流程控制函数、加密与解密函数、获取MySQL信息函数、聚合函数等。这里,我将这些丰富的内置函数再分为两类:单行函数、聚合函数(或分组函数)。
两种SQL函数
单行函数
- 操作数据对象
- 接受一个参数返回一个结果
- 只对一行进行变换
- 每一行返回一个结果
- 可以嵌套
- 参数可以是一列或者一个值
4. 数值函数
4.1 基本函数
| 函数 | 用法 |
|---|---|
| ABS(x) | 返回x的绝对值 |
| SIGN(X) | 返回X的符号。正数返回1,负数返回-1,0返回0 |
| PI() | 返回圆周率的值 |
| CEIL(x),CEILING(x) | 返回大于或等于某个值的最小整数 |
| FLOOR(x) | 返回小于或等于某个值的最大整数 |
| LEAST(e1,e2,e3…) | 返回列表中的最小值 |
| GREATEST(e1,e2,e3…) | 返回列表中的最大值 |
| MOD(x,y) | 返回X除以Y后的余数 |
| RAND() | 返回0~1的随机值 |
| RAND(x) | 返回0~1的随机值,其中x的值用作种子值,相同的X值会产生相同的随机数 |
| ROUND(x) | 返回一个对x的值进行四舍五入后,最接近于X的整数 |
| ROUND(x,y) | 返回一个对x的值进行四舍五入后最接近X的值,并保留到小数点后面Y位 |
| TRUNCATE(x,y) | 返回数字x截断为y位小数的结果 |
| SQRT(x) | 返回x的平方根。当X的值为负数时,返回NULL |
常见函数
# ABS(X) 绝对值函数# SIGN() 判断正负函数,正数返回1,负数返回-1,零返回0# PI() 返回π的值# CEILING(X) 求大于等于某个值的最小整数# FLOOR(X) 求小于等于某个值的最大整数# MOD(N,M) 求N除以M的余数SELECT ABS(-123),ABS(32),SIGN(-23),SIGN(43),PI(),CEIL(32.32),CEILING(-43.23),FLOOR(32.32),FLOOR(-43.23),MOD(12,5)FROM DUAL;
随机数函数
相同的随机数种子生成的随机数也一定是一样的
SELECT RAND(),RAND(),RAND(10),RAND(10),RAND(-1),RAND(-1)FROM DUAL;
四舍五入函数
round(x,y)对数据x保留y位有效数字,也可以省略y不写,默认保留到整数位
SELECT ROUND(12.33),ROUND(12.343,2),ROUND(12.324,-1),TRUNCATE(12.66,1),TRUNCATE(12.66,-1)FROM DUAL;
截断函数
TRUNCATE(x,y)对小数点后第y位之后的数据全部置0处理
SELECT TRUNCATE(123.456,0),TRUNCATE(123.456,1),TRUNCATE(123.456,2),TRUNCATE(123.456,-1)FROM DUAL;
4.2 三角函数
4.2.1 角度和弧度互换函数
| 函数 | 用法 |
|---|---|
| RADIANS(x) | 将角度转化为弧度,其中,参数x为角度值 |
| DEGREES(x) | 将弧度转化为角度,其中,参数x为弧度值 |
4.2.2 三角函数
| 函数 | 用法 |
|---|---|
| SIN(x) | 返回x的正弦值,其中,参数x为弧度值 |
| ASIN(x) | 返回x的反正弦值,即获取正弦为x的值。如果x的值不在-1到1之间,则返回NULL |
| COS(x) | 返回x的余弦值,其中,参数x为弧度值 |
| ACOS(x) | 返回x的反余弦值,即获取余弦为x的值。如果x的值不在-1到1之间,则返回NULL |
| TAN(x) | 返回x的正切值,其中,参数x为弧度值 |
| ATAN(x) | 返回x的反正切值,即返回正切值为x的值 |
| ATAN2(m,n) | 返回两个参数的反正切值 |
| COT(x) | 返回x的余切值,其中,X为弧度值 |
4.3 指数和对数
| 函数 | 用法 |
|---|---|
| POW(x,y),POWER(X,Y) | 返回x的y次方 |
| EXP(X) | 返回e的X次方,其中e是一个常数,2.718281828459045 |
| LN(X),LOG(X) | 返回以e为底的X的对数,当X <= 0 时,返回的结果为NULL |
| LOG10(X) | 返回以10为底的X的对数,当X <= 0 时,返回的结果为NULL |
| LOG2(X) | 返回以2为底的X的对数,当X <= 0 时,返回NULL |
SELECT POW(2,3),EXP(2),LN(2.98),LOG10(10),LOG10(-1),LOG2(4),LOG2(-4)FROM DUAL;
4.4 进制转换函数
| 函数 | 用法 |
|---|---|
| BIN(x) | 返回x的二进制编码 |
| HEX(x) | 返回x的十六进制编码 |
| OCT(x) | 返回x的八进制编码 |
| CONV(x,f1,f2) | 返回f1进制数变成f2进制数 |
SELECT BIN(16),HEX(16),OCT(16),CONV(16,10,2)FROM DUAL;

5. 字符串函数
注意:在MySQL中,字符串索引的开始位置为1!!!
| ASCII(S) | 返回字符串S中的第一个字符的ASCII码值 |
|---|---|
| CHAR_LENGTH(s) | 返回字符串s的字符数。作用与CHARACTER_LENGTH(s)相同 |
| LENGTH(s) | 返回字符串s的字节数,和字符集有关 |
| CONCAT(s1,s2,……,sn) | 连接s1,s2,……,sn为一个字符串 |
| CONCAT_WS(x, s1,s2,……,sn) | 同CONCAT(s1,s2,…)函数,但是每个字符串之间要加上x |
| INSERT(str, idx, len, replacestr) | 将字符串str从第idx位置开始,len个字符长的子串替换为字符串replacestr |
| REPLACE(str, a, b) | 用字符串b替换字符串str中所有出现的字符串a |
| UPPER(s) 或 UCASE(s) | 将字符串s的所有字母转成大写字母 |
| LOWER(s) 或LCASE(s) | 将字符串s的所有字母转成小写字母 |
| LEFT(str,n) | 返回字符串str最左边的n个字符 |
| RIGHT(str,n) | 返回字符串str最右边的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(s1 FROM s) | 去掉字符串s开始与结尾的s1 |
| TRIM(LEADING s1 FROM s) | 去掉字符串s开始处的s1 |
| TRIM(TRAILING s1 FROM s) | 去掉字符串s结尾处的s1 |
| REPEAT(str, n) | 返回str重复n次的结果 |
| SPACE(n) | 返回n个空格 |
| STRCMP(s1,s2) | 比较字符串s1,s2的ASCII码值的大小 |
| SUBSTR(s,index,len) | 返回从字符串s的index位置其len个字符,作用与SUBSTRING(s,n,len)、MID(s,n,len)相同 |
| LOCATE(substr,str) | 返回字符串substr在字符串str中首次出现的位置,作用于POSITION(substr IN str)、INSTR(str,substr)相同。未找到,返回0 |
| ELT(m,s1,s2,…,sn) | 返回指定位置的字符串,如果m=1,则返回s1,如果m=2,则返回s2,如果m=n,则返回sn |
| FIELD(s,s1,s2,…,sn) | 返回字符串s在字符串列表中第一次出现的位置 |
| FIND_IN_SET(s1,s2) | 返回字符串s1在字符串s2中出现的位置。其中,字符串s2是一个以逗号分隔的字符串 |
| REVERSE(s) | 返回s反转后的字符串 |
| NULLIF(value1,value2) | 比较两个字符串,如果value1与value2相等,则返回NULL,否则返回value1 |
5.1 字符串长度函数
CHAR_LENGTH()返回的字符串的长度LENGTH()返回的字符串存储占用的字节数
5.2 字符串连接函数
CONCAT(str1,str2,...)连接多个字符串
SELECTCONCAT('hello ','world',',hello ','China') str1,CONCAT_WS(' ','hello','world','hello','China') str2FROMDUAL;

应用举例
SELECTCONCAT(emp.last_name,' work for ',man.last_name) strFROM employees emp,employees manWHERE emp.manager_id = man.employee_id;
5.3 字符串插入函数
insert(str,idx,len,newStr)在索引为idx后的长度为len的字符串替换为newStr
SELECTINSERT('helloworld',2,3,'aaa') strFROMDUAL;

5.4 字符串替换函数
replace(str,old_str,new_str)将str中的old_str全部替换为new_str
SELECTREPLACE('helloworld','world',' China') strFROMDUAL;
5.5 大小写转换函数
UPPER(S)字符串S中所有字母转换成大写字母LOWER(S)字符串S中所有字母转换成小写字母
SELECTUPPER('hello,World,gooD,nIght') upper,LOWER('HELLO,WORLD!') lowerFROMDUAL;
5.6 字符串填充函数
LPAD(str,len,pad)使用字符串pad对字符串str的最左边进行填充,直到str长度为lenRPAD(str,len,pad)使用字符串pad对字符串str的最右边进行填充,直到str长度为len
5.7 去除空格函数
LTRIM(s)去除字符串S左边的所有空格RTRIM(s)去除字符串S右边的所有空格TRIM(s) 去除字符串开始和结尾的所有空格
SELECTTRIM(' hello, world ') str1,LTRIM(' hello, world ') str2,RTRIM(' hello, world ') str3FROMDUAL;
5.8 字符串比较函数
STRCMP()依次比较两个字符串的ASCII码
SELECTSTRCMP('apple','banana') str1,STRCMP('apple','appl') str2,STRCMP('apple','apple2') str3FROMDUAL;
5.9 字符串截取函数
SUBSTR(s,index,len)截取字符串位置index处长度为len的子串
SELECTSUBSTR('hello,world',7,5)FROMDUAL;
5.10 字符串查找函数
LOCATE(subStr,str)查找subStr在str中首次出现的位置,如果未找到,则返回 0
SELECTLOCATE('world','hello,world')FROMDUAL;
5.11 字符串翻转函数
REVERSE(str) 返回str翻转后的字符串
SELECTREVERSE('hello,world')FROMDUAL;

6. 日期和时间函数
6.1 获取日期和时间
| 函数 | 用法 |
|---|---|
| CURDATE() ,CURRENT_DATE() | 返回当前日期,只包含年、月、日 |
| CURTIME() , CURRENT_TIME() | 返回当前时间,只包含时、分、秒 |
| NOW() / SYSDATE() / CURRENT_TIMESTAMP() / LOCALTIME() / LOCALTIMESTAMP() | 返回当前系统日期和时间 |
| UTC_DATE() | 返回UTC(世界标准时间)日期 |
| UTC_TIME() | 返回UTC(世界标准时间)时间 |
6.1.1 当前日期和时间
CURDATE()当前日期,包含年、月、日CURTIME()当前时间,包含时、分、秒
SELECTCURDATE() cur_date,CURTIME() cur_timeFROMDUAL;
6.1.2 当前系统日期和时间
NOW() 返回当前系统日期和时间
6.1.3 世界标准时间和日期
UTC_DATE() UTC日期UTC_TIME() UTC时间
6.2 日期和时间戳的转换
| 函数 | 用法 |
|---|---|
| UNIX_TIMESTAMP() | 以UNIX时间戳的形式返回当前时间。SELECT UNIX_TIMESTAMP() ->1634348884 |
| UNIX_TIMESTAMP(date) | 将时间date以UNIX时间戳的形式返回。 |
| FROM_UNIXTIME(timestamp) | 将UNIX时间戳的时间转换为普通格式的时间 |
SELECTUNIX_TIMESTAMP() `UNIX_TIMESTAMP1`,UNIX_TIMESTAMP('2022-01-08 00:00:00') `UNIX_TIMESTAMP2`,FROM_UNIXTIME(UNIX_TIMESTAMP()) `now`FROMDUAL;
6.3 获取月份、星期、星期数、天数
| 函数 | 用法 |
|---|---|
| YEAR(date) / MONTH(date) / DAY(date) | 返回具体的日期值 |
| HOUR(time) / MINUTE(time) / SECOND(time) | 返回具体的时间值 |
| MONTHNAME(date) | 返回月份:January,… |
| DAYNAME(date) | 返回星期几:MONDAY,TUESDAY…..SUNDAY |
| WEEKDAY(date) | 返回周几,注意,周1是0,周2是1,。。。周日是6 |
| QUARTER(date) | 返回日期对应的季度,范围为1~4 |
| WEEK(date) , WEEKOFYEAR(date) | 返回一年中的第几周 |
| DAYOFYEAR(date) | 返回日期是一年中的第几天 |
| DAYOFMONTH(date) | 返回日期位于所在月份的第几天 |
| DAYOFWEEK(date) | 返回周几,注意:周日是1,周一是2,。。。周六是7 |
SELECT YEAR(CURDATE()),MONTH(CURDATE()),DAY(CURDATE()),HOUR(CURTIME()),MINUTE(NOW()),SECOND(SYSDATE())FROM DUAL;

SELECT MONTHNAME('2021-10-26'),DAYNAME('2021-10-26'),WEEKDAY('2021-10-26'),QUARTER(CURDATE()),WEEK(CURDATE()),DAYOFYEAR(NOW()),DAYOFMONTH(NOW()),DAYOFWEEK(NOW())FROM DUAL;

6.4 时间和秒之间的转换
| 函数 | 用法 |
|---|---|
| TIME_TO_SEC(time) | 将 time 转化为秒并返回结果值。转化的公式为:小时3600+分钟60+秒 |
| SEC_TO_TIME(seconds) | 将 seconds 描述转化为包含小时、分钟和秒的时间 |
TIME_TO_SEC(time)时间转换为对应的秒数SEC_TO_TIME(sec)秒数转换为对应的时间
SELECTTIME_TO_SEC('21:34:00') `second`,SEC_TO_TIME(TIME_TO_SEC('21:34:00')) `time`FROMDUAL;

6.5 日期的格式化与解析
| 函数 | 用法 |
|---|---|
| DATE_FORMAT(date,fmt) | 按照字符串fmt格式化日期date值 |
| TIME_FORMAT(time,fmt) | 按照字符串fmt格式化时间time值 |
| GET_FORMAT(date_type,format_type) | 返回日期字符串的显示格式 |
| STR_TO_DATE(str, fmt) | 按照字符串fmt对str进行解析,解析为一个日期 |
上述非GET_FORMAT函数中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) | %S和%s | 两位数字表示秒(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 | %% | 表示% |
示例:
SELECTDATE_FORMAT(NOW(),'%H:%i:%S') `time`FROMDUAL;

SELECT STR_TO_DATE('09/01/2009','%m/%d/%Y')FROM DUAL;SELECT STR_TO_DATE('20140422154706','%Y%m%d%H%i%s')FROM DUAL;SELECT STR_TO_DATE('2014-04-22 15:47:06','%Y-%m-%d %H:%i:%s')FROM DUAL;

7. 流程控制函数
| 函数 | 用法 |
|---|---|
| IF(value,value1,value2) | 如果value的值为TRUE,返回value1,否则返回value2 |
| IFNULL(value1, value2) | 如果value1不为NULL,返回value1,否则返回value2 |
| CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 …. [ELSE resultn] END | 相当于Java的if…else if…else… |
| CASE expr WHEN 常量值1 THEN 值1 WHEN 常量值1 THEN 值1 …. [ELSE 值n] END | 相当于Java的switch…case… |
7.1 IF
SELECTemp.last_name,emp.salary,IF(emp.salary >= 6000,'高工资','低工资') `details`FROMemployees emp;
7.2 IFNULL
SELECTemp.last_name,emp.department_id,IFNULL(emp.department_id,'没有部门') `datails`FROMemployees emp;

7.3 CASE-WHEN-THEN
SELECTemployee_id,salary,CASEWHEN salary >= 15000 THEN'高薪'WHEN salary >= 10000 THEN'潜力股'WHEN salary >= 8000 THEN'屌丝' ELSE '草根'END '描述'FROMemployees;

7.4 CASE-EXPR-WHEN-THEN
SELECT CASE WHEN 1 < 0 THEN 'yes' WHEN 1 = 0 THEN 'no' ELSE 'unknown' END;

SELECTlast_name,job_id,salary,CASEjob_idWHEN 'IT_PROG' THEN1.10 * salaryWHEN 'ST_CLERK' THEN1.15 * salaryWHEN 'SA_REP' THEN1.20 * salary ELSE salaryEND "REVISED_SALARY"FROMemployees;

练习:查询部门号为 10,20, 30 的员工信息, 若部门号为 10, 则打印其工资的 1.1 倍, 20 号部门, 则打印其工资的 1.2 倍, 30 号部门打印其工资的 1.3 倍数。
SELECTdepartment_id,salary,CASEdepartment_idWHEN 10 THEN1.1 * salaryWHEN 20 THEN1.2 * salaryWHEN 30 THEN1.3 * salary ELSE salaryEND "tips"FROMemployees;

8. 加密解密函数
实际上,现在基本不会在
MySQL中去进行加密操作,一般在前端或者后端,就已经对要存储的信息进行了一次或者多次加密。
| 函数 | 用法 |
|---|---|
| PASSWORD(str) | 返回字符串str的加密版本,41位长的字符串。加密结果不可逆,常用于用户的密码加密 |
| MD5(str) | 返回字符串str的md5加密后的值,也是一种加密方式。若参数为NULL,则会返回NULL |
| SHA(str) | 从原明文密码str计算并返回加密后的密码字符串,当参数为NULL时,返回NULL。SHA加密算法比MD5更加安全。 |
| ENCODE(value,password_seed) | 返回使用password_seed作为加密密码加密value |
| DECODE(value,password_seed) | 返回使用password_seed作为加密密码解密value |
注意:
PASSWORD()、ENCODE()、DECODE()在MySQL8.0以及后续版本中已经被弃用了。
MD5()使用md5算法进行加密SHA()使用sha算法进行加密
SELECTSHA('hello') `sha`,MD5('hello') `md5`FROMDUAL;

9. 信息函数
MySQL中内置了一些可以查询MySQL信息的函数,这些函数主要用于帮助数据库开发或运维人员更好地对数据库进行维护工作。
| 函数 | 用法 |
|---|---|
| VERSION() | 返回当前MySQL的版本号 |
| CONNECTION_ID() | 返回当前MySQL服务器的连接数 |
| DATABASE(),SCHEMA() | 返回MySQL命令行当前所在的数据库 |
| USER(),CURRENT_USER()、SYSTEM_USER(),SESSION_USER() | 返回当前连接MySQL的用户名,返回结果格式为“主机名@用户名” |
| CHARSET(value) | 返回字符串value自变量的字符集 |
| COLLATION(value) | 返回字符串value的比较规则 |
SELECTVERSION() `version`,CONNECTION_ID() `connection_sum`,DATABASE() `database`,USER() `user1`,CURRENT_USER() `user2`,CHARSET('你好') `str1`,CHARSET('hello,world') `str2`,COLLATION('good day')FROMDUAL;

10. 单行函数练习
# 1.显示系统时间(注:日期+时间)SELECTNOW()FROMDUAL;# 2.查询员工号,姓名,工资,以及工资提高百分之20%后的结果(new salary)SELECTemployee_id,last_name,salary,salary*1.2 `new salary`FROMemployees;# 3.将员工的姓名按首字母排序,并写出姓名的长度(length)SELECTlast_name,LENGTH(last_name)FROMemployeesORDER BY last_name;# 4.查询员工id,last_name,salary,并作为一个列输出,别名为OUT_PUTSELECTCONCAT(employee_id, ' ' ,last_name, ' ' ,salary) `OUT_PUT`FROMemployees;# 5.查询公司各员工工作的年数、工作的天数,并按工作年数的降序排序SELECTDATEDIFF(CURDATE(),hire_date) / 365 `years`,DATEDIFF(CURDATE(),hire_date) `days`FROMemployeesORDER BY`years` DESC;# 6.查询员工姓名,hire_date , department_id# 满足以下条件:雇用时间在1997年之后,department_id为80 或 90 或110, commission_pct不为空SELECTlast_name,hire_date,department_idFROMemployeesWHEREdepartment_id IN (80,90,110)AND commission_pct IS NOT NULL# AND hire_date > '1997-01-01';AND DATE_FORMAT(hire_date,'%Y-%m-%d') >= '1997-01-01';# AND DATE_FORMAT(hire_date,'%Y') >= '1997';# AND STR_TO_DATE('1997-01-01','%Y-%m-%d') <= hire_date;# AND YEAR(hire_date) >= '1997';# 7.查询公司中入职超过10000天的员工姓名、入职时间SELECTlast_name,hire_dateFROMemployeesWHEREDATEDIFF(CURDATE(),hire_date) >= 10000;
