MySQL基础(中)
七、单行函数
从函数定义的角度来看,函数分为内置函数和自定义函数。
第七章第八章所给出的函数全部都是Mysql中的内置函数,并非是自定义函数。
1.不同的DBMS的差异
DBMS之间的差异性很大,远大于一个语言不同版本之间的差异。实际上,只有很少的函数是被 DBMS同时支持的。这就意味着采用SQL函数的代码的可移植性是很差的,因此在使用函数的时候要注意。
2.MySQL的内置函数及其分类
MySQL提供的内置函数从实现功能上分为:数值函数,字符串函数,日期和时间函数,流程控制函数,加密与解密函数,获取mysql的信息函数,聚合函数等。这里我们将这些丰富的内置函数再分为两类:单行函数和聚合函数(或分组函数、多行函数)。
单行函数:
-
操作数据对象
-
接受参数返回一个结果
-
只进行一对一的变换
-
每行返回一个结果
-
可以嵌套
-
参数可以数一个值或一列
多行函数:相当于是给一组数据求最大值、求和等函数。
3.数值函数
常见函数:
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 |
#例子: SELECT CONCAT('hello','world') FROM DUAL; #数值函数 SELECT ABS(-13), ABS(13), SIGN(-36), SIGN(36), SIGN(0), PI(), CEIL(32.32), CEILING(-43.23), FLOOR(32.32), FLOOR(-43.23), MOD(12,5) FROM DUAL; #result:13, 13, -1, 1, 0, 3.141593, 33, -43, 32, -44, 2。 /* CEIL(X) 天花板函数 浮点型数向上取整 CEILING(X) 负数向上取整 取得数肯定是大于等于x的 FLOOR(X) 地板函数 向下取整 取得数肯定是小于等于x的 MOD(12, 5) == 12 % 5 */ #最大最小值 SELECT LEAST(3, 5, 1), GREATEST(3, 5, 1) FROM DUAL; #result:1, 5 /* LEAST(value1,value2,...) 取最小值 GREATEST(value1,value2,...) 取最大值 */ #取随机数(伪随机数) SELECT RAND(), RAND(), RAND(10), RAND(10), RAND(-1), RAND(-1) FROM DUAL; /* RAND()不带参数 每次运行得到的随机数都不一样,要想整数可以乘100四舍五入 RAND(X)带参数 保证X相同的函数 一起运行得到的随机数一样 */ #四舍五入 截断操作 SELECT ROUND(123.465), ROUND(123.556), ROUND(123.456, 0), ROUND(123.456, 1), ROUND(123.456, -1), ROUND(125.466, -1) FROM DUAL; /* ROUND(X):四舍五入掉小数点后的数 ROUND(X, Y):Y确定四舍五入小数点前或后几位数,当Y为负数时,会看小数点前的数, 如果小于5则舍为0,大于5则进1后变0 */ SELECT TRUNCATE(123.456, 2), TRUNCATE(129, -1) FROM DUAL; /* TRUNCATE(X, D):返回数字x截断为y位小数的结果 小数点后的直接舍掉,小数点前的补0 */ #单行函数可以嵌套 SELECT TRUNCATE(ROUND(123.466, 2), 0) FROM DUAL; #开方 SELECT SQRT(4) FROM DUAL;
角度与弧度的换算
RADIANS(x) | 将角度转化为弧度,其中,参数x为角度值 |
---|---|
DEGREES(x) | 将弧度转化为角度,其中,参数x为弧度值 |
#角度与弧度的换算 SELECT RADIANS(30),RADIANS(60),RADIANS(90), DEGREES(2*PI()),DEGREES(RADIANS(90)) FROM DUAL;
三角函数
函数 | 功能 |
---|---|
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为弧度值 |
举例:
ATAN2(M,N)函数返回两个参数的反正切值。 与ATAN(X)函数相比,ATAN2(M,N)需要两个参数,例如有两个
点point(x1,y1)和point(x2,y2),使用ATAN(X)函数计算反正切值为ATAN((y2-y1)/(x2-x1)),使用ATAN2(M,N)计
算反正切值则为ATAN2(y2-y1,x2-x1)。由使用方式可以看出,当x2-x1等于0时,ATAN(X)函数会报错,而
ATAN2(M,N)函数则仍然可以计算。
#三角函数 SELECT SIN(RADIANS(30)), DEGREES(ASIN(1)), TAN(RADIANS(45)), DEGREES(ATAN(1)) FROM DUAL;
指数和对数
函数 | 功能 |
---|---|
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, 5), POWER(2, 4), EXP(2)#e的二次方 FROM DUAL; SELECT LN(EXP(2)), LOG(EXP(2)), LOG10(10), LOG2(2) FROM DUAL;#LN(X) == LOG(X) 都是以e为底的对数
进制转换
函数 | 功能 |
---|---|
BIN(x) | 返回x的二进制编码 |
HEX(x) | 返回x的十六进制编码 |
OCT(x) | 返回x的八进制编码 |
CONV(x,f1,f2) | 返回f1进制数变成f2进制数 |
#进制转换 SELECT BIN(10), HEX(10), OCT(10), CONV(10,2,10) FROM DUAL; /* CONV(N,from_base,to_base):上面的表示 10 为 二进制数 要转换为 十进制数 */
4.字符串函数
函数 | 功能 |
---|---|
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, s3, ....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(substrIN 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 |
#字符串函数 #ASCII(str) SELECT ASCII('abc') FROM DUAL;#只返回字符串中第一个字母的ASCII码值 #统计字符字节个数 SELECT CHAR_LENGTH('hello'), CHAR_LENGTH('我们'), LENGTH('hello'), LENGTH('我们') FROM; #result:5 2 5 6 /* CHAR_LENGTH(str):返回的是字符串中字符的个数 LENGTH(str):返回的是字符串中所占字节的个数,字母占一个字节,汉字占三个字节(utf_8中) */ #连接字符串 SELECT CONCAT(e.last_name, ' worked for ', mar.last_name) AS "details" FROM employees e JOIN employees mar ON e.manager_id = mar.employee_id; SELECT CONCAT_WS('-','hello','world','beijing') FROM DUAL;#CONCAT_WS(separator,str1,str2,...):以第一个字符串连接后面的字符串 #result:hello-world-beijing SELECT INSERT('helloworld', 2, 3, 'aaaaa') FROM DUAL;#字符串的索引是从1开始的,意思是从第二个开始往后数三个字符替换成五个a #result:haaaoworld SELECT REPLACE('hello','ll','mmm') FROM DUAL;#将hello中的ll替换成mmm 如果没有ll 那就不做任何操作 #result:hemmmo SELECT UPPER('hello'), LOWER('Hello') FROM DUAL;#全部变成大写或者小写 #result:HELLO hello SELECT LEFT('hello', 2), RIGHT('hello', 3) FROM DUAL;#返回从左数两个字符 和 从右数3个字符 #result: he llo SELECT last_name, employee_id, LPAD(salary,10,'*') FROM employees;#右对齐操作,salary不够是个字符,用*在左边补全 SELECT last_name, employee_id, RPAD(salary,10,' ') FROM employees;#左对齐操作,salary不够是个字符,用空格在右边补全 SELECT CONCAT('---',TRIM(' he l lo '),'***') FROM DUAL;#去除字符串的首尾空格 #result:---he l lo*** SELECT TRIM('o' FROM 'oohello'), TRIM(LEADING 'o' FROM 'oohello'), TRIM(TRAILING 'o' FROM 'oohello') FROM DUAL;#去除字符串首尾的 o 和 去除字符串首的 o 和 去除字符串尾的 o #result:hell hello oohell SELECT REPEAT('hello',4), LENGTH(SPACE(5)), STRCMP('abc','abd') FROM DUAL;#重复四次 hello SPACE()返回5个空格 比较两个字符串的大小 #result:hellohellohellohello 5 -1 SELECT SUBSTR('hello', 2, 2) FROM DUAL;#从第二个字符开始(包括第二个字符)往后选取两个字符串 子字符串输出 #result:el SELECT LOCATE('lo', 'hello') FROM DUAL;#查找lo在hello中首次出现的位置, 如果没找到那么返回0 #result:4 SELECT ELT(2,'a','b','c','d') FROM DUAL;#返回后面出现的第二个字符串 #result:b SELECT FIELD('mm', 'gg', 'jj', 'dd', 'mm', 'mm') FROM DUAL;#返回第一个字符串在后面第一次出现的位置 #result:4 SELECT FIND_IN_SET('mm','gg,jj,dd,mm,mm') FROM DUAL;#后面的字符串以一整个集合体现 #result:4 SELECT REVERSE('abc') FROM DUAL;#翻转字符串 #result:cba SELECT employee_id, NULLIF(LENGTH(first_name), LENGTH(last_name)) AS "compare" FROM employees;#如果姓的长度和名的长度相等那么返回NULL 否则返回姓
注意:字符串的索引从1开始
5.日期和时间函数
获取日期、时间
函数 | 功能 |
---|---|
CURDATE() ,CURRENT_DATE() | 返回当前日期,只包含年、月、日 |
CURTIME() , CURRENT_TIME() | 返回当前时间,只包含时、分、秒 |
NOW() / SYSDATE() / CURRENT_TIMESTAMP() / LOCALTIME() /LOCALTIMESTAMP() | 返回当前系统日期和时间 |
UTC_DATE() | 返回UTC(世界标准时间)日期 |
UTC_TIME() | 返回UTC(世界标准时间)时间 |
日期与时间戳的转换
函数 | 用法 |
---|---|
UNIX_TIMESTAMP() | 以UNIX时间戳的形式返回当前时间。SELECT UNIX_TIMESTAMP() - >1634348884 |
UNIX_TIMESTAMP(date) | 将时间date以UNIX时间戳的形式返回。 |
FROM_UNIXTIME(timestamp) | 将UNIX时间戳的时间转换为普通格式的时间 |
获取月份、星期、星期数、天数等函数
其他时间函数在以下代码中体现:
#3.获取月份、星期、星期数、天数等函数 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;、 /* WEEKDAY(date)周一是0周二是1 QUARTER(date)显示的第几季度 WEEK(date)显示的这一年的第几周 DAYOFYEAR(date)显示的这一年的第多少天 DAYOFMONTH(date)显示的这个月的第多少天 DAYOFWEEK(date)显示的是这个星期的第多少天 */
日期的操作函数
函数 | 用法 |
---|---|
EXTRACT(type FROM date) | 返回指定日期中特定的部分,type指定返回的值 |
EXTRACT(type FROM date)函数中type取值的含义:
#4.日期的操作函数 SELECT EXTRACT(MINUTE FROM NOW()),EXTRACT( WEEK FROM NOW()), EXTRACT( QUARTER FROM NOW()),EXTRACT( MINUTE_SECOND FROM NOW()) FROM DUAL;
时间和秒钟转换的函数
函数 | 用法 |
---|---|
TIME_TO_SEC(time) | 将 time 转化为秒并返回结果值。转化的公式为: 小时*3600+分钟 *60+秒 |
SEC_TO_TIME(seconds) | 将 seconds 描述转化为包含小时、分钟和秒的时间 |
#5.时间和秒钟的转换函数 SELECT TIME_TO_SEC(NOW()), SEC_TO_TIME(67875) FROM DUAL;# 小时*3600+分钟 *60+秒,只转换小时往后的数据
计算日期和时间的函数
函数 | 用法 |
---|---|
DATE_ADD(datetime, INTERVAL expr type), ADDDATE(date,INTERVAL expr type) | 返回与给定日期时间相差INTERVAL时间段的日期时间 |
DATE_SUB(date,INTERVAL expr type),SUBDATE(date,INTERVAL expr type) | 返回与date相差INTERVAL时间间隔的日期 |
上述函数中的type取值:
#6.计算日期和时间的函数 SELECT NOW(), DATE_ADD(NOW(),INTERVAL 1 YEAR), #加1年 DATE_ADD(NOW(), INTERVAL -1 YEAR),#减1年 DATE_SUB(NOW(),INTERVAL 1 YEAR),#减1年 DATE_ADD(NOW(),INTERVAL '1_1' YEAR_MONTH)#加1年1个月,需要单引号 FROM DUAL;
SELECT NOW(), DATE_ADD(NOW(),INTERVAL 1 YEAR), #加1年 DATE_ADD(NOW(), INTERVAL -1 YEAR),#减1年 DATE_SUB(NOW(),INTERVAL 1 YEAR),#减1年 DATE_ADD(NOW(),INTERVAL '1_1' YEAR_MONTH)#加1年1个月,需要单引号 FROM DUAL; SELECT ADDTIME(NOW(),20),SUBTIME(NOW(),30),SUBTIME(NOW(),'1:1:3'),DATEDIFF(NOW(),'2021-10- 01'), TIMEDIFF(NOW(),'2021-10-25 22:10:10'),FROM_DAYS(366),TO_DAYS('0000-12-25'), LAST_DAY(NOW()),MAKEDATE(YEAR(NOW()),12),MAKETIME(10,21,23),PERIOD_ADD(20200101010101, 10) FROM DUAL;
举例:查询 7 天内的新增用户数有多少?
SELECT COUNT(*) as num FROM new_user WHERE TO_DAYS(NOW())-TO_DAYS(regist_time)<=7
日期的格式化和解析
这里的格式化和解析是显示的,以前我们接触过隐式的
函数 | 功能 |
---|---|
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参数常用的格式符:
GET_FORMAT函数中date_type和format_type参数取值如下:
#格式化:日期 ——> 字符串 #解析: 字符串--> 日期 /* 这里谈论的是日期的显示的格式化和解析 之前我们接触过日期的隐式的格式化和解析 */ SELECT * FROM employees WHERE hire_date = '1993-01-13';#如果字符串和日期能够匹配,那么就会隐式的转换为日期类型 #显示的格式化: #日期 SELECT DATE_FORMAT(NOW(),'%Y-%M-%D'), DATE_FORMAT(NOW(),'%y-%m-%d') FROM DUAL; #时间 SELECT TIME_FORMAT(NOW(),'%k-%i-%s') FROM DUAL; #显示的解析 SELECT STR_TO_DATE('2021-October-25th','%Y-%M-%D ') FROM DUAL;#格式化的逆过程 SELECT GET_FORMAT(DATE(), 'USA') FROM DUAL;#获取表达年月日的形式 SELECT GET_FORMAT(TIME, 'USA') FROM DUAL;#获取表达时间的形式 SELECT DATE_FORMAT(NOW(),GET_FORMAT(DATE, 'USA')) FROM DUAL;#配合使用 方便使用
6.流程控制函数
流程处理函数可以根据不同的条件, 执行不同的处理流程,可以在SQL语句中实现不同的条件选择。MySQL中的流程处理函数主要包括IF( )、IFNULL()和CASE()函数。
函数 | 用法 |
---|---|
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... |
流程控制包括:顺序结构、分支结构和循环结构。
在sql中顺序结构就是平常写的语句,分支结构是上面四个函数,那么循环结构其实已经在程序执行的时候体现了不需要我们再去定义。在后面的定义函数的时候我们才会用到循环。
#流程控制函数 #IF(VALUE, VALUE1, VALUE2) SELECT last_name, salary, IF(salary >= 6000, '高工资', '低工资') FROM employees; SELECT last_name, commission_pct, IF(commission_pct IS NOT NULL, commission_pct, 0) AS "details" FROM employees; #IFNULL(expr1,expr2):可以看作为上面的函数的特殊情况 SELECT last_name, commission_pct, IFNULL(commission_pct, 0) AS "details" FROM employees; #CASE WHEN...THEN...WHEN...THEN...ELSE...END SELECT last_name, salary, CASE WHEN salary >= 15000 THEN '高薪' WHEN salary >= 10000 THEN '潜力股' WHEN salary >= 8000 THEN '屌丝' ELSE '草根' END AS "details" FROM employees;#相当于 if else的结构 #CASE...WHEN ...THEN...WHEN...THEN...ELSE...END /* 查询部门号为10,20,30的员工信息, 若部门号为10,则打印其工资的1.1倍, 20号部门,则打印其工资的1.2倍, 30号部门,则打印其工资的1.3倍, 其他部门,则打印其工资的1.4倍 */ SELECT last_name, employee_id, department_id, salary, CASE department_id WHEN 10 THEN salary * 1.1 WHEN 20 THEN salary * 1.2 WHEN 30 THEN salary * 1.3 ELSE salary * 1.4 END AS "details" FROM employees;#相当于switch case 的结构 /* 查询部门号为10,20,30的员工信息, 若部门号为10,则打印其工资的1.1倍, 20号部门,则打印其工资的1.2倍, 30号部门,则打印其工资的1.3倍, */ SELECT last_name, employee_id, department_id, salary, CASE department_id WHEN 10 THEN salary * 1.1 WHEN 20 THEN salary * 1.2 WHEN 30 THEN salary * 1.3 END AS "details" FROM employees#相当于switch case 的结构 WHERE department_id IN (10, 20, 30);#不会有else的操作了那么就去掉else
7.加密与解密函数
加密与解密函数主要用于对数据库中的数据进行加密和解密处理,以防止数据被他人窃取。这些函数在保证数据库安全时非常有用。
一般我们用户在输入密码的时候,在页面前端已经完成了加密操作然后传入数据库,如果在以明文的方式传入数据库的话,在传输过程中就可能被窃取。所以这里的加密解密方式只是作为一个补充。
函数 | 用法 |
---|---|
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 |
可以看到,ENCODE(value,password_seed)函数与DECODE(value,password_seed)函数互为反函数。
#加密与解密的函数 SELECT PASSWORD('mysql') FROM DUAL;#在MySQL8.0中弃用了 SELECT MD5('mysql'), SHA('mysql') FROM DUAL;#不可逆的, 不能用别的函数还原回去 #ENCODE(str,pass_str), DECODE(crypt_str,pass_str)在MySQL8.0中也弃用了 SELECT ENCODE('atguigu', 'mysql'), DECODE(ENCODE('atguigu', 'mysql'),'mysql') FROM DUAL;#不能直接复制粘贴暗文
8.MySQL信息函数
MySQL中内置了一些可以查询MySQL信息的函数,这些函数主要用于帮助数据库开发人员或运维人员更好的对数据库进行维护工作。
函数 | 用法 |
---|---|
VERSION() | 返回当前MySQL的版本号 |
CONNECTION_ID() | 返回当前MySQL服务器的连接数 |
DATABASE(),SCHEMA() | 返回MySQL命令行当前所在的数据库 |
USER(),CURRENT_USER()、SYSTEM_USER(),SESSION_USER() | 返回当前连接MySQL的用户名,返回结果格式为“主机名@用户名” |
CHARSET(value) | 返回字符串value自变量的字符集 |
COLLATION(value) | 返回字符串value的比较规则 |
#MySQL的信息函数 SELECT VERSION(), CONNECTION_ID(), DATABASE(), SCHEMA(), USER(), CHARSET('侯静川'), COLLATION('侯静川') FROM DUAL;
9.其他函数
MySQL中有些函数无法对其进行具体的分类,但是这些函数在MySQL的开发和运维过程中也是不容忽视
的。
函数 | 用法 |
---|---|
FORMAT(value,n) | 返回对数字value进行格式化后的结果数据。n表示 四舍五入 后保留到小数点后n位 |
CONV(value,from,to) | 将value的值进行不同进制之间的转换 |
INET_ATON(ipvalue) | 将以点分隔的IP地址转化为一个数字 |
INET_NTOA(value) | 将数字形式的IP地址转化为以点分隔的IP地址 |
BENCHMARK(n,expr) | 将表达式expr重复执行n次。用于测试MySQL处理expr表达式所耗费的时间 |
CONVERT(value USINGchar_code) | 将value所使用的字符编码修改为char_code |
#其他函数 SELECT FORMAT(123.456, 1) FROM DUAL;#和四舍五入不同的就是当n小于0时,保留整数部分 SELECT CONV(16, 10, 2), CONV(888, 10, 16), CONV(NULL, 10, 2) FROM DUAL;#进制转换 将第一个数从 第二个数的进制转换为第三个数的进制 SELECT INET_ATON('198.168.1.100'), INET_NTOA(3332899172) FROM DUAL;#ip地址的转换,把以 . 分隔的ip地址转换为整数 和 将整数还原回去 相当于加密 SELECT BENCHMARK(1000000,2 * 3) FROM DUAL;#用于测试表达式的执行效率 SELECT CHARSET('atguigu'), CHARSET(CONVERT('atguigu' USING 'utf8mb4')) FROM DUAL;#可以实现字符集的转换
章节练习:
#07单行函数章节练习 #第一题:显示系统时间(日期 + 时间) SELECT SYSDATE(), NOW() FROM DUAL; #第二题:查询员工号、姓名、工资以及工资提高20%后的结果(new salary) SELECT employee_id, last_name, salary, salary * (1 + 0.2) AS "new salary" FROM employees; #第三题:将员工的姓名按首字母排序,并写出姓名的长度 SELECT last_name, LENGTH(last_name) AS "length" FROM employees ORDER BY last_name ASC #第四题:查询员工id, last_name, salary, 并作为一个列输出, 别名为OUT_PUT SELECT CONCAT(employee_id ,',', last_name , ',', salary) AS "OUT_PUT" FROM employees; #第五题:查询公司各员工工作的年数,工作的天数,并按工作年数的降序排列 SELECT employee_id, last_name, DATEDIFF(NOW(), hire_date)/365 AS "worked_years", DATEDIFF(NOW(), hire_date) AS "worked_days" FROM employees ORDER BY worked_years DESC; #第六题:查询员工姓名,hire_date, department_id, 满足下列条件:雇佣时间在1997年之后,department_id #为80 或 90 或 110, commission_pct 不为空 SELECT last_name, hire_date, department_id FROM employees WHERE #EXTRACT(YEAR FROM hire_date) >= 1997#隐式转换 #DATE_FORMAT(hire_date,'%Y') >= '1997'#显示格式化转换 日期-->字符串 hire_date >= STR_TO_DATE('1997-01-01','%Y-%m-%d')#显示解析转换 字符串-->日期 AND department_id IN(80,90,110) AND commission_pct IS NOT NULL; #第七题:查询公司中入职超过10000天的员工姓名、入职时间 SELECT last_name, hire_date FROM employees WHERE DATEDIFF(NOW(),hire_date) > 10000; #第八题:做一个查询,产生下面的结果 #<last_name> earns <salary> monthly but wants <salary * 3> SELECT CONCAT(last_name,' earns ',TRUNCATE(salary, 0), ' monthly but wants ', TRUNCATE(salary * 3, 0)) AS "details" FROM employees; #第九题:使用case - when按照下面的条件: /* job grade AD_PRES A ST_MAN B IT_PROG C SA_REP D ST_CLERK E 产生下面的结果: */ SELECT last_name, job_id, CASE job_id WHEN 'AD_PRES' THEN 'A' WHEN 'ST_MAN' THEN 'B' WHEN 'IT_PROG' THEN 'C' WHEN 'SA_REP' THEN 'D' WHEN 'ST_CLERK'THEN 'E' END AS "Grade" FROM employees; SELECT last_name, job_id, CASE job_id WHEN 'AD_PRES' THEN 'A' WHEN 'ST_MAN' THEN 'B' WHEN 'IT_PROG' THEN 'C' WHEN 'SA_REP' THEN 'D' WHEN 'ST_CLERK'THEN 'E' ELSE 'undefined' END AS "Grade" FROM employees;
八、聚合函数
聚合函数是对一组数据进行汇总的函数,输入的是一组数据的集合,输出的是单个值。
1.聚合函数介绍
聚合函数作用于一组数据,并对一组数据返回一个值。
2.常见的聚合函数
聚合函数类型
AVG()
SUM()
MAX()
MIN()
COUNT()
#08常见的聚合函数 #AVG\SUM:只适用于数值类型的字段 在计算的时候会自动过滤空值 SELECT AVG(salary), SUM(salary), AVG(salary) * 107 FROM employees; SELECT SUM(last_name), AVG(last_name), SUM(hire_date)#没有意义,只针对数值型的 FROM employees;#在MySQL中不会报错但是结果是0,在Oracle中会报错 #MAX\MIN:使用于数值类型、字符串类型、日期时间类型等字段 SELECT MAX(salary), MIN(salary) FROM employees; SELECT MAX(last_name), MIN(last_name), MAX(hire_date), MIN(hire_date) FROM employees;#虽然字符串不能相加等运算但是可以比较 #COUNT /* 作用:计算指定字段在查询结果中出现的个数(不包含NULL) */ SELECT COUNT(employee_id), COUNT(salary), COUNT(2 * salary), COUNT(1), COUNT(commission_pct) FROM employees;#COUNT检测的是每条记录有没有括号里所给出的字段,不为NULL那么就加一 SELECT * FROM employees; #如果计算表中有多少条记录: #方式1:COUNT(*) #方式2:COUNT(常数) #方式3:COUNT(具体字段):不一定对,都不为NULL才可以 SELECT COUNT(commission_pct) FROM employees;#计算指定字段出现的个数的前提是不能有NULL #计算平均值:AVG = SUM / COUNT SELECT AVG(salary), SUM(salary)/COUNT(salary) FROM employees; SELECT AVG(commission_pct), SUM(commission_pct)/COUNT(commission_pct), SUM(commission_pct) / 107 FROM employees; #需求:计算公司员工的平均奖金率 SELECT AVG(commission_pct) FROM employees;#error! SELECT SUM(commission_pct) / COUNT(IFNULL(commission_pct, 0)) FROM employees; #或 SELECT AVG(IFNULL(commission_pct, 0)) FROM employees; #如果需要统计表中的记录数,使用COUNT(*)、COUNT(1)、COUNT(具体字段)哪个效率更高? #如果使用的是 MyISAM 存储引擎,那么三个方式效率相同,都是O(1) #如果使用的是 Innodb 存储引擎,则三者效率:COUNT(*) = COUNT(1) > COUNT(字段)
3.GROUP BY 的使用
分组语句
需求:求公司各个部门的平均工资
#GROUP BY 的使用 #需求:查询各个部门的平均工资 SELECT department_id, ROUND(AVG(salary)), SUM(salary) FROM employees GROUP BY department_id; #使用多个列分组: SELECT department_id, job_id, AVG(salary) FROM employees GROUP BY department_id, job_id; SELECT department_id, job_id, AVG(salary) FROM employees GROUP BY job_id, department_id; #上面两个效果一样,只不过是先后顺序不一样,先按job_id分好以后再按department_id分 # 和 先按department_id 分好后再按job_id 分是一样的结果 只不过呈现的先后顺序不一样而已 #例子: SELECT department_id, job_id, AVG(salary) FROM employees GROUP BY department_id;#这样的话记录数和job_id的个数不匹配怎么办? #这是错误的没有显示出所有的工种 #结论1:SELECT中非组函数出现的字段,必须声明在GROUP BY中,反之可行 #结论2:GROUP BY声明在FROM后面,在WHERE后面,在ORDER BY前面,在LIMIT前面 #结论3:MySQL中GROUP BY中使用WITH ROLLUP SELECT department_id, ROUND(AVG(salary), 0) FROM employees GROUP BY department_id WITH ROLLUP;#会在最后将所有的部门平均起来算一个值 #需求:查询所有部门的平均薪资(升序排列) SELECT department_id, ROUND(AVG(salary), 0) AS "avg_sal" FROM employees GROUP BY department_id ORDER BY avg_sal ASC; #说明:当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。 SELECT department_id, ROUND(AVG(salary), 0) AS "avg_sal" FROM employees GROUP BY department_id WITH ROLLUP ORDER BY avg_sal ASC;#error!
4.HAVING的使用
作用:用来过滤数据的
过滤分组:HAVING子句
-
行已经被分组。
-
使用了聚合函数。
-
满足HAVING 子句中条件的分组将被显示。
-
HAVING 不能单独使用,必须要跟 GROUP BY 一起使用。
需求:公司所有部门的最高工资比10000高的部门有哪些?
#需求:公司所有部门的最高工资比10000高的部门有哪些? #错误的写法: SELECT department_id, MAX(salary) FROM employees WHERE MAX(salary) > 10000 GROUP BY department_id; #要求1:使用HAVING 如果过滤条件中使用了聚合函数, 则必须使用HAVING来替换WHERE 否则报错。 #要求2:如果出现HAVING 那么HAVING要放在GROUP BY的后面 #正确的写法: SELECT department_id, MAX(salary) FROM employees GROUP BY department_id HAVING MAX(salary) > 10000;#MAX(salary) 代表的是每一个组的最高工资 #要求3:开发中,我们使用HAVING的前提是SQL中使用了GROUP BY #需求:公司中部门id为10、20、30、40的部门中最高工资比10000高的部门有哪些? SELECT department_id, MAX(salary) FROM employees GROUP BY department_id HAVING MAX(salary) > 10000 && department_id IN (10, 20, 30, 40); #或 SELECT department_id, MAX(salary) FROM employees WHERE department_id IN (10, 20, 30, 40) GROUP BY department_id HAVING MAX(salary) > 10000; #上述两种方式哪个好? #推荐使用第二个,执行效率高 #结论:当过滤条件中有聚合函数时,则此过滤条件必须声明在HAVING中 # 当过滤条件中没有聚合函数时,则此过滤条件建议声明在WHERE中 /* WHERE与HAVING的对比: 1.从适用范围上 HAVING的适用范围更广 2.如果过滤条件中没有聚合函数:这种情况下,WHERE的执行效率要高于HAVING */
WHERE与HAVING的对比: 1.从适用范围上 HAVING的适用范围更广 2.如果过滤条件中没有聚合函数:这种情况下,WHERE的执行效率要高于HAVING
优点 | 缺点 | |
---|---|---|
WHERE | 先筛选数据再关联,执行效率高 | 不能使用分组中的计算函数进行筛选 |
HAVING | 可以使用分组中的计算函数 | 在最后的结果集中进行筛选,执行效率低 |
5.SQL底层执行原理
SELECT 是先执行 FROM 这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:
-
首先先通过 CROSS JOIN 求笛卡尔积,相当于得到虚拟表 vt(virtual table)1-1;
-
通过 ON 进行筛选,在虚拟表 vt1-1 的基础上进行筛选,得到虚拟表 vt1-2;
-
添加外部行。如果我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟
表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3。
当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得
到是我们的原始数据。
当我们拿到了查询数据表的原始数据,也就是最终的虚拟表 vt1 ,就可以在此基础上再进行 WHERE 阶
段 。在这个阶段中,会根据 vt1 表的结果进行筛选过滤,得到虚拟表 vt2 。
然后进入第三步和第四步,也就是 GROUP 和 HAVING 阶段 。在这个阶段中,实际上是在虚拟表 vt2 的
基础上进行分组和分组过滤,得到中间的虚拟表 vt3 和 vt4 。
当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 和 DISTINCT
阶段 。
首先在 SELECT 阶段会提取想要的字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表
vt5-1 和 vt5-2 。
当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY 阶段 ,得到
虚拟表 vt6 。
最后在 vt6 的基础上,取出指定行的记录,也就是 LIMIT 阶段 ,得到最终的结果,对应的是虚拟表
vt7 。
当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。
同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的
关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。
#SQL底层执行原理 #SQL92语法: /* SELECT ...,...,...(可能存在聚合函数) FROM ...,...,... WHERE 多表连接条件 AND 过滤条件(不能有聚合函数) GROUP BY...,... HAVING...(包含聚合函数的过滤条件) ORDER BY...(ASC/DESC) LIMIT ...,... */ #SQL99语法: /* SELECT ...,...,...(可能存在聚合函数) FROM... (LEFT\RIGHT)JOIN ... ON 多表的连接条件 (LEFT\RIGHT)JOIN... ON ... WHERE 过滤条件(不能有聚合函数) GROUP BY...,... HAVING...(包含聚合函数的过滤条件) ORDER BY...(ASC/DESC) LIMIT ...,... */ #SQL语句的执行过程: #FROM...,...--> ON -> (LEFT\RIGHT) -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT #FROM -> WHERE -> GROUP BY -> HAVING -> SELECT 的字段 -> DISTINCT -> ORDER BY -> LIMIT
章节练习
#08聚合函数章节练习 #第一题:WHERE子句可否使用组函数进行过滤? #不能。 #第二题:查询公司员工工资的最大值,最小值,平均值,总和 SELECT MAX(salary) AS "max_sal", MIN(salary) AS "min_sal", AVG(salary) AS "avg_sal", SUM(salary) AS "sum_sal" FROM employees;#带有组函数的字段最好起别名 #第三题:查询各job_id的员工工资的最大值,最小值,平均值,总和 SELECT job_id, MAX(salary), MIN(salary), AVG(salary), SUM(salary) FROM employees GROUP BY job_id; #第四题:选择具有各个job_id的员工人数 SELECT job_id, COUNT(*) AS "persons" FROM employees GROUP BY job_id; #第五题:查询员工最高工资和最低工资的差距: SELECT MAX(salary), MIN(salary), MAX(salary) - MIN(salary) AS "DIFFERENCE" FROM employees; #第六题:查询各个管理者手下员工的最低工资,其中最低工资不能低于6000,没有管理者的员工不计算在内 SELECT last_name, MIN(salary) FROM employees WHERE manager_id IS NOT NULL GROUP BY manager_id HAVING MIN(salary) >= 6000; #第七题:查询所有部门的名字,location_id, 员工数量和平均工资, 并按平均工资降序 SELECT department_name, location_id, COUNT(employee_id) AS "persons", AVG(salary) AS "avg_sal" FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id GROUP BY d.department_id, d.location_id#按照右边的表做分组操作 ORDER BY avg_sal DESC; #第八题:查询每个工种、每个部门的部门名称、工种名和最低工资 SELECT job_id, department_name, MIN(salary) FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id GROUP BY d.department_id, job_id;
九、子查询
子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从MySQL4.1开始引入。
SQL中子查询的使用大大增强了SELECT的查询能力, 因为很多时候查询需要从结果集中获取数据, 或者需要从同一个表中先计算得出一个数据结果, 然后与这个数据结果进行比较。
1.案例引入
Main query : 谁的工资比Abel高 ?
Subquery : Abel 的工资是多少 ?
现有的解决方案 与 子查询的引入 :
#方式一 : #先查出Abel的工资, 再过滤其他人的工资, 分两个SQL语句 SELECT last_name, salary FROM employees WHERE last_name = 'Abel'; SELECT last_name, salary FROM employees WHERE salary > 11000; #方式二 : #自连接 SELECT e2.last_name, e2.salary FROM employees e1, employees e2 WHERE e1.last_name = 'Abel' AND e2.salary > e1.salary;#多表的连接条件 #方式三 : #子查询 SELECT last_name, salary FROM employees WHERE salary > ( SELECT salary FROM employees WHERE last_name = 'Abel'); #称谓的规范 : 外查询(主查询) 和 内查询(子查询) /* 注意 : 子查询的结果被主查询使用 子查询要包含在括号内 将子查询放在比较条件的右侧 单行操作符对应单行子查询, 多行操作符对应多行子查询 */
2.子查询的分类
角度一 :从内查询返回结果的条目数 单行子查询 vs 多行子查询 子查询语句执行以后只有一条记录 叫做单行子查询 子查询语句执行以后含有多条记录 叫做多行子查询
角度二 :内查询是否被执行多次 相关子查询 vs 不相关子查询 比如 :相关子查询的需求 :查询工资大于本部门平均工资的员工信息 不相关子查询的需求 : 查询工资大于本公司平均工资的员工信息
3.单行子查询
单行比较操作符 :
操作符 | 含义 |
---|---|
= | equal to |
> | greater than |
>= | greater than or equal to |
< | less than |
<= | less than or equal to |
<> | not equal to |
#单行子查询 #题目 : 查询工资大于149号员工工资的信息 SELECT employee_id, last_name, salary FROM employees WHERE salary > ( SELECT salary FROM employees WHERE employee_id = 149); #子查询的编写技巧(步骤) 1 从外往里写 2 从里往外写 #题目 : 返回job_id 与 141号员工相同, salary比143号员工多的员工姓名,job_id, 和工资 SELECT last_name, job_id, salary FROM employees WHERE job_id <=> ( SELECT job_id FROM employees WHERE employee_id = 141 ) AND salary > ( SELECT salary FROM employees WHERE employee_id = 143 ); #题目 : 返回公司工资最少的员工的last_name, job_id, salary #先查公司最少工资是多少 再查对应工资的员工 SELECT MIN(salary) FROM employees; SELECT last_name FROM employees WHERE salary = (); #和在一起 : SELECT last_name FROM employees WHERE salary = ( SELECT MIN(salary) FROM employees ); #题目 : 查询与141号员工的manager_id, 和department_id相同的其它员工的 #employee_id, manager_id, department_id #方式一 SELECT employee_id, manager_id, department_id FROM employees WHERE manager_id = ( SELECT manager_id FROM employees WHERE employee_id = 141 ) AND department_id = ( SELECT department_id FROM employees WHERE employee_id = 141 ) AND employee_id <> 141; #方式二(了解) SELECT employee_id, manager_id, department_id FROM employees WHERE (manager_id, department_id) = ( SELECT manager_id, department_id FROM employees WHERE employee_id = 141 ); AND employee_id <> 141; #HAVING 里的子查询 #题目 : 查询最低工资大于50号部门最低工资的部门id和其最低工资 SELECT department_id, MIN(salary) AS "min_salary" FROM employees GROUP BY department_id HAVING MIN(salary) > ( SELECT MIN(salary) FROM employees WHERE department_id = 50 ); #题目 : 显示员工的employee_id, last_name, location #其中, 员工department_id 与 location_id为1800的department_id 相同 #则location为'Canada', 其余则为'USA' SELECT employee_id, last_name, () AS "location" FROM employees; SELECT department_id FROM departments WHERE location_id = 1800; CASE department_id WHEN () THEN 'Canada' ELSE 'USA' END #合起来是 : SELECT employee_id, last_name, CASE department_id WHEN (SELECT department_id FROM departments WHERE location_id = 1800) THEN 'Canada' ELSE 'USA' END AS "location" FROM employees; #子查询的空值问题 SELECT last_name, job_id FROM employees WHERE job_id = ( SELECT job_id FROM employees WHERE last_name = 'Haas'#没有这个人 查不到 那么不会报错 只是没有结果 ); #非法使用子查询 #error :Subquery returns more than 1 row SELECT last_name, job_id FROM employees WHERE salary = ( SELECT MIN(salary) FROM employees GROUP BY department_id#查出的是多行数据 ); #单行操作符对应多行数据
4.多行子查询
-
也称为集合比较子查询
-
内查询返回多行
-
使用多行比较操作符
多行比较操作符 :
操作符 | 含义 |
---|---|
IN | 等于列表中任意一个值 |
ANY | 需要和单行比较操作符一起使用, 和子查询返回的某一个值比较 |
ALL | 需要 和单行比较操作符一起使用, 和子查询返回的所有值比较 |
SOME | 实际上是ANY的别名, 作用相同, 一般常使用ANY |
体会ANY和ALL的区别
#多行子查询 #多行子查询的操作符 IN ANY ALL SOME(同ANY) # IN : 查询薪资等于各个部门最低工资的人(逻辑不好) SELECT last_name, employee_id, salary FROM employees WHERE salary IN ( SELECT MIN(salary) FROM employees GROUP BY department_id ); #ANY ALL : #题目 : 返回其他job_id 中比 job_id 为’IT_PROG‘部门任一工资低的员工的员工号 #姓名、job-id 以及salary SELECT employee_id, last_name, job_id, salary FROM employees WHERE job_id <> 'IT_PROG'#去掉 AND salary < ANY ( SELECT salary FROM employees WHERE job_id = 'IT_PROG' ); #题目 : 返回其他job_id 中比 job_id 为’IT_PROG‘部门所有工资低的员工的员工号 #姓名、job-id 以及salary SELECT employee_id, last_name, job_id, salary FROM employees WHERE job_id <> 'IT_PROG' AND salary < ALL ( SELECT salary FROM employees WHERE job_id = 'IT_PROG' ); #题目 : 查询平均工资最低的部门id #方式一 : 利用分页来获取最小值 SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) = ( SELECT AVG(salary) FROM employees GROUP BY department_id ORDER BY AVG(salary) ASC LIMIT 0, 1 ); SELECT MIN(AVG(salary))#错误的, MySQL中聚合函数不能嵌套, Oracle中可以 FROM employees GROUP BY department_id; SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id#将各个部门的平均薪资都查出来, 看作一张表 SELECT MIN(avg_sal) FROM ( SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ) t_dept_avg__sal; #方式二 : 将平均薪资看做一张表 SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) = ( SELECT MIN(avg_sal) FROM ( SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ) t_dept_avg__sal ); #方式三 : 使用多行子查询 SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) <= ALL ( SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ); #多行子查询的空值问题 SELECT last_name FROM employees WHERE employee_id IN ( SELECT manager_id FROM employees );#查出来的都是公司的管理者 SELECT last_name FROM employees WHERE employee_id NOT IN ( SELECT manager_id FROM employees #WHERE manager_id IS NOT NULL );#希望查出来的都不是公司的管理者 #result : NULL 是因为在内查询的时候 有一条记录为NULL 所以要把他过滤掉
5.相关子查询
以上练习的都是不相关子查询
相关子查询的执行流程 :
如果子查询的执行依赖于外部查询, 通常情况下都是因为子查询中的表使用到了外部的表, 并进行了条件关联,因此每执行一次外部查询, 子查询都要重新计算一次, 这样的子查询就叫做 关联子查询。
相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询。
说明 : 子查询中使用主查询中的列
#相关子查询 #回顾 :查询员工中工资不大于本公司平均工资的员工的 last_name, salary, department_id SELECT last_name, salary, department_id FROM employees WHERE salary > ( SELECT AVG(salary) FROM employees ); #题目 :查询员工中工资不大于本部门平均工资的员工的 last_name, salary, department_id #方式一 : 相关子查询 SELECT last_name, salary, department_id FROM employees e1 WHERE salary > ( SELECT AVG(salary) FROM employees e2 WHERE department_id = e1.department_id );#构成相关性的概念 #方式二 : 在FROM当中声明子查询 SELECT e.last_name, e.salary, e.department_id FROM employees e, ( SELECT department_id, AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ) t_dept_avg_sal WHERE e.salary > t_dept_avg_sal.avg_sal AND e.department_id = t_dept_avg_sal.department_id; #题目 :查询员工的id, salary, 按照department_name 排序 SELECT employee_id, salary FROM employees e ORDER BY ( SELECT department_name FROM departments d WHERE e.department_id = d.department_id ); #使用关联子查询 SELECT employee_id, salary FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id ORDER BY d.department_name;#需要左外连接 有的员工没有部门 /* 结论 :在 SELECT 结构中除了GROUP BY 和 LIMIT 以外 都可以使用子查询 SELECT ...,...,...(可能存在聚合函数) FROM... (LEFT\RIGHT)JOIN ... ON 多表的连接条件 (LEFT\RIGHT)JOIN... ON ... WHERE 过滤条件(不能有聚合函数) GROUP BY...,... HAVING...(包含聚合函数的过滤条件) ORDER BY...(ASC/DESC) LIMIT ...,... */ #题目 : 若employees表中employee_id与job_history表中employee_id相同的数目不小于2(调岗过两次及以上的), #输出这些相同id的员工的employee_id, last_name, 和其job_id SELECT * FROM job_history SELECT employee_id, last_name, job_id FROM employees e WHERE 2 <= ( SELECT COUNT(*) FROM job_history j WHERE e.employee_id = j.employee_id ); #相关子查询 :EXISTS 和 NOT EXISTS关键字 /* 关联子查询也会和EXISTS操作符一起来使用, 用来检查子查询中是否存在满足条件的行。 如果在子查询中不存在满足条件的行: 条件返回FALSE 继续在子查询中查找 如果在子查询中存在满足条件的行: 不在子查询中继续查找 条件返回TRUE NOT EXISTS 关键字表示如果不存在某种条件, 则返回TRUE, 否则返回FALSE。 */ #题目 :查询公司管理者的employee_id, last_name, job_id, department_id 等信息 #方式一:自连接 SELECT DISTINCT mar.employee_id, mar.last_name, mar.job_id, mar.department_id FROM employees emp, employees mar WHERE emp.manager_id = mar.employee_id;#需要去重 每个人对应相同的管理者的话 就显示一条就可以了 #方式二:子查询 SELECT DISTINCT manager_id FROM employees;#这是所有管理者的id SELECT employee_id, last_name, job_id, department_id FROM employees WHERE employee_id IN ( SELECT DISTINCT manager_id FROM employees#这是所有管理者的id ); #方式三:使用EXISTS SELECT employee_id, last_name, job_id, department_id FROM employees e1 WHERE EXISTS ( SELECT * FROM employees e2 WHERE e1.employee_id = e2.manager_id ); #题目 :查询departmemts表中, 不存在于employees表中的部门的department_id 和 department_name #方式一 : 左外连接去重 SELECT d.department_id, d.department_name FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id WHERE e.department_id IS NULL; #方式二 : NOT EXISTS SELECT department_id, department_name FROM departments d WHERE NOT EXISTS ( SELECT * FROM employees e WHERE d.department_id = e.department_id ); #思考题:自连接和子查询哪个效率高? #自连接的效率高, DBMS对自连接都会做优化, 而在子查询时如果改子查询能修改为多表查询, DBMS就会自动转换为多表查询
子查询的章节练习
#09子查询相关练习 #1.查询和Zlotkey相同部门的员工姓名和工资 SELECT last_name, salary FROM employees WHERE department_id = ( SELECT department_id FROM employees WHERE last_name = 'Zlotkey' ); #2.查询工资比公司平均工资高的员工的员工号,姓名和工资。 SELECT employee_id, last_name, salary FROM employees WHERE salary > ( SELECT AVG(salary) FROM employees ); #3.选择工资大于所有JOB_ID = 'SA_MAN'的员工的工资的员工的last_name, job_id, salary SELECT last_name, job_id, salary FROM employees WHERE salary > ALL ( SELECT salary FROM employees WHERE job_id = 'SA_MAN' ); #4.查询和姓名中包含字母u的员工在相同部门的员工的员工号和姓名 SELECT employee_id, last_name FROM employees WHERE department_id IN ( SELECT DISTINCT department_id#可以去重 FROM employees WHERE last_name LIKE '%u%' ); #5.查询在部门的location_id为1700的部门工作的员工的员工号 SELECT employee_id FROM employees WHERE department_id IN ( SELECT department_id FROM departments WHERE location_id = 1700 ); #6.查询管理者是King的员工姓名和工资 SELECT last_name, salary FROM employees WHERE manager_id IN ( SELECT employee_id FROM employees WHERE last_name = 'King' ); #使用EXISTS SELECT last_name, salary FROM employees e1 WHERE EXISTS ( SELECT * FROM employees e2 WHERE last_name = 'King' AND e1.manager_id = e2.employee_id ); #7.查询工资最低的员工信息: last_name, salary SELECT last_name, salary FROM employees WHERE salary = ( SELECT MIN(salary) FROM employees ); SELECT last_name, salary FROM employees WHERE salary = ( SELECT salary FROM employees ORDER BY salary ASC LIMIT 0, 1 ); #8.查询平均工资最低的部门信息 #从里往外写 #平均工资 : SELECT AVG(salary) FROM employees GROUP BY department_id; #最低的平均工资 : SELECT MIN(avg_sal) FROM ( SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id; ) t_dept_avg_sal; #最低平均工资的部门 SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) = ( SELECT MIN(avg_sal)#由于聚合函数不能嵌套所以要再写一个子查询 FROM ( SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ) t_dept_avg_sal ) #在这里可以优化一下不用两层子查询 使用排序分页找出最低的 SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) = ( SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ORDER BY avg_sal ASC LIMIT 0, 1 ); #或者使用多行子查询 SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) <= All ( SELECT AVG(salary) FROM employees GROUP BY department_id ); #最低平均工资的部门的信息 SELECT * FROM departments WHERE department_id = ( SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) = ( SELECT MIN(avg_sal) FROM ( SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ) t_dept_avg_sal ) ) #方式二 : 多表连接查询 SELECT d.* #查询department表的全部信息 FROM departments d, ( SELECT department_id, AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ORDER BY avg_sal ASC LIMIT 0, 1 ) t_dept_avg_sal WHERE d.department_id = t_dept_avg_sal.department_id #9.查询平均工资最低的部门信息和该部门的平均工资(相关子查询) #方式一 : 在要查的地方补上一个子查询 SELECT d.*, (SELECT AVG(salary) FROM employees WHERE department_id = d.department_id) AS "avg_sal" #SELECT 语句是在筛选语句后执行的所以 FROM departments d #筛选后的结果集只剩下平均工资最低的部门了 直接使用结果集来查department_id就行 WHERE department_id = ( SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) = ( SELECT MIN(avg_sal) FROM ( SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ) t_dept_avg_sal ) ) #方式二 多表查询 SELECT * #查询department表的全部信息 FROM departments d, ( SELECT department_id, AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ORDER BY avg_sal ASC LIMIT 0, 1 ) t_dept_avg_sal WHERE d.department_id = t_dept_avg_sal.department_id #10.查询平均工资最高的 job 信息 SELECT * FROM jobs WHERE job_id = ( SELECT job_id FROM employees GROUP BY job_id HAVING AVG(salary) = ( SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY job_id ORDER BY avg_sal DESC LIMIT 1 ) ); #可以和第八题一样再优化 SELECT j.* FROM jobs j, ( SELECT job_id, AVG(salary) AS "avg_sal" FROM employees GROUP BY job_id ORDER BY avg_sal DESC LIMIT 1 ) t_job_avg_sal WHERE j.job_id = t_job_avg_sal.job_id; #11.查询平均工资高于公司平均工资的部门有哪些? SELECT department_id FROM employees WHERE department_id IS NOT NULL GROUP BY department_id HAVING AVG(salary) > ( SELECT AVG(salary) FROM employees ); #12.查询出公司中所有 manager 的详细信息 #方式一 : 自连接 SELECT DISTINCT mgr.* FROM employees emp JOIN employees mgr ON emp.manager_id = mgr.employee_id; #方式二 : EXISTS SELECT * FROM employees e1 WHERE EXISTS ( SELECT * FROM employees e2 WHERE e1.employee_id = e2.manager_id ); #方式三 : 子查询 SELECT * FROM employees WHERE employee_id IN ( SELECT DISTINCT manager_id FROM employees ); #13.各个部门中 最高工资中最低的那个部门的 最低工资是多少? SELECT MIN(salary) FROM employees GROUP BY department_id HAVING department_id = ( SELECT department_id FROM employees GROUP BY department_id HAVING MAX(salary) = ( SELECT MAX(salary) AS "max_sal" FROM employees GROUP BY department_id ORDER BY max_sal ASC LIMIT 1 ) ); #14.查询平均工资最高的部门的 manager 的详细信息: last_name, department_id, email, salary SELECT last_name, department_id, email, salary FROM employees WHERE employee_id = ( SELECT manager_id FROM departments WHERE department_id = ( SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) = ( SELECT AVG(salary) AS "avg_sal" FROM employees GROUP BY department_id ORDER BY avg_sal DESC LIMIT 1 ) ) ); #15. 查询部门的部门号,其中不包括job_id是"ST_CLERK"的部门号 #方式一 : NOT IN SELECT department_id FROM departments WHERE department_id NOT IN ( SELECT department_id FROM employees WHERE job_id = 'ST_CLERK' ); #方式二 : NOT EXISTS SELECT department_id FROM departments d WHERE NOT EXISTS ( SELECT * FROM employees e WHERE d.department_id = e.department_id AND e.job_id = 'ST_CLERK' ); #16. 选择所有没有管理者的员工的last_name #直接查 SELECT last_name FROM employees WHERE manager_id IS NULL; #子查询方式 : SELECT last_name FROM employees emp WHERE NOT EXISTS ( SELECT * FROM employees mgr WHERE emp.manager_id = mgr.employee_id ); #17.查询员工号、姓名、雇用时间、工资,其中员工的管理者为 'De Haan' SELECT employee_id, last_name, hire_date, salary FROM employees WHERE manager_id IN ( SELECT employee_id FROM employees WHERE last_name = 'De Haan' ); #EXISTS SELECT employee_id, last_name, hire_date, salary FROM employees e1 WHERE EXISTS ( SELECT * FROM employees e2 WHERE e1.manager_id = e2.employee_id AND e2.last_name = 'De Haan' ); #18.查询各部门中工资比本部门平均工资高的员工的员工号, 姓名和工资(相关子查询) SELECT employee_id, last_name, salary FROM employees e1 WHERE salary > ( SELECT AVG(salary) FROM employees e2 WHERE e1.department_id = e2.department_id ); #方式二 在FROM下声明子查询 #19.查询每个部门下的部门人数大于 5 的部门名称(相关子查询) SELECT department_name FROM departments d WHERE 5 < ( SELECT COUNT(*) FROM employees e WHERE d.department_id = e.department_id ) #20.查询每个国家下的部门个数大于 2 的国家编号(相关子查询) DESC countries DESC departments DESC locations SELECT country_id FROM locations l WHERE 2 < ( SELECT COUNT(*) FROM departments d WHERE l.location_id = d.location_id ); /* 子查询的编写技巧: 1. 从外往里 2.从里往外 如果子查询的结构相对简单 就可以从外往里写 一旦子查询的结构复杂 建议从里往外写 如果是相关子查询 通常从外往里写 */