目录
函数
一、函数分类:
- 单行函数:一个输入一个输出(本文)
(1)操作数据对象
(2)接受参数返回一个结果
(3)只对一行进行变换
(4)每行返回一个结果
(5)可以嵌套
(6)参数可以是一列或一个值 - 多行函数
单行函数
基本函数
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;
# 当你传入的因子一样时,返回的随机数一样,这说明`RAND()`函数的底层其实是根据因子计算出的随机数,是伪随机数
SELECT RAND(),RAND(),RAND(10),RAND(10),RAND(-1),RAND(-1)
FROM DUAL;
SELECT ROUND(123.556),ROUND(123.456,0),ROUND(123.456,1),ROUND(123.456,2),
ROUND(123.456,-1),ROUND(153.456,-2)
FROM DUAL;
SELECT TRUNCATE(123.456,0),TRUNCATE(123.496,1),TRUNCATE(129.45,-1)
FROM DUAL;
# 单行函数可以嵌套
SELECT TRUNCATE(ROUND(123.456,2),0)
FROM DUAL;
角度与弧度互换函数
SELECT RADIANS(30),RADIANS(45),RADIANS(60),RADIANS(90),
DEGREES(2*PI()),DEGREES(RADIANS(60))
FROM DUAL;
三角函数
SELECT SIN(RADIANS(30)),DEGREES(ASIN(1)),TAN(RADIANS(45)),DEGREES(ATAN(1))
FROM DUAL;
指数与对数
指数:SELECT POW(2,5),POWER(2,4),EXP(2) FROM DUAL;
对数:SELECT LN(EXP(2)),LOG(EXP(2)),LOG10(10),LOG2(4) FROM DUAL;
进制间的转换
CONV(x,f1,f2)
将f1进制的x转换成f2进制,如CONV(10,2,8)
就是将二进制的10转换成八进制,二进制的10也就是2,转换成八进制也是2
SELECT BIN(10),HEX(10),OCT(10),CONV(10,10,8)
FROM DUAL;
字符串函数
SELECT ASCII('Abcdfsf'),CHAR_LENGTH('hello'),CHAR_LENGTH('我们'),
LENGTH('hello'),LENGTH('我们')
LENGTH('hello')
一个字符用一个字节存储就行,所以LENGTH('hello')
站5个字节。而UTF-8中每个汉字占3个字节,所以LENGTH('我们')
占6个字节。用LENGTH()
就是在衡量底层存储时使用了多少个字节,CHAR_LENGTH()
衡量的是内存中占了多少个字符,所以CHAR_LENGTH('hello'),CHAR_LENGTH('我们')
都为5。
# 字符串拼接成:xxx worked for yyy
SELECT CONCAT(emp.last_name,' worked for ',mgr.last_name) "details"
FROM employees emp JOIN employees mgr
WHERE emp.`manager_id` = mgr.employee_id;
SELECT CONCAT_WS('-','hello','world','hello','beijing')
FROM DUAL;
字符串与字符串之间用第一个参数拼接
如果第一个参数是空字符串
SELECT CONCAT_WS('','hello','world','hello','beijing')
FROM DUAL;
则效果同CONCAT
方法:
SELECT CONCAT('hello','world','hello','beijing')
FROM DUAL;
SELECT INSERT('helloworld',2,3,'aaaaa'),REPLACE('hello','ll','mmm')
FROM DUAL;
字符串的索引是从1开始的,INSERT('helloworld',2,3,'aaaaa')
将helloworld
中以索引2开始,往后数3个字符(包含第3个字符),将这些字符(这一范围的字符)替换为aaaaa
。REPLACE('hello','ll','mmm')
将hello
中的ll
替换为mmm
SELECT REPLACE('hello','lol','mmm')
FROM DUAL;
如果在将hello
中没有找到lol
,则不替换不处理不报错,返回原字符串。
SELECT UPPER('HelLo'),LOWER('HelLo')
FROM DUAL;
在Oracle中字符串里面区分大小写,下述代码只会找到last_name = 'King';
的人,king
和KING
都找不到
SELECT last_name,salary
FROM employees
WHERE last_name = 'King';
所以可以使用UPPER()
、LOWER()
转换一下:
SELECT last_name,salary
FROM employees
WHERE LOWER(last_name) = 'King';
不过MySQL中不存在这个问题,MySQL字符串里面也不区分大小写
SELECT LEFT('hello',2),RIGHT('hello',3),RIGHT('hello',13)
FROM DUAL;
# LPAD:实现右对齐效果
# RPAD:实现左对齐效果
SELECT employee_id,last_name,LPAD(salary,10,'*')
FROM employees;
LPAD(salary,10,'*')
表示salary
占10位(小数点也要算),不够的用*
在左边
补齐
SELECT CONCAT('---',LTRIM(' h el lo '),'***'),
TRIM('oo' FROM 'ooheollo')
FROM DUAL;
TRIM(字符串)
用于去除字符串首尾空格,TRIM('oo' FROM 'ooheollo')
用于去除ooheollo
首尾的oo
,LTRIM()
、RTRIM()
分别用于去除首、尾的空格
SELECT REPEAT('hello',4),LENGTH(SPACE(5)),STRCMP('abc','abe')
FROM DUAL;
SPACE()
用于提供空格,STRCMP()
用于字符串比大小,返回1表示前面一个字符串比后面的大
SELECT SUBSTR('hello',2,2),LOCATE('ll','hello')
FROM DUAL;
SUBSTR(字符串,索引位置(截取起始位置,要截取该位置的字符),截取数量)
LOCATE()
第一个字符串在第二个字符串中首次出现的位置
如果第二个字符串中没有第一个字符串,返回0
SELECT ELT(2,'a','b','c','d'),FIELD('mm','gg','jj','mm','dd','mm'),
FIND_IN_SET('mm','gg,mm,jj,dd,mm,gg')
FROM DUAL;
SELECT employee_id,NULLIF(LENGTH(first_name),LENGTH(last_name)) "compare"
FROM employees;
NULLIF(字符串1, 字符串2)
比较两个字符串,如果相等,则返回NULL,否则返回字符串1
日期和时间函数
获取日期、时间
SELECT CURDATE(),CURRENT_DATE(),CURTIME(),NOW(),SYSDATE(),
UTC_DATE(),UTC_TIME()
FROM DUAL;
SELECT CURDATE(),CURDATE() + 0,CURTIME() + 0,NOW() + 0
FROM DUAL;
日期与时间戳的转换
SELECT UNIX_TIMESTAMP(),UNIX_TIMESTAMP('2021-10-01 12:12:32'),
FROM_UNIXTIME(1635173853),FROM_UNIXTIME(1633061552)
FROM DUAL;
获取月份、星期、星期数、天数等函数
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;
日期的操作函数
type
的取值与含义:
SELECT EXTRACT(SECOND FROM NOW()),EXTRACT(DAY FROM NOW()),
EXTRACT(HOUR_MINUTE FROM NOW()),EXTRACT(QUARTER FROM '2021-05-12')
FROM DUAL;
时间和秒钟转换的函数
SELECT TIME_TO_SEC(CURTIME()),
SEC_TO_TIME(83355)
FROM DUAL;
计算日期和时间的函数
type
的取值:
SELECT NOW(),DATE_ADD(NOW(),INTERVAL 1 YEAR),
DATE_ADD(NOW(),INTERVAL -1 YEAR),
DATE_SUB(NOW(),INTERVAL 1 YEAR)
FROM DUAL;
DATE_ADD(NOW(),INTERVAL 1 YEAR),
在当前时间加1年,DATE_ADD(NOW(),INTERVAL -1 YEAR)
相当于DATE_SUB(NOW(),INTERVAL 1 YEAR)
都是在当前时间减1年
SELECT DATE_ADD(NOW(), INTERVAL 1 DAY) AS col1,DATE_ADD('2021-10-21 23:32:12',INTERVAL 1 SECOND) AS col2,
ADDDATE('2021-10-21 23:32:12',INTERVAL 1 SECOND) AS col3,
DATE_ADD('2021-10-21 23:32:12',INTERVAL '1_1' MINUTE_SECOND) AS col4,
DATE_ADD(NOW(), INTERVAL -1 YEAR) AS col5, #可以是负数
DATE_ADD(NOW(), INTERVAL '1_1' YEAR_MONTH) AS col6 #需要单引号
FROM DUAL;
DATE_ADD('2021-10-21 23:32:12',INTERVAL '1_1' MINUTE_SECOND)
在2021-10-21 23:32:12
上增加1分1秒
,注意'1_1'
不是数值,要用''
引起来
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()),32),MAKETIME(10,21,23),PERIOD_ADD(20200101010101,10)
FROM DUAL;
SUBTIME(NOW(),'1:1:3')
当前时间减去1小时1分钟1秒
日期的格式化与解析
上述非GET_FORMAT函数
中fmt
参数常用的格式符及说明如下:
GET_FORMAT函数
中date_type
和format_type
参数取值如下:
格式化:日期 —> 字符串
解析: 字符串 ----> 日期
此时我们谈的是日期的显式格式化和解析,之前,我们接触过隐式的格式化或解析
SELECT *
FROM employees
WHERE hire_date = '1993-01-13';
我们这里'1993-01-13'
是个字符串,因为它满足Date格式,所以在查找时,底层会自动将这个字符串转换成Date格式进行匹配,这就是隐式格式化或解析
#格式化:
SELECT DATE_FORMAT(CURDATE(),'%Y-%M-%D'),
DATE_FORMAT(NOW(),'%Y-%m-%d'),TIME_FORMAT(CURTIME(),'%h:%i:%S'),
DATE_FORMAT(NOW(),'%Y-%M-%D %h:%i:%S %W %w %T %r')
FROM DUAL;
#解析:格式化的逆过程
SELECT STR_TO_DATE('2021-October-25th 11:37:30 Monday 1','%Y-%M-%D %h:%i:%S %W %w')
FROM DUAL;
SELECT GET_FORMAT(DATE,'USA')
FROM DUAL;
SELECT DATE_FORMAT(CURDATE(),GET_FORMAT(DATE,'USA'))
FROM DUAL;
流程控制函数
流程处理函数可以根据不同的条件,执行不同的处理流程,可以在SQL语句中实现不同的条件选择。MySQL中的流程处理函数主要包括IF()
、IFNULL()
和CASE()
函数。
#4.1 IF(VALUE,VALUE1,VALUE2)
SELECT last_name,salary,IF(salary >= 6000,'高工资','低工资') "details"
FROM employees;
SELECT last_name,commission_pct,IF(commission_pct IS NOT NULL,commission_pct,0) "details",
salary * 12 * (1 + IF(commission_pct IS NOT NULL,commission_pct,0)) "annual_sal"
FROM employees;
#4.2 IFNULL(VALUE1,VALUE2):看做是IF(VALUE,VALUE1,VALUE2)的特殊情况
SELECT last_name,commission_pct,IFNULL(commission_pct,0) "details"
FROM employees;
#4.3 CASE WHEN ... THEN ...WHEN ... THEN ... ELSE ... END
# 类似于java的if ... else if ... else if ... else
SELECT last_name,salary,CASE WHEN salary >= 15000 THEN '白骨精'
WHEN salary >= 10000 THEN '潜力股'
WHEN salary >= 8000 THEN '小屌丝'
ELSE '草根' END "details",department_id
FROM employees;
SELECT last_name,salary,CASE WHEN salary >= 15000 THEN '白骨精'
WHEN salary >= 10000 THEN '潜力股'
WHEN salary >= 8000 THEN '小屌丝'
END "details"
FROM employees;
#4.4 CASE ... WHEN ... THEN ... WHEN ... THEN ... ELSE ... END
# 类似于java的swich ... case...
/*
练习1
查询部门号为 10,20, 30 的员工信息,
若部门号为 10, 则打印其工资的 1.1 倍,
20 号部门, 则打印其工资的 1.2 倍,
30 号部门,打印其工资的 1.3 倍数,
其他部门,打印其工资的 1.4 倍数
*/
SELECT employee_id,last_name,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 "details"
FROM employees;
/*
练习2
查询部门号为 10,20, 30 的员工信息,
若部门号为 10, 则打印其工资的 1.1 倍,
20 号部门, 则打印其工资的 1.2 倍,
30 号部门打印其工资的 1.3 倍数
*/
SELECT employee_id,last_name,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 "details"
FROM employees
WHERE department_id IN (10,20,30);
加密与解密函数
加密与解密函数主要用于对数据库中的数据进行加密和解密处理,以防止数据被他人窃取。
# PASSWORD()在mysql8.0中弃用。
SELECT MD5('mysql'),SHA('mysql'),MD5(MD5('mysql'))
FROM DUAL;
MD5()
和SHA()
这两种加密方式是不可逆的,只能将明文加密成暗文,不能将暗文解密成明文
#ENCODE()\DECODE() 在mysql8.0中弃用。
SELECT ENCODE('atguigu','mysql'),DECODE(ENCODE('atguigu','mysql'),'mysql')
FROM DUAL;
MySQL信息函数
MySQL中内置了一些可以查询MySQL信息的函数,这些函数主要用于帮助数据库开发或运维人员更好地对数据库进行维护工作。
SELECT VERSION(),CONNECTION_ID(),DATABASE(),SCHEMA(),
USER(),CURRENT_USER(),CHARSET('尚硅谷'),COLLATION('尚硅谷')
FROM DUAL;
其他函数
#如果n的值小于或者等于0,则只保留整数部分
SELECT FORMAT(123.125,2),FORMAT(123.125,0),FORMAT(123.125,-2)
FROM DUAL;
SELECT CONV(16, 10, 2), CONV(8888,10,16), CONV(NULL, 10, 2)
FROM DUAL;
#以“192.168.1.100”为例,计算方式为192乘以256的3次方,加上168乘以256的2次方,加上1乘以256,再加上100。
SELECT INET_ATON('192.168.1.100'),INET_NTOA(3232235876)
FROM DUAL;
#BENCHMARK()用于测试表达式的执行效率
SELECT BENCHMARK(100000,MD5('mysql'))
FROM DUAL;
# CONVERT():可以实现字符集的转换
SELECT CHARSET('atguigu'),CHARSET(CONVERT('atguigu' USING 'gbk'))
FROM DUAL;
练习
# 1.显示系统时间(注:日期+时间)
SELECT NOW(),SYSDATE(),CURRENT_TIMESTAMP(),LOCALTIME(),LOCALTIMESTAMP() #大家只需要掌握一个函数就可以了
FROM DUAL;
# 2.查询员工号,姓名,工资,以及工资提高百分之20%后的结果(new salary)
SELECT employee_id,last_name,salary,salary * 1.2 "new salary"
FROM employees;
# 3.将员工的姓名按首字母排序,并写出姓名的长度(length)
SELECT last_name,LENGTH(last_name) "name_length"
FROM employees
#order by last_name asc;
ORDER BY name_length ASC;
# 4.查询员工id,last_name,salary,并作为一个列输出,别名为OUT_PUT
SELECT CONCAT(employee_id,',',last_name,',',salary) "OUT_PUT"
FROM employees;
# 5.查询公司各员工工作的年数、工作的天数,并按工作年数的降序排序
# DATEDIFF(date1, date2):date1与date2相差的天数
# TO_DAYS(date):date距离0000年1月1日的天数
SELECT employee_id,DATEDIFF(CURDATE(),hire_date)/365 "worked_years",DATEDIFF(CURDATE(),hire_date) "worked_days",
TO_DAYS(CURDATE()) - TO_DAYS(hire_date) "worked_days1"
FROM employees
ORDER BY worked_years DESC;
# 6.查询员工姓名,hire_date , department_id,满足以下条件:
#雇用时间在1997年之后,department_id 为80 或 90 或110, commission_pct不为空
SELECT last_name,hire_date,department_id
FROM employees
WHERE department_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'; # 显式转换操作,格式化
# STR_TO_DATE('1997-01-01','%Y-%m-%d'):将字符串1997-01-01转为%Y-%m-%d格式的日期
AND hire_date >= STR_TO_DATE('1997-01-01','%Y-%m-%d');# 显式转换操作,解析:字符串 ----> 日期
# 7.查询公司中入职超过10000天的员工姓名、入职时间
SELECT last_name,hire_date
FROM employees
WHERE DATEDIFF(CURDATE(),hire_date) >= 10000;
# 8.做一个查询,产生下面的结果
#<last_name> earns <salary> monthly but wants <salary*3>
# TRUNCATE(数字,从第几位开始截断)
SELECT CONCAT(last_name,' earns ',TRUNCATE(salary,0), ' monthly but wants ',TRUNCATE(salary * 3,0)) "Dream Salary"
FROM employees;
# 9.使用case-when,按照下面的条件:
/*job grade
AD_PRES A
ST_MAN B
IT_PROG C
SA_REP D
ST_CLERK E
产生下面的结果:
*/
SELECT last_name "Last_name",job_id "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 "Grade"
FROM employees;
SELECT last_name "Last_name",job_id "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 "Grade"
FROM employees;
聚合函数
聚合函数是对一组数据进行汇总的函数,输入的是一组数据的集合,输出的是单个值。
常见的聚合函数
一、AVG
/SUM
:只适用于数值类型的字段(或变量),不会将null值计算在内
SELECT AVG(salary),SUM(salary),AVG(salary) * 107
FROM employees;
如下的操作没有意义:
SELECT SUM(last_name),AVG(last_name),SUM(hire_date)
FROM employees;
二、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(2),COUNT(*)
FROM employees ;
计算表中有多少条记录:
- 方式1:
COUNT(*)
- 方式2:
COUNT(1)
- 方式3:
COUNT(具体字段)
: 不一定对!因为如果具体字段的值为null,是不会被计算在内的
如果需要统计表中的记录数,使用COUNT(*)
、COUNT(1)
、COUNT(具体字段)
哪个效率更高呢?
- 如果使用的是MyISAM存储引擎,则三者效率相同,都是O(1)
- 如果使用的是InnoDB存储引擎,则三者效率:
COUNT(*) = COUNT(1)> COUNT(字段)
四、GROUP BY
:
#需求:查询各个部门的平均工资,最高工资
SELECT department_id,AVG(salary),SUM(salary)
FROM employees
GROUP BY department_id
#需求:查询各个job_id的平均工资
SELECT job_id,AVG(salary)
FROM employees
GROUP BY job_id;
#需求:查询各个department_id,job_id的平均工资,同一个部门同一个工种的平均工资
#方式1:
SELECT department_id,job_id,AVG(salary)
FROM employees
GROUP BY department_id,job_id;
#方式2:这两种方式查询结果一样,表每一列排序不一样
SELECT job_id,department_id,AVG(salary)
FROM employees
GROUP BY job_id,department_id;
SELECT department_id,job_id,AVG(salary)
FROM employees
GROUP BY department_id;
这样会出错,导致数据紊乱,department_id为10的不止SA_REP这一个部门
结论:
- 结论1:SELECT中出现的非组函数的字段必须声明在GROUP BY中。反之,GROUP BY中声明的字段可以不出现在SELECT中。
- 结论2:GROUP BY声明在FROM后面、WHERE后面,ORDER BY前面、LIMIT前面
再GROUP BY中使用WITH ROLLUP
关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。
SELECT department_id,AVG(salary)
FROM employees
GROUP BY department_id WITH ROLLUP;
注意:当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。
#错误的:
SELECT department_id,AVG(salary) avg_sal
FROM employees
GROUP BY department_id WITH ROLLUP
ORDER BY avg_sal ASC;
五、HAVING
:用来过滤数据
- 查询各个部门中最高工资比10000高的部门信息
#错误的写法:
SELECT department_id,MAX(salary)
FROM employees
WHERE MAX(salary) > 10000
GROUP BY department_id;
如果过滤条件中使用了聚合函数,则必须使用HAVING来替换WHERE。否则,报错,并且HAVING必须声明在GROUP BY的后面。
#正确的写法:
SELECT department_id,MAX(salary)
FROM employees
GROUP BY department_id# 根据department_id分组
HAVING MAX(salary) > 10000;# 分组后,查询每个组薪水大于10000的组
注意:开发中,我们使用HAVING的前提是SQL中使用了GROUP BY。
- 查询部门id为10,20,30,40这4个部门中最高工资比10000高的部门信息
#方式1:推荐,执行效率高于方式2.
SELECT department_id,MAX(salary)
FROM employees
WHERE department_id IN (10,20,30,40)
GROUP BY department_id
HAVING MAX(salary) > 10000;
#方式2:
SELECT department_id,MAX(salary)
FROM employees
GROUP BY department_id
HAVING MAX(salary) > 10000 AND department_id IN (10,20,30,40);
注意:
(1)当过滤条件中有聚合函数时,则此过滤条件必须声明在HAVING中。
(2)当过滤条件中没有聚合函数时,则此过滤条件声明在WHERE中或HAVING中都可以。但是,建议大家声明在WHERE中。
- WHERE与HAVING的对比
(1)从适用范围上来讲,HAVING的适用范围更广。这是因为HAVING在GROUP BY之后,可以使用分组字段和分组中的计算函数,对分组的结果集进行筛选
(2)如果过滤条件中没有聚合函数:这种情况下,WHERE的执行效率要高于HAVING。这是因为在需要通过连接从关联表中获取需要的数据,WHERE是先筛选后连接,而HAVING是先连接后筛选
练习
#1.where子句可否使用组函数进行过滤? No!
#2.查询公司员工工资的最大值,最小值,平均值,总和
SELECT MAX(salary),MIN(salary),AVG(salary),SUM(salary)
FROM employees;
#3.查询各job_id的员工工资的最大值,最小值,平均值,总和
SELECT MAX(salary),MIN(salary),AVG(salary),SUM(salary)
FROM employees
GROUP BY job_id;
#4.选择具有各个job_id的员工人数
SELECT COUNT(employee_id)
FROM employees
GROUP BY job_id;
# 5.查询员工最高工资和最低工资的差距(DIFFERENCE) #DATEDIFF
SELECT MAX(salary) - MIN(salary)
FROM employees;
# 6.查询各个管理者手下员工的最低工资,其中最低工资不能低于6000,没有管理者的员工不计算在内
SELECT manager_id,MIN(salary)
FROM employees
WHERE manager_id IS NOT NULL
GROUP BY manager_id
HAVING MIN(salary) >= 6000;
# 7.查询所有部门的名字,location_id,员工数量和平均工资,并按平均工资降序
SELECT d.department_name,d.location_id,COUNT(employee_id),AVG(salary)
FROM departments d LEFT JOIN employees e
ON d.`department_id` = e.`department_id`
GROUP BY department_name,location_id
# 8.查询每个工种、每个部门的部门名、工种名和最低工资
SELECT d.department_name,e.job_id,MIN(salary)
FROM departments d LEFT JOIN employees e
ON d.`department_id` = e.`department_id`
GROUP BY department_name,job_id
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 ...,....
其中:
(1)from:从哪些表中筛选
(2)on:关联多表查询时,去除笛卡尔积
(3)where:从表中筛选的条件
(4)group by:分组依据
(5)having:在统计结果中再次筛选
(6)order by:排序
(7)limit:分页
SQL语句的执行过程:#FROM ...,...-> ON -> (LEFT/RIGNT JOIN) -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT
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语句的时候,不一定存在所有的关键字,相应的阶段就会省略。