第07章 单行函数
1. 数值函数
1.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 |
1.2 角度与弧度
函数 |
用法 |
RADIANS(x) |
将角度转化为弧度,其中,参数x为角度值 |
DEGREES(x) |
将弧度转化为角度,其中,参数x为弧度值 |
1.3 三角函数
函数 |
用法 |
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为弧度值 |
1.4 指数与对数
函数 |
用法 |
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 |
1.5 进制间的转换
函数 |
用法 |
BIN(x) |
返回x的二进制编码 |
HEX(x) |
返回x的十六进制编码 |
OCT(x) |
返回x的八进制编码 |
CONV(x,f1,f2) |
返回f1进制数变成f2进制数 |
2. 字符串函数
函数 |
用法 |
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 |
注意:MySQL中,字符串的位置是从1开始的。
3. 日期和时间函数
3.1 获取日期、时间
函数 |
用法 |
CURDATE() ,CURRENT_DATE() |
返回当前日期,只包含年、月、日 |
CURTIME() , CURRENT_TIME() |
返回当前时间,只包含时、分、秒 |
NOW() / SYSDATE() / CURRENT_TIMESTAMP() / LOCALTIME() / LOCALTIMESTAMP() |
返回当前系统日期和时间 |
UTC_DATE() |
返回UTC(世界标准时间)日期 |
UTC_TIME() |
返回UTC(世界标准时间)时间 |
3.2 日期与时间戳的转换
函数 |
用法 |
UNIX_TIMESTAMP() |
以UNIX时间戳的形式返回当前时间。SELECT UNIX_TIMESTAMP() ->1634348884 |
UNIX_TIMESTAMP(date) |
将时间date以UNIX时间戳的形式返回。 |
FROM_UNIXTIME(timestamp) |
将UNIX时间戳的时间转换为普通格式的时间 |
3.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 |
3.4 日期的操作函数
函数 |
用法 |
EXTRACT(type FROM date) |
返回指定日期中特定的部分,type指定返回的值 |
EXTRACT(type FROM date)函数中type的取值与含义:
type取值 |
含义 |
MICROSECOND |
返回毫秒数 |
SECOND |
返回秒数 |
MINUTE |
返回分钟数 |
HOUR |
返回小时数 |
DAY |
返回天数 |
WEEK |
返回日期在一年中的第几个星期 |
MONTH |
返回日期在一年中的第几个月 |
QUARTER |
返回日期在一年中的第几个季度 |
YEAR |
返回日期的年份 |
SECOND_MICROSECOND |
返回秒和毫秒值 |
MINUTE_MICROSECOND |
返回分钟和毫秒值 |
MINUTE_SECOND |
返回分钟和秒值 |
HOUR_MICROSECOND |
返回小时和毫秒值 |
HOUR_SECOND |
返回小时和秒值 |
HOUR_MINUTE |
返回小时和分钟值 |
DAY_MICROSECOND |
返回天和毫秒值 |
DAY_SECOND |
返回天和秒值 |
DAY_MINUTE |
返回天和分钟值 |
DAY_HOUR |
返回天和小时 |
YEAR_MONTH |
返回年和月 |
3.5 时间和秒钟转换的函数
函数 |
用法 |
TIME_TO_SEC(time) |
将 time 转化为秒并返回结果值。转化的公式为: 小时*3600+分钟*60+秒 |
SEC_TO_TIME(seconds) |
将 seconds 描述转化为包含小时、分钟和秒的时间 |
3.6 计算日期和时间的函数
函数 |
用法 |
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的取值:
间隔类型 |
含义 |
HOUR |
小时 |
MINUTE |
分钟 |
SECOND |
秒 |
YEAR |
年 |
MONTH |
月 |
DAY |
日 |
YEAR_MONTH |
年和月 |
DAY_HOUR |
日和小时 |
DAY_MINUTE |
日和分钟 |
DAY_SECOND |
日和秒 |
HOUR_MINUTE |
小时和分钟 |
HOUR_SECOND |
小时和秒 |
MINUTE_SECOND |
分钟和秒 |
函数 |
用法 |
ADDTIME(time1,time2) |
返回time1加上time2的时间。当time2为一个数字时,代表的是秒 ,可以为负数 |
SUBTIME(time1,time2) |
返回time1减去time2后的时间。当time2为一个数字时,代表的是秒,可以为负数 |
DATEDIFF(date1,date2) |
返回date1 - date2的日期间隔天数 |
TIMEDIFF(time1, time2) |
返回time1 - time2的时间间隔 |
FROM_DAYS(N) |
返回从0000年1月1日起,N天以后的日期 |
TO_DAYS(date) |
返回日期date距离0000年1月1日的天数 |
LAST_DAY(date) |
返回date所在月份的最后一天的日期 |
MAKEDATE(year,n) |
针对给定年份与所在年份中的天数返回一个日期 |
MAKETIME(hour,minute,second) |
将给定的小时、分钟和秒组合成时间并返回 |
PERIOD_ADD(time,n) |
返回time加上n后的时间 |
3.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参数常用的格式符:
格式符 |
说明 |
格式符 |
说明 |
%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 |
%% |
表示% |
GET_FORMAT函数中date_type和format_type参数取值如下:
日期类型 |
格式化类型 |
返回的格式化字符串 |
DATE |
USA |
%m.%d.%Y |
DATE |
JIS |
%Y-%m-%d |
DATE |
ISO |
%Y-%m-%d |
DATE |
EUR |
%d.%m.%Y |
DATE |
INTERNAL |
%Y%m%d |
TIME |
USA |
%h:%i:%s %P |
TIME |
JIS |
%H:%i:%s |
TIME |
ISO |
%H:%i:%s |
TIME |
EUR |
%H.%i.%s |
TIME |
INTERNAL |
%H%i%s |
DATETIME |
USA |
%Y-%m-%d %H.%i.%s |
DATETIME |
JIS |
%Y-%m-%d %H:%i:%s |
DATETIME |
ISO |
%Y-%m-%d %H:%i:%s |
DATETIME |
EUR |
%Y-%m-%d %H.%i.%s |
DATETIME |
INTERNAL |
%Y%m%d%H%i%s |
4. 流程控制函数
函数 |
用法 |
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... |
5. 加密与解密函数
函数 |
用法 |
PASSWORD(str) |
返回字符串str的加密版本,41位长的字符串。加密结果不可逆,常用于用户的密码加密。在mysql8.0中已弃用。 |
MD5(str) |
返回字符串str的md5加密后的值,也是一种加密方式。若参数为NULL,则会返回NULL |
SHA(str) |
从原明文密码str计算并返回加密后的密码字符串,当参数为NULL时,返回NULL。 SHA加密算法比MD5更加安全 。 |
ENCODE(value,password_seed) |
返回使用password_seed作为加密密码加密value,在mysql8.0中已弃用。 |
DECODE(value,password_seed) |
返回使用password_seed作为加密密码解密value,在mysql8.0中已弃用。 |
6. MySQL信息函数
函数 |
用法 |
VERSION() |
返回当前MySQL的版本号 |
CONNECTION_ID() |
返回当前MySQL服务器的连接数 |
DATABASE(),SCHEMA() |
返回MySQL命令行当前所在的数据库 |
USER(),CURRENT_USER()、SYSTEM_USER(),SESSION_USER() |
返回当前连接MySQL的用户名,返回结果格式为“主机名@用户名” |
CHARSET(value) |
返回字符串value自变量的字符集 |
COLLATION(value) |
返回字符串value的比较规则 |
7. 其他函数
函数 |
用法 |
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 USING char_code) |
将value所使用的字符编码修改为char_code |
第08章 聚合函数
1. 聚合函数介绍
1.1 AVG和SUM函数
可以对数值型数据使用AVG 和 SUM 函数。
mysql> SELECT AVG(salary), MAX(salary),MIN(salary), SUM(salary) -> FROM employees -> WHERE job_id LIKE '%REP%'; +-------------+-------------+-------------+-------------+ | AVG(salary) | MAX(salary) | MIN(salary) | SUM(salary) | +-------------+-------------+-------------+-------------+ | 8272.727273 | 11500.00 | 6000.00 | 273000.00 | +-------------+-------------+-------------+-------------+
1.2 MIN和MAX函数
可以对任意数据类型的数据使用 MIN 和 MAX 函数。
mysql> SELECT MIN(hire_date), MAX(hire_date) -> FROM employees; +----------------+----------------+ | MIN(hire_date) | MAX(hire_date) | +----------------+----------------+ | 1987-06-17 | 2000-04-21 | +----------------+----------------+
1.3 COUNT函数
- COUNT(*)返回表中记录总数,适用于任意数据类型。
mysql> SELECT COUNT(*) -> FROM employees -> WHERE department_id = 50; +----------+ | COUNT(*) | +----------+ | 45 | +----------+
- COUNT(expr) 返回expr不为NULL的记录总数。
mysql> SELECT COUNT(commission_pct) -> FROM employees -> WHERE department_id = 50; +-----------------------+ | COUNT(commission_pct) | +-----------------------+ | 0 | +-----------------------+
- 问题:用count(*),count(1),count(列名)谁好呢?
其实,对于MyISAM引擎的表是没有区别的。这种引擎内部有一计数器在维护着行数。
Innodb引擎的表用count(*),count(1)直接读行数,复杂度是O(n),因为innodb真的要去数一遍。但好于具体的count(列名)。
- 问题:能不能使用count(列名)替换count(*)?
不要使用 count(列名)来替代count(*)
,count(*)
是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
2. GROUP BY
2.1 基本使用
可以使用GROUP BY子句将表中的数据分成若干组
SELECT column, group_function(column) FROM table [WHERE condition] [GROUP BY group_by_expression] [ORDER BY column];
明确:WHERE一定放在FROM后面
在SELECT列表中所有未包含在组函数中的列都应该包含在GROUP BY子句中
SELECT department_id, AVG(salary) FROM employees GROUP BY department_id;
包含在 GROUP BY 子句中的列不必包含在SELECT 列表中
SELECT AVG(salary) FROM employees GROUP BY department_id;
2.2 使用多个列分组
SELECT department_id dept_id, job_id, SUM(salary) FROM employees GROUP BY department_id, job_id;
2.3 GROUP BY中使用WITH ROLLUP
使用WITH ROLLUP
关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。
SELECT department_id,AVG(salary) FROM employees WHERE department_id > 80 GROUP BY department_id WITH ROLLUP;
注意:
当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。
3. HAVING
3.1 基本使用
SELECT department_id, MAX(salary) FROM employees GROUP BY department_id HAVING MAX(salary)>10000;
- 非法使用聚合函数 : 不能在 WHERE 子句中使用聚合函数。
mysql> SELECT department_id, AVG(salary) -> FROM employees -> WHERE AVG(salary) > 8000 -> GROUP BY department_id; ERROR 1111 (HY000): Invalid use of group function
结论:
当过滤条件中有聚合函数时,则此过滤条件必须声明在HAVING中。
当过滤条件中没有聚合函数时,则此过滤条件声明在WHERE中或HAVING中都可以。但是,建议大家声明在WHERE中。
3.2 WHERE和HAVING的对比
优点 |
缺点 |
|
WHERE |
先筛选数据再关联,执行效率高 |
不能使用分组中的计算函数进行筛选 |
HAVING |
可以使用分组中的计算函数 |
在最后的结果集中进行筛选,执行效率较低 |
4. SELECT的执行过程
4.1 查询的结构
#方式1: SELECT ...,....,... FROM ...,...,.... WHERE 多表的连接条件 AND 不包含组函数的过滤条件 GROUP BY ...,... HAVING 包含组函数的过滤条件 ORDER BY ... ASC/DESC LIMIT ...,... #方式2: SELECT ...,....,... FROM ... JOIN ... ON 多表的连接条件 JOIN ... ON ... WHERE 不包含组函数的过滤条件 AND/OR 不包含组函数的过滤条件 GROUP BY ...,... HAVING 包含组函数的过滤条件 ORDER BY ... ASC/DESC LIMIT ...,... #其中: #(1)from:从哪些表中筛选 #(2)on:关联多表查询时,去除笛卡尔积 #(3)where:从表中筛选的条件 #(4)group by:分组依据 #(5)having:在统计结果中再次筛选 #(6)order by:排序 #(7)limit:分页
4.2 SELECT执行顺序
你需要记住 SELECT 查询时的两个顺序:
1. 关键字的顺序是不能颠倒的:
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT...
2.SELECT 语句的执行顺序(在 MySQL 和 Oracle 中,SELECT 执行顺序基本相同):
#FROM ...,...-> ON -> (LEFT/RIGNT JOIN) -> WHERE -> GROUP BY -> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT
SELECT DISTINCT player_id, player_name, count(*) as num # 顺序 5 FROM player JOIN team ON player.team_id = team.team_id # 顺序 1 WHERE height > 1.80 # 顺序 2 GROUP BY player.team_id # 顺序 3 HAVING num > 2 # 顺序 4 ORDER BY num DESC # 顺序 6 LIMIT 2 # 顺序 7
在 SELECT 语句执行这些步骤的时候,每个步骤都会产生一个虚拟表
,然后将这个虚拟表传入下一个步骤中作为输入。需要注意的是,这些步骤隐含在 SQL 的执行过程中,对于我们来说是不可见的。
4.3 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 语句的时候,还要注意相应的关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。
第09章 子查询
1. 需求分析与问题解决
1.1 实际问题
题目:谁的工资比Abel高?
#方式一: SELECT 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 e1.`salary` < e2.`salary` #方式三:子查询 SELECT last_name,salary FROM employees WHERE salary > ( SELECT salary FROM employees WHERE last_name = 'Abel' );
1.2 子查询的基本使用
- 子查询(内查询)在主查询之前一次执行完成。
- 子查询的结果被主查询(外查询)使用 。
- 注意事项
- 子查询要包含在括号内
- 将子查询放在比较条件的右侧
- 单行操作符对应单行子查询,多行操作符对应多行子查询
1.3 子查询的分类
分类方式1:
我们按内查询的结果返回一条还是多条记录,将子查询分为单行子查询
、多行子查询
。
分类方式2:
我们按内查询是否被执行多次,将子查询划分为相关(或关联)子查询
和不相关(或非关联)子查询
。
2. 单行子查询
2.1 单行比较操作符
操作符 |
含义 |
= |
equal to |
> |
greater than |
>= |
greater than or equal to |
< |
less than |
<= |
less than or equal to |
<> |
not equal to |
2.2 代码示例
题目:返回公司工资最少的员工的last_name,job_id和salary
SELECT last_name, job_id, salary FROM employees WHERE salary = ( SELECT MIN(salary) FROM employees );
2.3 HAVING 中的子查询
题目:查询最低工资大于50号部门最低工资的部门id和其最低工资
SELECT department_id, MIN(salary) FROM employees GROUP BY department_id HAVING MIN(salary) > ( SELECT MIN(salary) FROM employees WHERE department_id = 50 );
2.4 CASE中的子查询
题目:显示员工的employee_id,last_name和location。其中,若员工department_id与location_id为1800的department_id相同,则location为’Canada’,其余则为’USA’。
SELECT employee_id, last_name, ( CASE department_id WHEN ( SELECT department_id FROM departments WHERE location_id = 1800 ) THEN 'Canada' ELSE 'USA' END ) location FROM employees;
2.5 子查询中的空值问题
mysql> SELECT last_name, job_id -> FROM employees -> WHERE job_id = ( -> SELECT job_id -> FROM employees -> WHERE last_name = 'Haas' -> ); Empty set (0.01 sec)
子查询不返回任何行
2.6 非法使用子查询
mysql> SELECT employee_id, last_name -> FROM employees -> WHERE salary = ( # 多行子查询使用单行比较符 -> SELECT MIN(salary) -> FROM employees -> GROUP BY department_id -> ); ERROR 1242 (21000): Subquery returns more than 1 row
3. 多行子查询
3.1 多行比较操作符
操作符 |
含义 |
IN |
等于列表中的任意一个 |
ANY |
需要和单行比较操作符一起使用,和子查询返回的某一个值比较 |
ALL |
需要和单行比较操作符一起使用,和子查询返回的所有值比较 |
SOME |
实际上是ANY的别名,作用相同,一般常使用ANY |
3.2 代码示例
题目:查询平均工资最低的部门id
#方式1: SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) = ( SELECT MIN(avg_sal) FROM ( SELECT AVG(salary) avg_sal FROM employees GROUP BY department_id ) dept_avg_sal ); #方式2: SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) <= ALL ( SELECT AVG(salary) avg_sal FROM employees GROUP BY department_id );
MySQL中聚合函数是不能嵌套使用的。
3.3 空值问题
mysql> SELECT last_name -> FROM employees -> WHERE employee_id NOT IN ( -> SELECT manager_id -> FROM employees -> ); Empty set (0.01 sec)
4. 相关子查询
4.1 相关子查询执行流程
如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为关联子查询
。
4.2 代码示例
题目:查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id
方式一:相关子查询
SELECT last_name, salary, department_id FROM employees e WHERE salary > ( SELECT AVG(salary) FROM employees WHERE department_id = e.department_id );
方式二:在 FROM 中使用子查询
SELECT last_name,salary,e1.department_id FROM employees e1,( SELECT department_id,AVG(salary) dept_avg_sal FROM employees GROUP BY department_id ) e2 WHERE e1.`department_id` = e2.department_id AND e2.dept_avg_sal < e1.`salary`;
在ORDER BY 中使用子查询:
题目:查询员工的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中,除了GROUP BY 和 LIMIT之外,其他位置都可以声明子查询!
4.3 EXISTS与NOT EXISTS关键字
- 关联子查询通常也会和 EXISTS操作符一起来使用,用来检查在子查询中是否存在满足条件的行。
- 如果在子查询中不存在满足条件的行:
- 条件返回 FALSE
- 继续在子查询中查找
- 如果在子查询中存在满足条件的行:
- 不在子查询中继续查找
- 条件返回 TRUE
- NOT EXISTS关键字表示如果不存在某种条件,则返回TRUE,否则返回FALSE。
题目:查询公司管理者的employee_id,last_name,job_id,department_id信息
方式一:
SELECT employee_id, last_name, job_id, department_id FROM employees e1 WHERE EXISTS ( SELECT * FROM employees e2 WHERE e2.manager_id = e1.employee_id );
方式二:自连接
SELECT DISTINCT e1.employee_id, e1.last_name, e1.job_id, e1.department_id FROM employees e1 JOIN employees e2 WHERE e1.employee_id = e2.manager_id;
方式三:
SELECT employee_id,last_name,job_id,department_id FROM employees WHERE employee_id IN ( SELECT DISTINCT manager_id FROM employees );
题目:查询departments表中,不存在于employees表中的部门的department_id和department_name
SELECT department_id,department_name FROM departments d WHERE NOT EXISTS ( SELECT * FROM employees e WHERE d.`department_id` = e.`department_id` );
题目中可以使用子查询,也可以使用自连接。一般情况建议你使用自连接,因为在许多 DBMS 的处理过程中,对于自连接的处理速度要比子查询快得多。
第10章 创建和管理表
1. 基础知识
1.1 标识符命名规则
- 数据库名、表名不得超过30个字符,变量名限制为29个
- 必须只能包含 A–Z, a–z, 0–9, _共63个字符
- 数据库名、表名、字段名等对象名中间不要包含空格
- 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名
- 必须保证你的字段没有和保留字、数据库系统或常用方法冲突。如果坚持使用,请在SQL语句中使用`(着重号)引起来
- 保持字段名和类型的一致性:在命名字段并为其指定数据类型的时候一定要保证一致性,假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了
2. 创建和管理数据库
2.1 创建数据库
- 方式1:创建数据库
CREATE DATABASE 数据库名;
- 方式2:创建数据库并指定字符集
CREATE DATABASE 数据库名 CHARACTER SET 字符集;
- 方式3:判断数据库是否已经存在,不存在则创建数据库(
推荐
)
CREATE DATABASE IF NOT EXISTS 数据库名;
注意:DATABASE 不能改名。一些可视化工具可以改名,它是建新库,把所有表复制到新库,再删旧库完成的
2.2 使用数据库
- 查看当前所有的数据库
SHOW DATABASES; #有一个S,代表多个数据库
- 查看当前正在使用的数据库
SELECT DATABASE(); #使用的一个 mysql 中的全局函数
- 查看指定库下所有的表
SHOW TABLES FROM 数据库名;
- 查看数据库的创建信息
SHOW CREATE DATABASE 数据库名; 或者: SHOW CREATE DATABASE 数据库名\G
- 使用/切换数据库
USE 数据库名;
注意:要操作表格和数据之前必须先说明是对哪个数据库进行操作,否则就要对所有对象加上“数据库名.”。
2.3 修改数据库
- 更改数据库字符集
ALTER DATABASE 数据库名 CHARACTER SET 字符集; #比如:gbk、utf8等
2.4 删除数据库
- 方式1:删除指定的数据库
DROP DATABASE 数据库名;
- 方式2:删除指定的数据库(
推荐
)
DROP DATABASE IF EXISTS 数据库名;
3. 创建表
3.1 创建方式1
- 必须具备:
- CREATE TABLE权限
- 存储空间
- 语法格式:
CREATE TABLE [IF NOT EXISTS] 表名( 字段1 数据类型 [约束条件] [默认值], 字段2 数据类型 [约束条件] [默认值], 字段3 数据类型 [约束条件] [默认值], …… [表约束条件] );
加上了IF NOT EXISTS关键字,则表示:如果当前数据库中不存在要创建的数据表,则创建数据表;如果当前数据库中已经存在要创建的数据表,则忽略建表语句,不再创建数据表。
3.2 创建方式2
- 使用 AS subquery 选项,将创建表和插入数据结合起来
CREATE TABLE table [(column, column...)] AS subquery;
- 指定的列和子查询中的列要一一对应
- 通过列名和默认值定义列
CREATE TABLE emp1 AS SELECT * FROM employees; CREATE TABLE emp2 AS SELECT * FROM employees WHERE 1=2; -- 创建的emp2是空表
3.3 查看数据表结构
在MySQL中创建好数据表之后,可以查看数据表的结构。MySQL支持使用DESCRIBE/DESC
语句查看数据表结构,也支持使用SHOW CREATE TABLE
语句查看数据表结构。
语法格式如下:
SHOW CREATE TABLE 表名\G
使用SHOW CREATE TABLE语句不仅可以查看表创建时的详细语句,还可以查看存储引擎和字符编码。
4. 修改表
4.1 追加一个列
语法格式如下:
ALTER TABLE 表名 ADD [COLUMN] 字段名 字段类型 [FIRST|AFTER 字段名];
4.2 修改一个列
- 修改字段数据类型、长度、默认值、位置的语法格式如下:
ALTER TABLE 表名 MODIFY [COLUMN] 字段名1 字段类型 [DEFAULT 默认值][FIRST|AFTER 字段名 2];
- 对默认值的修改只影响今后对表的修改
4.3 重命名一个列
- 语法格式如下:
ALTER TABLE 表名 CHANGE [column] 列名 新列名 新数据类型;
4.4 删除一个列
- 语法格式如下:
ALTER TABLE 表名 DROP [COLUMN] 字段名;
5. 重命名表
- 方式一:使用RENAME(
推荐
)
RENAME TABLE emp TO myemp;
- 方式二:
ALTER table dept RENAME [TO] detail_dept; -- [TO]可以省略
6. 删除表
- 在MySQL中,当一张数据表
没有与其他任何数据表形成关联关系
时,可以将当前数据表直接删除。 - 数据和结构都被删除
- 所有正在运行的相关事务被提交
- 所有相关索引被删除
- 语法格式:
DROP TABLE [IF EXISTS] 数据表1 [, 数据表2, …, 数据表n];
IF EXISTS
的含义为:如果当前数据库中存在相应的数据表,则删除数据表;如果当前数据库中不存在相应的数据表,则忽略删除语句,不再执行删除数据表的操作。
- DROP TABLE 语句不能回滚
7. 清空表
- TRUNCATE TABLE语句:
- 删除表中所有的数据
- 释放表的存储空间
- 举例:
TRUNCATE TABLE detail_dept;
- TRUNCATE语句不能回滚,而使用 DELETE 语句删除数据,可以回滚
- COMMIT:提交数据。一旦执行COMMIT,则数据就被永久的保存在了数据库中,意味着数据不可以回滚。
- ROLLBACK:回滚数据。一旦执行ROLLBACK,则可以实现数据的回滚。回滚到最近的一次COMMIT之后。
SET autocommit = FALSE; DELETE FROM emp2; #TRUNCATE TABLE emp2; SELECT * FROM emp2; ROLLBACK; SELECT * FROM emp2;
DDL 和 DML 的说明
- DDL的操作一旦执行,就不可回滚。指令SET autocommit = FALSE对DDL操作失效。(因为在执行完DDL操作之后,一定会执行一次COMMIT。而此COMMIT操作不受SET autocommit = FALSE影响的。)
- DML的操作默认情况,一旦执行,也是不可回滚的。但是,如果在执行DML之前,执行了 SET autocommit = FALSE,则执行的DML操作就可以实现回滚。
阿里开发规范:
【参考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 TRIGGER,有可能造成事故,故不建议在开发代码中使用此语句。
说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
8. 内容拓展
拓展1:阿里巴巴《Java开发手册》之MySQL字段命名
- 【
强制
】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
- 正例:aliyun_admin,rdc_config,level3_name
- 反例:AliyunAdmin,rdcConfig,level_3_name
- 【
强制
】禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。 - 【
强制
】表必备三字段:id, gmt_create, gmt_modified。
- 说明:其中 id 必为主键,类型为BIGINT UNSIGNED、单表时自增、步长为 1。gmt_create, gmt_modified 的类型均为 DATETIME 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新
- 【
推荐
】表的命名最好是遵循 “业务名称_表的作用”。
- 正例:alipay_task 、 force_project、 trade_config
- 【
推荐
】库名与应用名称尽量一致。 - 【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
拓展2:如何理解清空表、删除表等操作需谨慎?!
表删除
操作将把表的定义和表中的数据一起删除,并且MySQL在执行删除操作时,不会有任何的确认信息提示,因此执行删除操作时应当慎重。在删除表前,最好对表中的数据进行备份
,这样当操作失误时可以对数据进行恢复,以免造成无法挽回的后果。
同样的,在使用ALTER TABLE
进行表的基本修改操作时,在执行操作过程之前,也应该确保对数据进行完整的备份
,因为数据库的改变是无法撤销
的,如果添加了一个不需要的字段,可以将其删除;相同的,如果删除了一个需要的列,该列下面的所有数据都将会丢失。
拓展3:MySQL8新特性—DDL的原子化
在MySQL 8.0版本中,InnoDB表的DDL支持事务完整性,即DDL操作要么成功要么回滚
。DDL操作回滚日志写入到data dictionary数据字典表mysql.innodb_ddl_log(该表是隐藏的表,通过show tables无法看到)中,用于回滚操作。通过设置参数,可将DDL操作日志打印输出到MySQL错误日志中。
第11章 数据处理之增删改
1. 插入数据
1.1 方式1:VALUES的方式添加
使用这种语法一次只能向表中插入一条数据。
情况1:为表的所有字段按默认顺序插入数据
INSERT INTO 表名 VALUES (value1,value2,....);
值列表中需要为表的每一个字段指定值,并且值的顺序必须和数据表中字段定义时的顺序相同。
情况2:为表的指定字段插入数据(推荐)
INSERT INTO 表名(column1 [, column2, …, columnn]) VALUES (value1 [,value2, …, valuen]);
为表的指定字段插入数据,就是在INSERT语句中只向部分字段中插入值,而其他字段的值为表定义时的默认值。
情况3:同时插入多条记录(推荐)
INSERT语句可以同时向数据表中插入多条记录,插入时指定多个值列表,每个值列表之间用逗号分隔开,基本语法格式如下:
INSERT INTO table_name VALUES (value1 [,value2, …, valuen]), (value1 [,value2, …, valuen]), …… (value1 [,value2, …, valuen]);
或者
INSERT INTO table_name(column1 [, column2, …, columnn]) VALUES (value1 [,value2, …, valuen]), (value1 [,value2, …, valuen]), …… (value1 [,value2, …, valuen]);
一个同时插入多行记录的INSERT语句等同于多个单行插入的INSERT语句,但是多行的INSERT语句在处理过程中效率更高
。因为MySQL执行单条INSERT语句插入多行数据比使用多条INSERT语句快,所以在插入多条记录时最好选择使用单条INSERT语句的方式插入。
VALUES 也可以写成 VALUE ,但是VALUES是标准写法。
1.2 方式2:将查询结果插入到表中
INSERT还可以将SELECT语句查询的结果插入到表中,此时不需要把每一条记录的值一个一个输入,只需要使用一条INSERT语句和一条SELECT语句组成的组合语句即可快速地从一个或多个表中向一个表中插入多行。
基本语法格式如下:
INSERT INTO 目标表名 (tar_column1 [, tar_column2, …, tar_columnn]) SELECT (src_column1 [, src_column2, …, src_columnn]) FROM 源表名 [WHERE condition]
- 在 INSERT 语句中加入子查询。
- 不必书写 VALUES 子句。
- 子查询中的值列表应与 INSERT 子句中的列名对应。
举例:
INSERT INTO emp2 SELECT * FROM employees WHERE department_id = 90;
INSERT INTO sales_reps(id, name, salary, commission_pct) SELECT employee_id, last_name, salary, commission_pct FROM employees WHERE job_id LIKE '%REP%';
说明:emp2表中要添加数据的字段的长度不能低于employees表中查询的字段的长度。
如果emp2表中要添加数据的字段的长度低于employees表中查询的字段的长度的话,就有添加不成功的风险。
2. 更新数据
- 使用 UPDATE 语句更新数据。语法如下:
UPDATE table_name SET column1=value1, column2=value2, … , column=valuen [WHERE condition]
- 可以一次更新多条数据。
- 如果需要回滚数据,需要保证在DML前,进行设置:SET AUTOCOMMIT = FALSE;
- 使用 WHERE 子句指定需要更新的数据。
- 如果省略 WHERE 子句,则表中的所有数据都将被更新。
3. 删除数据
- 使用 DELETE 语句从表中删除数据
DELETE FROM table_name [WHERE <condition>];
- 使用 WHERE 子句删除指定的记录。
- 如果省略 WHERE 子句,则表中的全部数据将被删除
4. MySQL8新特性:计算列
什么叫计算列呢?简单来说就是某一列的值是通过别的列计算得来的。例如,a列值为1、b列值为2,c列不需要手动插入,定义a+b的结果为c的值,那么c就是计算列,是通过别的列计算得来的。
在MySQL 8.0中,CREATE TABLE 和 ALTER TABLE 中都支持增加计算列。下面以CREATE TABLE为例进行讲解。
举例:定义数据表tb1,然后定义字段id、字段a、字段b和字段c,其中字段c为计算列,用于计算a+b的值。 首先创建测试表tb1,语句如下:
CREATE TABLE tb1( id INT, a INT, b INT, c INT GENERATED ALWAYS AS (a + b) VIRTUAL );
插入演示数据,语句如下:
INSERT INTO tb1(a,b) VALUES (100,200);
查询数据表tb1中的数据,结果如下:
mysql> SELECT * FROM tb1; +------+------+------+------+ | id | a | b | c | +------+------+------+------+ | NULL | 100 | 200 | 300 | +------+------+------+------+
更新数据中的数据,语句如下:
mysql> UPDATE tb1 SET a = 500; mysql> SELECT * FROM tb1; +------+------+------+------+ | id | a | b | c | +------+------+------+------+ | NULL | 500 | 200 | 700 | +------+------+------+------+