文章目录
数据库:
如果一个项目是动态的(内容会变化的,网页后缀.jsp、php、shtml)内容的话,则数据库必不可少。
DB:database,看做是数据库文件(类似于.doc,.txt)
DBMS:Database Management System 数据库管理系统。(类似于word,wps,记事本)
MySQL数据库服务器安装了MySQL DBMS,使用其来管理和操作DB,使用的是SQL语言。
MySQL
1、SQL语句分类
1、DDL(Data Definition Languages)数据定义语言:这些语句定义了不同的数据库,表,视图,索引等数据对象,还可以用于创建,删除,修改数据库和数据表的结构。语句关键字:create , drop , alter,rename , truncate(清空)等
2、DML(Data Manipulation Languages)数据操作语言:用于添加,删除,更新和查询数据库记录,并检查数据完整性。语句关键字: insert, delete, update, select等
3、DCL(Data Control Language)数据控制语言:用于定义数据库,表,字段,用户的访问权限和安全级别。语句关键字:grant, revoke, commit, rollback(撤销),savepoint等
2、SQL的基本规则和规范
基本规则
:
SQL可以写在一行或者多行,为了挺高可读性,各子句分行写,必要时缩进;
每行命令以;或\G,\g结尾(\g是转置显示的意思吧)
关键字不能被缩进也不能分行(废话)
SQL大小写规范(建议遵守)
MySQL在windows下是大小写不敏感的(即不区分大小写);
MySQL在Linux下是大小写敏感的{数据库名,表名,表的别名,变量名是严格区分大小写的,关键字,函数名,列名(字段名),列的别名(字段别名)是不区分大小写的}。
推荐写法:数据库名,表名,表别名,字段名,字段别名等都小写;关键字,函数名,绑定变量等都大写
注释
:
单行注释: # 或者–
多行注释:/**/
命名规则
:
数据库、表名不得超过30个字符,变量名限制为29;
必须只能包含A-Z,a-z,0-9,_ 共63个字符;
数据库名,表名,字段名等对象名中间不能包含空格
同一个mysql软件中,数据库不能同名,同一个库中,表不能重名;。。。
必须保证你的字段没有和保留名,数据库系统或常用方法冲突,如果坚持使用,请在SQL语句中使用`(着重号);
保持字段名和类型的的一致性,在命名字段并为其指定数据类型的时候一定要保证一致性。假设数据类型在一个表里是整数,在另一个表里可就不要是字符型了。
mysql 8.0版本默认字符集就是utf8
3、MySQL的启动
1、启动MySQL服务:service mysql start
2、查看是否启动:sudo systemctl status mysql
3、进入MySQL的方式:mysql -u 用户名 -p
4、退出MySQL的方式:exit
默认目录/文件位置: 数据库存储目录:/var/lib/mysql 配置文件:/etc/mysql/my.cnf
一、MySQL的基本操作(简易学习)
数据库:类似于一个excel文件;
数据表:可以看做一个excel文件中的工作表;
记录):行;
列(字段:列;
1、库操作
以下命令在mysql终端命令行中执行。不区分大小写。
show databases; //显示当前mysql中的全部数据库
create database 库名; //创建数据库
drop database 库名; // 删除数据库
use 库名; //切换数据库
2、表操作
show tables; //显示当前数据库中所有的表名(必须先use数据库)
//创建表
create table 表名
(
列名称1 数据类型[NOT NULL AUTO_INCREMENT],
列名称2 数据类型,
列名称3 数据类型,
。。。
primary key(主键字段名)
);
常见数据类型:int char(定长字符) varchar(不定长字符)
主键一般就是序号所在的那一列(主键不能重复)。
desc 表名; //查看表结构
drop table [if exists] 表名; //删除表
3、记录/字段操作
增加记录
insert into 表名称 values(值1,值2,...);
insert into 表名称 (列1,列2,...) values(值1,值2,...); //***
查询记录
select 列名称1,列名称2,... from 表名称 where 条件;
select * from 表名称 where 条件;
更新记录
update 表名称 SET 列名称1 = 值1,列名称2 = 值2,... where 条件;//条件一定要写,不写就全部更新了。
在执行影响行数的sql操作的时候一定要加条件。
删除记录
delete from 表名称 where 列名称 = 值;
4、备份与还原
备份
:
//常见备份:
//全量备份(数据+结构)linux命令行执行
mysqldump -u用户名 -p密码 -A > 备份文件路径
//指定库备份(数据+结构)//*****常用
mysqldump -u用户名 -p密码 库名 > 备份文件路径
//多个库备份(数据+结构)
mysqldump -uroot -p密码 --databases db1 db2 > 备份文件路径
案例:每一分钟备份一次数据库wang
//创建shell脚本
touch test8.sh
chmod 777 test8.sh
之后使用计划任务实现每一分钟执行脚本
还原
:
方法有两种:1、(重点)mysql命令行下source方法;2、系统命令行方法
(1)还原全部数据库:
//mysql下
source 备份文件路径
//系统命令行下
mysql -uroot -p密码 < 备份文件路径
(2)还原单个数据库(指定数据库)
//mysql下
use 库名
source 备份文件路径
//系统命令行下
mysql -u用户名 -p密码 库名 <备份文件路径
有时候会还原包含中文的数据库,但是可能会显示不出来。就需要指定mysql连接字符集
set names utf8; //等价于utf-8 ----这是指定传输过程中
//保证三码一致----服务器端,传输过程中,客户端
二、MySQL的学习(详细)
1、导入现有的数据表,表数据。
方式1:source 文件路径
2、图像化界面下直接在工具中导入。
1、select 基本语句及常见操作
select 字段1,字段2,… from 表名
//1
select 1+1,1*10;
//等价于:
select 1+1,1*10
from dual; //伪表
//2
select * from employees;//employees是已经存在的表名,*是表中所有的字段(列)
2.1.1、列的别名
//as alias(别名)可以省略,
//别名也可以使用双引号引起来。算是三种
select employee_id emp_id,last_name as lname,department_id "部门ID",
salary * 12 "annual sal"
from employees;
2.1.2、去除重复行
查询员工表中一共有哪些部门id呢?
select department_id from employees;//会罗列出所有的。
select distinct department_id from employees;
2.1.3、空值参与运算
空值:null,不等同0,null
一般表示不知道;
空值参与运算:结果一定也是为空值。
select employee_id,salary "月工资",salary * (1 + commission_pct) * 12 "年工资" from employees;
//实际问题解决方案,引入ifnull函数,当该函数第一个参数为空值时,就返回第二个参数值,否则返回第一个参数
select employee_id,salary "月工资" ,salary * (1 + IFNULL(commission_pct,0)) * 12 "年工资"
from employees;
2.1.4、着重号
字段名,表名是一个关键字的时候,需要使用着重号
//假设我们有一个表名叫order,但是order是一个关键字
select * from order;//会识别为关键字,需要我们添加表名。
//解决方法
select * from `order`;
2.1.5、查询常数
select 'NPU',employee_id from employees;//会在每一个结果前加NPU
2.1.6、显示表结构
describe employees;//显示了表中字段的详细信息
desc employees;//等价
2.1.7、过滤数据
where 一定要在 from结构的后面。
select * from employees where department_id = 90;
2、运算符
2.2.1、算数运算符
#+ - * / div % mod
select 100,100 + 1, 100 - 30, 100 *1, 100/2.0,100 mod 2 from DUAL;
select 100 + 'a'; //会将‘a’看做0,'2' 看做2
select employee_id,last_name,salary from employees where employee_id%2==0;
2.2.2、比较运算符
-- = <=> <> != < <= > >=
select 1 = 2, 1 != 2,1<>2, 1 = '1', 1 = 'a', 0 ='a' from DUAL;
#字符串转换不成功,则为0
select 'addf' = 'asds' from DUAL;#都是字符串的,则进行ANSI码比较
select 1 = NULL,NULL = NULL;#都是NULL
select last_name,salary,commission_pct from employees where commision_pct = NULL;#此时执行,不会有任何结果。
//安全等于 <=>,可以用来对NULL进行判断,在两个操作数均为NULL下,其返回值是1,而不是NULL,当一个操作数是NULL,返回值是0,也不是NULL
select 1 <=> NULL,NULL <=> NULL;# 0 , 1
# IS NULL(ISNULL) 判断值,字符串,表达式是否为空
select B from tablename where A IS NULL; # WHERE ISNULL(A);
select last_name,salary,commission_pct from employees where commision_pct IS NULL;
# IS NOT NULL 判断值,字符串,表达式是否不为空
SELECT B from tablename where A IS NOT NULL; # WHERE IS NOT NULL(A)
select last_name,salary,commission_pct from employees where commision_pct IS NOT NULL;
# LEAST 在多个值中返回最小值
select D from tablename where C = LEAST(A,B);
select LEAST(first_name,last_name) from employees;
# GREATEST 在多个值中返回最大值
select D from tablename where C = GREATEST(A,B);
select GREATEST(first_name,last_name) from employees;
# BETWEEN AND 判断一个值是否在两个值之间,闭区间 , NOT BETWEEN AND
select D from tablename where C BETWEEN A AND B;
select last_name,salary,commission_pct from employees
where salary BETWEEN 6000 AND 8000;
where salary >= 6000 AND salary <=8000
# IN 判断一个值是否为列表的任意一个值
select D from tablename WHERE C IN (A,B);
select department_id,last_name,salary,commission_pct from employees
where department_id IN (10,20,30);
where department_id = 10 OR department_id = 20;
# NOT IN 判断一个值是否不是列表中的任意一个值
select D from tablename C NOTIN(A,B);
select department_id,last_name,salary,commission_pct from employees
where department_id NOT IN (10,20,30);
# LIKE 判断一个值是否符模糊匹配的规则
# %表示不确定个数的字符(0个,多个)
select C from tablename where A LIKE B;
select department_id,last_name,salary,commission_pct from employees
where last_name LIKE 'a%'; #查询包含字符‘a’的信息
where last_name LIKE 'a%'; # 查询以字符'a'开头的信息
#查询即包含a又包含e的信息
select department_id,last_name,salary,commission_pct from employees
where last_name LIKE '%a%' AND last_name LIKE '%e%';
where last_name LIKE '%a%e%' OR last_name LIKE '%e%a%';
#查询第二个字符是‘a’的信息,
# _表示一个不确定字符
select department_id,last_name,salary,commission_pct from employees
where last_name LIKE '_a%';
#查询第二字符是_,第三个是a的信息 \ 是转义字符
select department_id,last_name,salary,commission_pct from employees
where last_name LIKE '_\_a%';
#了解
select department_id,last_name,salary,commission_pct from employees
where last_name LIKE '_$_a%' ESCAPE '$'; #指出$是转义字符
# REGEXP 判断一个值是否符合正则表达式的规则
select C from tablename where A REGEXP B;
# RLIKE 判断一个值是否符合正则表达式的规则
select C from tablename where A RLIKE B
2.2.3、逻辑运算符
# NOT ! 非
# AND && 与
# OR || 或
# XOR 异或
3、排序与分页
如果没有使用排序操作,默认情况下查询返回的数据是按照添加数据的顺序显示的。
#ORDER BY对查询到的数据进行排序。默认升序
#升序 ASC(ascend) ; 降序 DESC(descend)
select employee_id,last_name,salary from employee order by salary desc;
#列的别名只能在order by 中使用,不能在where中使用。因为和执行顺序有关,先from,在where,再select,再order by ,where必须声明在from之后,order by 之前
select employee_id,last_name,salary, salary * 12 annual_salary
from employee
where department_id IN (50,60,70)
order by annual_salary desc;
#二级排序
select employee_id,salary,department_id
from employees
order by department_id DESC,salary ASC;
mysql使用limit实现数据的分页显示
#参数1是起始索引,从0开始,后面是从索引开始的个数。
#mysql8.0的新特性:limit ... offset ... ; offset后面的是起始索引,limit后面的是个数
select employee_id,last_name
from employees
limit 0,20; #每页20个数据,显示第一页的20个数据,前面是0可以省略,只写20即可。
limit 20,20;#第二页 20*(2-1)
limit 40,20;#第三页 20*(3-1)
where … order by … limit … 声明顺序如下:
select employee_id,last_name,salary
from employees
where salary > 6000
order by salary DESC
LIMIT 0,20;
4、多表查询
也叫关联查询,指的是两个或更多个表一起完成查询操作。
前提条件
:这些表之间是有关系的(一对一,一对多),他们之间一定是有关联字段,这个关联字段可能建立了外键,也可能没有建立外键。比如:员工表和部门表,依靠“部门编号”进行关联。
#实现多表的查询?
#出现了笛卡尔积错误
#错误实现,他会把employee_id遍历一遍,又与每个部门匹配一遍。
select employee_id,department_name
from employees,departments;#107*27个信息
# 等价下面, cross join 就是笛卡尔连接(交叉连接)
select employee_id,department_name
from employees cross join departments;#107*27个信息
# 正确方式,需要连接条件
select employee_id,department_name
from employees,departments
where employees.department_id = departments.department_id;
# 如果查询语句中出现多个表都存在的字段,则必须指明此字段所在的表。从sql优化的角度,建议多表查询时,都指明其所在的表。
select employee_id,last_name,department_name,employees.department_id
from employees,departments
where employees.department_id = departments.department_id;
#可以给表起别名,使用起来简单,并且在select,where中使用到有别名的表名,就必须使用别名,不能使用原来的表名。
select emp.employee_id,emp.last_name,emp.department_id,dep.department_name
from employees emp,departments dep
where emp.department_id = dep.department_id;
如果有n个表实现多表查询,则需要至少n-1个连接条件。
select e.employee_id,e.last_name,e.department_id,d.department_name,l.city,l.location_id
from employees e,departments d,locations l
where e.department_id = d.department_id and d.location_id = l.location_id;
4.1、多表查询的分类
角度1
: 等值连接 vs 非等值连接
# 前面就是等值的,举例非等值
#查询每个人的工作档位
select e.last_name,e.salary,j.grade_level
from employees e, job_grades j
where e.salary >= j.lowest_sal and e.salary <= j.highest_sal;
#等价下面
where e.salary between j.lowest_sal and j.highest_sal;
角度2
:自连接 vs 非自连接
#前面的实现的都是非连接,举例自连接,就是同一个表的某一个字段用到的是本表的另一个字段信息。
#查询每个员工的id,姓名及其管理者的id,姓名(管理者其实也是本表的员工)
select e.employee_id,e.last_name,m.employee_id mid,m.last_name mname
from employees e,employees m
where e.manager_id = m.employee_id;
角度3
: 内连接 vs 外连接
内连接:合并具有同一列的两个以上的表的行,结果集中不包含一个表与另一个表不匹配的行。
外连接:结果集中除了包含匹配的行,还包含查询到左表或右表不匹配的行。
外连接分类:左外连接,右外连接,满外连接
左外连接:两个表在连接过程中除了返回满足连接条件的行以外,还返回了左表不满足条件的行。
#前面都是内连接
#查询所有员工的last_name,department_name信息 #所有的,使用左外连接
#SQL92实现外连接(内连接如上) ----- MySQL不支持哈哈哈
select employee_id,department_name
from employees e,departments d
where e.department_id = d.department_id(+);
#SQL99 使用join on实现多表的查询,也可以解决外连接
#实现内连接
select last_name,department_name
from employees e inner join departments d
on e.department_id = d.department_id;
select last_name,department_name,city
from employees e join departments d
on e.department_id = d.department_id
join locations l
on d.location_id = l.location_id;
#实现外连接
#左外连接
select last_name,department_name
from employees e left outer join departments d
on e.department_id = d.department_id;
#右外连接
select last_name,department_name
from employees e right join departments d
on e.department_id = d.department_id;
#满外连接 ----- 不支持full
select last_name,department_name
from employees e full join departments d
on e.department_id = d.department_id;
5、UNION
合并查询结果,利用union关键字,可以给出多条select语句,并将他们的结果合成一个结果集。合并时,两个表对应的列数和数据类型必须相同,并且相互对应。各个select语句之间使用union或union all 关键字分隔。
union—返回两个结果集的并集(去除重复记录)
union all —返回两个结果集的并集(不去除重复记录)
但union all 所用的资源少,如果明确知道合并结果集没有重复,就使用union all。
7中SOL JOIN实现
:
# A & B 中图实现:内连接
select employee_id,department_name
from employees e join departments d
on e.department_id = d.department_id;
# 左上图:左外连接
select employee_id,department_name
from employees e left join departments d
on e.department_id = d.department_id;
# 右上图:右外连接
select employee_id,department_name
from employees e right join departments d
on e.department_id = d.department_id;
#左中图
select employee_id,department_name
from employees e left join departments d
on e.department_id = d.department_id
where d.department_id is null;
#右中图
select employee_id,department_name
from employees e right join departments d
on e.department_id = d.department_id
where e.department_id is null;
#左下图:满外连接
#方式1:左上 union all 右中
select employee_id,department_name
from employees e left join departments d
on e.department_id = d.department_id
union all
select employee_id,department_name
from employees e right join departments d
on e.department_id = d.department_id
where e.department_id is null;
#方式1:左中 union all 右上
select employee_id,department_name
from employees e left join departments d
on e.department_id = d.department_id
where d.department_id is null
union all
select employee_id,department_name
from employees e right join departments d
on e.department_id = d.department_id;
#右下图 左中 union all 右中
select employee_id,department_name
from employees e left join departments d
on e.department_id = d.department_id
where d.department_id is null
union all
select employee_id,department_name
from employees e right join departments d
on e.department_id = d.department_id
where e.department_id is null;
SQL99语法新特性
:
自然连接:natural join 表示,我们可以把自然连接理解为SQL92中的等值连接。会帮你自动查询两张连接表中所有相同字段,进行等值连接。
select employee_id, department_name
from employees e join departments d
on e.department_id = d.department_id
and e.manager_id = d.manager_id;
#等价于
select employee_id, department_name
from employees e natural join departments d;
using使用
(结合join):
select employee_id, department_name
from employees e join departments d
on e.department_id = d.department_id;
#等价于
select employee_id, department_name
from employees e join departments d
using (department_id)
6、函数
函数分类为内置函数和自定义函数。
对于不同数据库,函数差别很大。
6.1、 单行函数
特点:操作数据对象、接受参数返回一个结果、只对一行进行变换、每行返回一个结果、可以嵌套、参数可以是一列或一个值 前面都是单行函数
6.1.1、数值函数
# 返回x的绝对值 abs(x)
# 返回x的正负号,正数1,负数-1,0返回0 sign(x)
# 返回圆周率的值 pi()
# 返回大于或等于某个值的最小整数 ceil(x),ceiling(x)
# 返回小于或等于某个值的最大整数 floor(x)
# 返回列表的最小值 LEAST(x1,x2,x3,...)
# 返回列表的最大值 GREATEST(x1,x2,x3,...)
# 返回x除以y的余数 mod(x,y)
# 返回0-1的随机数 RAND()
# 返回0-1的随机数,x为种子 RAND(x)
# 返回一个对x四舍五入最接近x的整数 ROUND(x)
# 返回对x的四舍五入后最接近x的值,保留小数点后y位 ROUND(x,y)
# 返回数字x截断为y位小数的值 TRUNCATE(x,y)
# 返回x的平方根。x为负数时,返回NULL SQRT(x)
SELECT ABS(-3),SIGN(-3),PI(),CEIL(32.23),CEILING(-32.32),FLOOR(-32.32),MOD(13,2),13 MOD 2,13%2
from DUAL
SELECT RAND(),RAND(),(2),RAND(-33),RAND(-33)
from DUAL
SELECT ROUND(3.43434),ROUND(3.43243,4),ROUND(3221.232,-3),TRUNCATE(123.3242342,4),TRUNCATE(1342342.23423,-4),
from DUAL
#单行嵌套
SELECT TRUNCATE(RAND(),3)
from DUAL
#三角函数运算
# 角度转换弧度 RADIANS(x)
# 弧度转化角度 DEGREES(x)
# SIN(x),COS(x),TAN(x),COT(x) ,x都是弧度值
# ASIN(x),ACOS(x),ATAN(X),参数不符合条件的,返回NULL
# ATAN2(m,n) 等价于ATAN(m/n)
# 指数与对数运算
#参数不符合条件的,返回NULL
# POW(x,y),POWER(x,y) 返回x的y次方
# EXP(x) 返回e的x次方
# LN(x),LOG(x) 返回以e为底数的x的对数
# LOG10(x) 返回以10为底数的x的对数
# LOG2(x)
# 进制转换
# BIN(x) 2进制
# HEX(x) 16机制
# OCT(x) 8进制
# CONV(x,f1,f2) 将f1进制的x转换成f2进制
6.1.2、字符串函数
MySQL字符串索引从1开始
# ASCII(s) 返回字符串第一个字符的ASCII码
# CHAR_LENGTH(s) 返回字符串的字符数,等价于CHARACTER_LENGTH(s)
# LENGTH(s) 返回字节数,和字符集有关
SELECT ASCII('dsdfsd'),CHAR_LENGTH('hello'),CHAR_LENGTH('爱你'),LENGTH('hello'),LENGTH('爱你')
from DUAL # 100 5,2,5,6
# CONCAT(s1,s2,...,sn) 连接s1s2,s3,...为一个字符串
# CONCAT_WS(x,s1,s2,...) 字符串之间加上x
SELECT CONCAT('l','o','v','e'),CONCAT_WS('-','h','e','l','l','o')
from DUAL
# INSERT(str,idx,len,re_str) 将字符串str从索引idx开始的len个长度字符换成re_str
SELECT INSERT('helloworld',2,3,'12345')
from DUAL; # h12345oworld
# REPLACE(str,a,b) 将str中的a全部替换成b
SELECT REPLACE('helloworld','w','12345')
from DUAL #hello12345orld
SELECT REPLACE('helloworld','www','12345')
from DUAL #helloworld ,若被替换的不存在,就不替换呗
# UPPER(s) UCASE(s) 所有字符装换成大写字母
# LOWER(s) LCASE(s) 转换成小写字母
SELECT UPPER('sdfsdfDS'),LOWER('cdsdSDF')
from DUAL #SDFSDFDS cdsdsdf
# LEFT(str,n) 返回str最左边的n个
# RIGHT(str,n)
SELECT LEFT('sdfsdfDS',4),RIGHT('cdsdSDF',2)
from DUAL #sdfs DF
# LPAD(str,len,pad) (实现右对齐)用字符串pad对str的最左边进行填充,直到str的长度为len
# RPAD(str,len,pad) (实现左对齐)最右边
SELECT LPAD('sdfsdfDS',15,'ABC'),RPAD('cdsdSDF',15,'abc')
from DUAL #ABCABCAsdfsdfDS cdsdSDFabcabcab
# LTRIM(s) 去掉字符串s左侧的空格
# RTRIM(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码大小,1--前面大,0--相等,-1 -- 后面大
# SUBSTR(s,index,len) 返回字符串s从索引index位置的len个长度的字符 等价于SUBSTRING(s,index,len), MID(s,index,len)
# LOCATE(substr,str) 返回字符串substr在str中首次出现的位置,没找到返回0,等价于 POSITION(substr IN str) INSTR(str,substr)
# ELT(m,s1,s2,s3,..,sn) 返回指定位置的字符串,m=1,返回s1。。。。
# FIELD(s,s1,s2,s3,...) 返回字符串s在字符串列表中第一次出现的位置
# FIND_IN_SET(s1,s2) 返回s1在s2中出现的位置,其中,s2是一个以逗号分隔的字符串
SELECT FIELD('aa','a','aa','aaa','asds','aa') ,FIND_IN_SET('aa','a,aa,aaa,aac,aa')
from DUAL #2 2
#REVERSE(s) 反转
# NULLIF(value1,value2) 比较value1,value2,如果相等,返回NULL 否则返回value1
6.1.3、日期和时间相关函数
① 获取日期、时间
:
# CURDATE(),CURRENT_DATE() 返回当前日期(年月日)
# CURTIME() , CURRENT_TIME() 返回当前时间(时分秒)
# NOW() , SYSDATE(),CURRENT_TIMESTAMP(),LOCATIME(),LOCALTIMESTAMP() 返回当前系统日期和时间
# UTC_DATE() 返回UTC日期
# UTC_TIME() 返回UTC时间
SELECT CURDATE(),CURRENT_DATE(),CURTIME(),CURRENT_TIME(),NOW(),SYSDATE(),UTC_DATE(),UTC_TIME()
FROM DUAL;
#2022-05-27 2022-05-27 11:57:07 11:57:07 2022-05-27 11:57:07 2022-05-27 11:57:07 2022-05-27 03:57:07
② 日期与时间戳的转换
:
# UNIX_TIMESTAMP(),UNIX_TIMESTAMP(datetime) 以unix时间戳的形式返回当前(指定)时间
# FROM_UNIXTIME(timestamp) 将unix时间戳转换成普通格式时间
SELECT UNIX_TIMESTAMP(),UNIX_TIMESTAMP('2022-12-12 11:11:11'),
FROM_UNIXTIME(UNIX_TIMESTAMP('2022-12-12 11:11:11'))
FROM DUAL;
# 1653624025 1670814671 2022-12-12 11:11:11
③ 获取月份,星期,星期数,天数等
:
# YEAR(date),MONTH(date),DAY(date) 返回具体的日期
# HOUR(date),MINUTE(date),SECOND(date) 返回具体的时间
# MONTHNAME(date) 返回月份
# WEEKDAY(date) 返回周几,周一是0,周天是6
# QUARTER(date) 返回日期对应的季度 1-4
# DAYOFYEAR(date) 返回日期是一年中的第几天
# DAYOFMONTH(date) 返回日期是所在月份的第几天
# DAYOFWEEK(date) 返回周几,周日是1,周六是7
SELECT NOW(), YEAR(CURDATE()), MONTH(NOW()), DAY(NOW()), HOUR(NOW()), MINUTE(NOW()),SECOND(NOW())
FROM DUAL;#2022-05-27 12:03:53 2022 5 27 12 3 53
SELECT NOW(), MONTHNAME(NOW()),DAYNAME(NOW()),WEEKNAME(NOW()) WEEKDAY(NOW()), QUARTER(NOW()), DAYOFYEAR(NOW()),DAYOFMONTH(NOW()),DAYOFWEEK(NOW())
FROM DUAL;
#2022-05-27 12:10:07 May Friday 4 2 147 27 6
④ 日期操作函数
:
# EXTRACT(type FROM date) 返回指定日期中特定部分,type是指定返回的值
# type取值及其含义
# MICROSECPND 返回毫秒 ,单词的意思不是微秒(百万分之一秒)吗??
# SECOND,MINUTE,HOUR,DAY,WEEK, MONTH,QUARTER,YAER
#SECOND_MICROSECOND 返回秒和毫秒值
# MINUTE_MICROSECOND, MINUTE_SECOND
# HOUR_MICROSECOND,HOUR_SECOND,HOUR_MINUTE
# DAY_MICROSECOND,DAY_MINUTE,DAY_HOUR,YEAR_MONTH
SELECT NOW(), EXTRACT(MINUTE FROM NOW()),EXTRACT(DAY_SECOND FROM NOW()),EXTRACT(MICROSECOND FROM NOW())
FROM DUAL;
# 2022-05-27 12:13:27 13 121327 0
⑤ 时间和秒,分钟转换函数
:
# TIME_TO_SEC(time) 将time转换成秒
# SEC_TO_TIME(seconds) 将seconds转换成时分秒的时间
SELECT NOW(), TIME_TO_SEC(NOW()),SEC_TO_TIME(TIME_TO_SEC(NOW()))
FROM DUAL;
#2022-05-27 12:17:39 44259 12:17:39
⑥ 计算日期和时间的函数
:
# DATE_ADD(datetime,INTERVAL expr type),ADDDATE(date,INTERVAL expr type) 返回与给定日期datetime相差INTERVAL时间段的日期时间(加上时间段,当然时间段可以写负数)
# DATE_SUB(date,INTERVAL expr type), SUBDATE(date,INTERVAL expr type) 返回与date相差INTERVAL时间间隔的日期(减去时间段)
#其实记住一个就行
#type取值即含义
# HOUR,MINUTE,SECOND,YEAR,YEAR_MONTH,DAY_HOUR,DAY_MINUTE,DAY_SECOND,HOUR_MINUTE,HOUR_SECOND,MINUTE_SECOND
SELECT DATE_ADD(NOW(),INTERVAL 1 DAY),ADDDATE('2022-5-1 11:11:11',INTERVAL 1 SECOND) FROM DUAL;# 2022-05-28 11:47:23 2022-05-01 11:11:12
SELECT DATE_SUB('2022-5-1 11:11:11',INTERVAL 1 DAY),SUBDATE('2022-5-1 11:11:11',INTERVAL '1 2' HOUR_MINUTE) FROM DUAL;#2022-04-30 11:11:11 2022-05-01 10:09:11
# ADDTIME(time1,time2) 返回time1加上time2的时间,当time2为数字时,代表秒数
# SUBTIME(time1,time2) 返回time1减去time2的时间,当time2为数字时,代表秒数
# DATEDIFF(date1,date2) 返回date1-date2的间隔天数
# TIMEDIFF(time1,time2) 返回时间间隔
# FROM_DAYS(N) 返回从0000年1月1日起,N天以后的日期
# TO_DAYS(date) 返回日期距离0000年1月1日的天数
# LAST_DAY(date) 返回date所在月份的最后一天日期
# MAKEDATE(year,n) 针对给定年份和天数返回一个日期
# MAKETIME(hour,minute,second) 将给定的小时,分钟,秒组合成时间并返回
SELECT ADDTIME(NOW(),20),SUBTIME(now(),-20),SUBTIME(NOW(),'1:1:3'),DATEDIFF(NOW(),'2032-11-11'),TIMEDIFF(NOW(),'2023-11-11 11:11:11'),FROM_DAYS(32212),TO_DAYS(NOW()),LAST_DAY(NOW()),MAKEDATE(2022,321),MAKETIME(3,2,1)
FROM DUAL;
#2022-05-27 15:46:04 2022-05-27 15:46:04 2022-05-27 14:44:41 -3821 -838:59:59 0088-03-11 738667 2022-05-31 2022-11-17 03:02:01
# PERIOD_ADD(time,n) 返回time加上n后的时间
SELECT PERIOD_ADD(20220111111111,1)
FROM DUAL;
#20220111111112
⑦ 日期的格式与解析
:
# DATE_FORMAT(date,fmt) 按照字符串fmt格式化日期date
# TIME_FORMAT(time,fmt) 按照字符串fmt格式化时间time
# GET_FORMAT(date_type,format_type) 返回日期字符串的显示格式
GET_FORMAT({DATE|TIME|DATETIME},{'EUR'|'USA'|'JIS'|'ISO'|'INTERNAL'})
# 获取不同的格式
# STR_TO_DATE(str,fmt) 按照字符串fmt对str进行解析,解析为一个日期
#格式符即说明
# %Y 4位数表示年 %y 二位数表示年
# %M 英语表示January %m 二位数字表示月份01,02,03
# %b 缩写的月份Jan %c 数字表示月份 1,2,3
# %D 英文后缀表示月中的天数 (1st,2nd,3rd,...) %d 两位数表示月中天数(01,02,03,...)
# %e 数字形式表示月中天数 1,2,3,4
# %H 两位数表示,24小时制 01 02 %h %I 两位数表示,12小时制 01,02,03
# %k 数字形式的小时,24小时制(1,2,3) %l 数字表示,12小时制 1,2,3
# %i 两位数分钟 %S 和%s 两位数秒
# %W 一周中的星期名称(Sunday) %a 星期缩写(Sun.Mon.)
# %w 数字表示周内天数(0,1,2) %j 三位数表示一年中的天数
# %U 以数字表示一年中的第几周(1,2,3,4),Sunday为第一天 %u 以数字表示一年中的第几周,Monday为第一天
# %T 24小时制 %r 12小时制 %p AM或PM %% 表示%
SELECT DATE_FORMAT(NOW(),'%Y--%b--%D %I::%i::%s')
FROM DUAL;
# 2022--May--27th 04::07::05
SELECT DATE_FORMAT(NOW(),'%Y-%b-%D %W %w %T')
FROM DUAL;
#2022-May-27th Friday 5 16:10:39
SELECT STR_TO_DATE('2022--May--27th 04::07::05','%Y--%b--%D %I::%i::%s')
FROM DUAL;
# 2022-05-27 04:07:05
6.1.4、流程控制函数
#IF(value,value1,value2) #如果value是true,则返回value1,否则返回value2
#IFNULL(value1,value2) #如果value1不为空,就返回value1,否则返回value2
SELECT IF(1>2,'true','false'),IFNULL(NULL,'HELLO')
FROM DUAL;
#false HELLO
#CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 ... ELSE resultn END
SELECT last_name,salary,CASE WHEN salary >= 15000 THEN ' 高高薪'
WHEN salary >= 10000 THEN ' 高薪'
WHEN salary >= 8000 THEN ' 一般'
ELSE ' 穷' END "details"
FROM employees;
#CASE expr WHEN 常量值1 THEN 值1 WHEN 常量值2 TEHN 值2 ... ELSE 值n END
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
WHERE department_id in (10,20,30);
6.1.5、加密和解密函数
#PASSEORD(str) ENCODE(str,s),DECODE(str,s) 8.0弃用
#MD5(str)
#SHA(str)
SELECT MD5('DS'),SHA('DS')
FROM DUAL;
#47b79bd259e22596ffc4be2ffbbe5c5a 2ee0d2dca289c3eb54f4cc5e98db8d63e9b0794b
6.1.6、MySQL信息函数
#VERSION() 返回版本号
#CONNECTION_ID 返回服务器的连接数
#DATABASE(),SCHEMA() 返回所在数据库
#USER(),CURRENT_USER(),SYSTEM_USER(),SESSION_USER() 返回当前连接MySQL的用户名
# CHARSET(value) 返回字符串value自变量的字符集
#COLLATION(value) 返回字符串value的比较规则
select VERSION(),CONNECTION_ID(),USER(),CHARSET('地方'),COLLATION('得到')
FROM DUAL;
#8.0.25 15 root@192.168.3.24 utf8mb4 utf8mb4_0900_ai_ci
6.1.7、其他
6.2、 聚合函数
作用于一组数据,返回一个值
6.2.1、常见的几个聚合函数
# AVG / SUM 适应用数值类型
# MAX / MIN 适用于数值,字符串,日期时间
# COUNT 作用:求指定字段在查询结构中的个数(不包含null);如何计算表中有多少记录?COUNT(*),COUNT(1)
# 方差、标准差、中位数等
SELECT AVG(salary),SUM(salary),MAX(salary),MIN(salary),MAX(last_name),COUNT(salary),COUNT(1)
FROM employees;
SELECT AVG(commission_pct),SUM(commission_pct)/COUNT(commission_pct),SUM(commission_pct)/107
FROM employees;
#0.222857 0.222857 0.072897
6.3、GROUP BY 的使用
#查询各个部门的平均工资。需要分组来查询
SELECT department_id,AVG(salary)
FROM employees
GROUP BY department_id;
#查询不同工种的平均工资
SELECT job_id,AVG(salary)
FROM employees
GROUP BY job_id;
#根据不同部门的不同工种计算平均工资
SELECT department_id,job_id,AVG(salary)
FROM employees
GROUP BY department_id,job_id;
#等价于
SELECT job_id,department_id,AVG(salary)
FROM employees
GROUP BY job_id,department_id;
# 使用WITH ROLLUP 计算整体平均值
SELECT department_id,AVG(salary)
FROM employees
GROUP BY department_id
WITH ROLLUP;
#排序
SELECT department_id,AVG(salary) avg_sal
FROM employees
GROUP BY department_id
ORDER BY avg_sal;
结论:GROUP BY
声明在FROM
后面,WHERE
后面,ORDER BY
前面,LIMIT
前面
6.4、HAVING 的使用
#开发中,使用HAVING的前提是SQL中使用了GROUP BY。否则没什么意义
# 查询各个部门中最高工资比10000高的部门信息
SELECT department_id, MAX(salary)
FROM employees
#WHERE MAX(salary)>10000
#若过滤条件使用到了聚合函数,则必须使用HAVING替换WHERE,并且HAVING必须声明在GROUP BY后面,否则就会报错。
GROUP BY department_id
HAVING MAX(salary)>10000;
#1
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的效率高于2
#当过滤条件包含聚合函数,只能声明在HAVING中
#当过滤条件没有聚合函数,WHERE HAVING都可以,但是建议声明在WHERE
/*
1、从适应范围来说,HAVING适应范围更广;
2、若过滤条件没有聚合条件时,WHERE的效率高于HAVING;
*/
6.5、SQL 底层执行原理
1️⃣ SELECT 语句的完整结构
#SQL92语法
SELECT ...,...,...(存在聚合函数)
FROM ...,...,...
WHERE 多表的连接条件 AND 不包含聚合函数的过滤条件
GROUP BY ...,...
HAVING 包含聚合函数的过滤条件
ORDER BY ...,...(ASC,DESC)
LIMIT ...,...
#SQL99
#执行顺序2
SELECT ...,...,...(存在聚合函数)
#执行顺序1(内部从上往下)
FROM ...(LEFT/RIGHT)JOIN ... ON 多表的连接条件 (LEFT/RIGHT)JOIN ... ON ...
WHERE 不包含聚合函数的过滤条件
GROUP BY ...,...
HAVING 包含聚合函数的过滤条件
#执行顺序3
ORDER BY ...,...(ASC,DESC)
LIMIT ...,...
#总结:在SELECT中,除了GROUP BY,LIMIT之外,其他位置都可以声明子查询
#SQL的执行过程
/* FROM ...,... -> ON -> (LEFT,RIGHT JOIN) -> WHERE -> GROUP BY
-> HAVING -> SELECT -> DISTINCT -> ORDER BY -> LIMIT
*/
#从这可以看出来,GROUP BY 在 WHERE后面,所以WHERE 不能处理聚合函数。
# 在select 中起的别名,也只能在他的后面语句中使用,在WHERE中不能使用,在ORDER BY 中可以使用。
7、子查询
7.1、基本子查询知识
将一个查询语句嵌套在另一个查询语句内部的查询。
SQL子查询的使用大大增强了SELECT查询能力,因为很多时候查询需要从结果集中获取数据,或者需要从同一个表中先计算得出一个数据结果,然后与这个数据结果(标量或集合)进行比较
#需求:谁的工资比Abel高
#1
SELECT salary
FROM employees
WHERE last_name='Abel';
SELECT last_name,salary
FROM employees
WHERE salary>11000;
#2 自连接
SELECT e1.last_name,e1.salary #用e2会是Abel,不是正确答案
FROM employees e1,employees e2
WHERE e2.last_name='Abel' AND e1.salary >e2.salary;
#子查询
SELECT last_name,salary
FROM employees
WHERE salary > (
SELECT salary
FROM employees
WHERE last_name='Abel'
);
外查询(主查询)、内查询(子查询)
内查询在外查询之前一次执行完成;内查询的结果被主查询使用;
注意事项:
内查询要包含在括号里;
将内查询放在比较条件的右侧;
单行操作符对应单行子查询,多行操作符对应多行子查询
7.2、子查询的分类
7.2.1、根据内查询返回结果的条目数
单行子查询、多行子查询;
7.2.1.1、单行子查询
#单行子查询比较操作符
# = , > , >= , < , <= , <> (不等于)
#查询工资大于149号员工的工资的员工信息
SELECT last_name,salary
FROM employees
WHERE salary > (
SELECT salary
FROM employees
WHERE employee_id = 149
);
# 查询job_id与141号员工相同,salary比143号员工多的员工姓名,job_id,salary
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 last_name,job_id,salary
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;
#查询最低工资大于110号部门最低工资的部门id和其最低工资
SELECT department_id,MIN(salary)
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id
HAVING MIN(salary) > (
SELECT MIN(salary)
FROM employees
WHERE department_id = 110
);
#显示员工的employee_id,last_name,location,其中,若员工的department_id与location_id为1800的department_id相同,则locatio为'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;
7.2.1.2、多行子查询
也叫集合比较子查询;内查询返回多行;使用多行比较操作符
#多行比较操作符
IN #等于列表中的任意一个
ANY #需要和单行比较操作符一起使用,和子查询返回的某一个值进行比较
ALL #需要和单行比较操作符一起使用,和子查询返回的所有结果比较
SOME # 实际是ANY的别名
#查询工资等于某个部门的最低工资的员工姓名及id
SELECT employee_id,last_name
FROM employees
WHERE salary IN (
SELECT MIN(salary)
FROM employees
GROUP BY department_id
);
#返回其他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
#聚合函数不能嵌套使用
# 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
) t_dept_avg_sal
);
# 2
SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary) <= ALL(
SELECT AVG(salary)
FROM employees
GROUP BY department_id
);
7.2.2、根据内查询是否执行多次
相关子查询、不相关子查询;
1、相关子查询需求:查询工资大于本部门平均工资的员工信息。
2、不相关子查询需求:查询工资大于本公司平均工资的员工信息。
#前面的例子都给的是不相关子查询需求
#相关子查询
#查询员工工资大于本部门平均工资的员工的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
);
#多表实现
SELECT e.last_name,e.salary,e.department_id
FROM employees e,(
SELECT department_id,AVG(salary) avg_sal
FROM employees
GROUP BY department_id
) t_dept_avg_sal
WHERE e.department_id = t_dept_avg_sal.department_id AND e.salary > t_dept_avg_sal.avg_sal;
#查询员工的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
)ASC;
# 若employees表中employee_id与job_history表中employee_id相同的数目不小于2,输出这些相同id的员工的employee_id,last_name,job_id
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
);
7.3、关键字EXISTS ,NOT EXISTS
关联子查询通常也会和该关键字一起使用,用来检查在子查询中是否存在满足条件的行。
如果在子查询中存在满足条件的行:返回TRUE,否则返回FALSE。
NOT EXISTS关键字表示如果不存在某种条件,则返回TRUE,否则返回FALSE。
#查询公司管理者的employee_id,last_name,job_id,department_id信息
#1自连接
SELECT DISTINCT m.employee_id,m.last_name,m.job_id,m.department_id
FROM employees e JOIN employees m
ON e.manager_id = m.employee_id;
#2 子查询
SELECT DISTINCT employee_id,last_name,job_id,department_id
FROM employees
WHERE employee_id IN(
SELECT DISTINCT manager_id
FROM employees
);
#使用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
);
# 查询departments中,不存在与employees表中的部门的department_id和department_name
#1
SELECT d.department_id,d.department_name
FROM employees e RIGHT JOIN departments d
ON e.department_id = d.department_id
WHERE e.department_id IS NULL;
#2
SELECT department_id,department_name
FROM employees d
WHERE NOT EXISTS(
SELECT *
FROM employees e
WHERE d.department_id = e.department_id
);
7.5、子查询相关题目
#1、查询与Zlotkey相同部门的员工姓名和工资
#2、查询工资比公司平均工资高的员工的员工号、姓名、工资
#3、选择工资大于所有job_id='SA_MAN'的员工的工资的员工的last_name,job_id,salary
#4、查询和姓名中包含字母u的员工在相同部门的员工的员工号和姓名
#5、查询在部门的location_id为1700的部门工作的员工的员工号
#6、查询管理者是King的员工姓名和工资
#7、查询工资最低的员工信息:last—name,salary
#8、查询平均工资最低的部门信息
#9、查询平均工资最低的部门信息和该部门的平均工资(相关子查询)
#10、查询平均工资最高的job信息
#11、查询平均工资高于公司平均工资的部门有哪些
#12、查询出公司中所有manager的详细信息
#13、在各个部门中最高工资中最低的那个部门的最低工资是多少
#14、查询平均工资最高的部门的manager的详细信息:last_name,department_id,email,salary
#15、查询部门的部门号,其中不包括job_id是"ST_CLERK"的部门号
#16、选择所有没有管理者的员工的last_name
#17、查询员工号、姓名、雇佣时间、工资、其中员工的管理者是'De Haan'
#18、查询各部门中工资比本部门平均工资高的员工的员工号,姓名,工资(相关子查询)
#19、查询各个部门下的部门人数大于5的部门编号(相关子查询)
#20、查询各个国家下的部门个数大于2的国家编号(相关子查询)
8、创建、管理数据库
8.1、如何创建数据库
#1
CREATE DATABASE test1;#使用默认字符集创建数据库
#2
CREATE DATABASE test2 CHARACTER SET 'gbk';#显式的指明要创建的数据库字符集
#
CREATE DATABASE IF NOT EXISTS test1 CHARACTER SET 'utf8' ;#若存在,在创建不成功
8.2、如何管理数据库
#查看当前数据库
SHOW DATABASES;
#切换数据库
USE test1;
#查看当前数据库中保存的数据表
SHOW TABLES;
# 查看当前使用的数据库
SELECT DATABASE() FROM DUAL;
#查看指定数据库下保存的数据表
SHOW TABLES FROM mysql;
8.3、 如何修改数据库(一般不修改)
#更改数据库字符集
ALTER DATABASE test2 CHARACTER SET 'utf8';
8.4、如何删除数据库
DROP DATABASE test2;
DROP DATABASE IF EXISTS test2;
9、创建、管理数据表
9.1、如何创建数据表
#先指定数据库
use test1;
##
#1
CREATE TABLE IF NOT EXISTS myemp1(
id int,
emp_name varchar(15),
hire_date date
);
#查看表结构
desc myemp1;
#基于现有表创建表
CREATE TABLE myemp2
AS
SELECT employee_id,last_name,salary
FROM employees; #myemp2中也有数据
#查询语句中的别名可以作为新建表的字段名称,查询语句丰富,可以使用前面讲过的各种SELECT
create table myemp3
as
select e.employee_id emp_id,e.last_name lname,d.department_name
from employees e join departments d
on e.department_id = d.department_id;
#练习1:实现对employees表的复制,包括表数据
create table emp_copy
as
select *
from employees;
#练习2:实现对employees表的复制,不包括表数据
create table emp_copy
as
select *
from employees
where false;
9.2、如何修改数据表
9.2.1、添加字段
ALTER TABLE myemp1
ADD salary DOUBLE(10,2);#默认添加到表中最后一个字段位置
ALTER TABLE myemp1
ADD phone_number VARCHAR(20) FIRST;
ALTER TABLE myemp1
ADD email VARCHAR(40) AFTER phone_number;#指定在某个字段后面
9.2.2、修改字段(数据类型、长度、默认值)
#一般不会修改类型
ALTER TABLE myemp1
MODIFY lname VARCHAR(30);
ALTER TABLE myemp1
MODIFY lname VARCHAR(30) DEFAULT 'DAS';
9.2.3、重命名字段
ALTER TABLE myemp1
CHANGE salary monthly_salary DOUBLE(10,2);
ALTER TABLE myemp1
CHANGE email my_email VARCHAR(50);
9.2.4、删除字段
ALTER TABLE myemp1
DROP COLUMN my_email;
9.3、如何重命名数据表
#1
RENAME TABLE myemp1 TO myemp11;
#2
ALTER TABLE myemp11
RENAME TO myemp12;
9.4、如何删除数据表
删除结构和数据
。
DROP TABLE IF EXISTS myemp12;#IF EXISTS加上判断
9.5、如何清空数据表
只删除数据
。
TRUNCATE TABLE myemp12;
DCL中的COMMIT、ROLLBACK
。
COMMIT:提交数据。一旦执行COMMIT 则数据被永久的保存在了数据库中,意味着数据不能回滚。
ROLLBACK:回滚数据。一旦执行ROLLBACK 则可以实现数据的回滚(不一定成功),回滚到最近一次COMMIT。
对比 TRUNCATE TABLE 和 DELETE TABLE
。
TRUNCATE TABLE:一旦执行此操作,表数据全部清除。同时,数据不可以回滚
DELETE TABLE: 一旦执行此操作,表数据可以全部清除(不带WHERE) 。同时,数据可以实现回滚。
DDL和DML说明
:
DDL的操作一旦执行,就不可以回滚。执行完DDL操作后,一定会执行一次COMMIT。而此COMMIT不受SET autocommit = FALSE
的影响
DML操作默认情况,一旦执行,也是不可以回滚的,但是,如果在执行DML前执行 SET autocommit = FALSE
,则执行的DML操作就可以实现回滚。
SELECT * FROM myemp3;
SET autocommit = FALSE;
DELETE FROM mpemp3;
SELECT * FROM myemp3;
ROLLBACK;
SELECT * FROM myemp3;
SELECT * FROM myemp3;
SET autocommit = FALSE;
TRUNCATE TABLE mpemp3;
SELECT * FROM myemp3;
ROLLBACK;
SELECT * FROM myemp3;
10、添加、管理记录
处理数据时,是可能不成功的,可能是约束造成的,后面介绍。
10.1、添加记录
# 方式1:
## 1.
insert into emp1 values (1,'Tom',2200);#括号里要和表的字段(先后顺序)严格对应。
## 2.
insert into emp1(id,name,salary) values(2,'Jerry',3000)
## 3.
insert into emp1(id,name,salary) values(3,'Jerry',3000),(4,'Tom',2200);
# 一个同时插入多行记录的INSERT语句等同于多个单行插入的insert语句,但是多行的insert语句在处理过程中效率更高。
# 方式2:将查询结果插入表中
insert into emp1 (id,name,salary)
SELECT employee_id,last_name,salary
from employees
where department in (60,70)
#注意字段长度
#values 和value 都可以
10.2、修改记录
# update ... set... where...
update emp1
set salary=1000,name='ssd'
where id = 3;
# 不写where,就是批量修改。
10.3、删除记录
# delete from ... where...
DELETE FROM emp1
WHERE id=1;
小结:DML操作默认情况下,执行完以后都会自动提交数据,可以修改。
10.4、MySQL新特性:计算列
简单来说就是某一列的值是通过别的列计算得来的。例如,a列值为1,b列值为2,c列不需要手动插入,定义为a+b,那么c列就叫做计算列。CREATE TABLE 和 ALTER TABLE 都支持增加计算列。
CREATE TABLE test1(
a INT,
b INT,
c INT GENERATED ALWAYS AS (a+b) VIRTUAL
);#添加数据只需要a,b,不能给c。
11、MySQL中的数据结构
类型 | 举例 |
---|---|
整数类型 | TINYINT,SMALLINT,MEDIUMINT,INT(INTEGER),BIGINT |
浮点类型 | FLOAT,DOUBLE |
定点数类型 | DECIMAL |
位类型 | BIT |
日期时间类型 | YEAR,TIME,DATE,DATETIME,TIMESTAMP |
文本字符串类型 | CHAR,WARCHAR,TINYTEXT,TEXT,MEDIUMTEXT,LONGTEXT |
枚举类型 | ENUM |
集合类型 | SET |
二进制字符串类型 | BINARY,VARBINARY,TINYBLOB,BLOB,MEDIUMBLOB,LONGBLOB |
JSON类型 | JSON对象,JSON数组 |
空间数据类型 | 单值类型:GEOMETRY,POINT,LINESTRING,POLYGON 集合类型:MULTIPOINT,MULTILINESTRING,MULTIPOLYGON,GEOMETRYCOLLECTION |
常见数据类型的属性,如下:
关键字 | 含义 |
---|---|
NULL | 数据列可包含NULL值 |
NOT NULL | 数据列不允许包含NULL值 |
DEFAULT | 默认值 |
AUTO_INCREMENT | 自动递增,适合整数类型 |
UNSIGNED | 无符号 |
CHARACTER SET name | 指定字符集 |
#关于character set name
#创建数据库时指定字符集
create database if not exists dbtest character set 'utf8'
#创建表时指定字符集
create table temp(
id INT
)character set 'utf8';
#创建表的字段时,也可以指定字段的字符集
create table temp1(
id INT,
name VARCHAR(15) CHARATER SET 'UTF8'
);
11.1、整型类型介绍
包括5中,区别如下。
类型 | 字节 | 有符号范围 | 无符号范围 |
---|---|---|---|
TINYINT | 1 | -128-127 | 0-255 |
SMALLINT | 2 | -32768-32767 | 0-65536 |
MEDIUMINT | 3 | -8388608-8388607 | 0-16777215 |
INT,INTEGER | 4 | -2147483648-2147483647 | 0-4294967295 |
BIGINT | 8 | -9223372036854775808-9223372036854775807 | 0-18446744073709551615 |
声明时加unsigned 指定无符号。
可选属性
整数类型的可选属性有三个:
M:表示显示宽度,M的取值范围(0,255)。例如,int(5):当数据宽度小于5位的时候在数字前面需要用字符填满宽度。该项故能需要配合“zerofill”使用,表示用“0”填满宽度,否则指定显示宽度无效。
如果设置了显示宽度,那么插入的数据宽度超过显示限制,会不会截断或插入失败?
不会对插入的数据有任何影响,还是按照类型的实际宽度进行保存,即 显示宽度与类型可以存储的值范围无关 。从MySQL 8.0.17开始,整数数据类型不推荐使用显示宽度属性。
11.2、浮点数类型介绍
浮点数和定点数类型的特点是可以处理小数,你可以把整数看做是小数的一种特例。因此,浮点数和定点数的使用场景,比整数大多了。mysql支持的浮点数类型分别是FLOAT(单精度浮点数),DOUBLE(双精度浮点数),REAL
类型 | 占用字节 | 有符号范围 | 无符号范围 |
---|---|---|---|
FLOAT | 4 | ||
DOUBLE | 8 |
REAL默认就是DOUBLE,如果把sql模式设定为启动“REAL_AS_FLOAT”,那么,mysql就认为REAL是FLOAT. set sql_mode = “REAL_AS_FLOAT”。
FLOAT和DOUBLE区别? FLOAT占用字节数少,取值范围小。
为什么浮点数类型的无符号取值范围只是相当于有符号数取值范围的一半,也就是只相当于有符号取值范围大于0的部分?mysql存储浮点数的格式为:符号(S),尾数(M),阶码(E),因此,无论是否有符号,mysql的浮点数都会存储表示符号的部分。
MySQL允许使用非标准语法(其他数据库未必支持):FLOAT(M,D)或DOUBLE(M,D),这里,M称为精度,D称为标度。(M,D)中M=整数位+小数位,D=小数位。D<=M<=255,0<=D<=30
FLOAT(5,2) #数据范围-999.99-999.99,超过这个范围就会报错
FLOAT(5,2) UNSIGNED #数据范围 0-999.99
不管是否显示的设置了精度(M,D),这里mysql的处理方案是:如果存储时,整数部分超出了范围,mysql就会报错,不允许存这样的值。如果存储时,小数部分若超出范围,分情况1.若四舍五入后,整数部分没有超出范围,则只警告,但能成功操作并四舍五入删除多余的小数位保存。2.若四舍五入后,整数部分超出范围,则保存,解决处理。如FLOAT(5,2)插入999.995就会报错。
从MySQL 8.0.17开始,FLOAT(M,D),DOUBLE(M,D)用法在官方文档已经明确不推荐使用啦,未来可能就移除了,同时对浮点数使用UNSIGNED也不推荐使用了,将来也可能被移除。
对浮点数使用时,有精度的损失,要避免使用‘=’来判断两个数是否相等。
11.3、定点数类型
类型 | 占用字节 | 含义 |
---|---|---|
DECIMAL(M,D),DEC,NUMERIC | M+2字节 | 有效范围由M,D决定 |
- 使用DECIMAL(M,D)的方式表示高精度小数。其中M被称为精度,D被称为标度。0<=M<=65,0<=D<=30,D<M
- DECIMAL(M,D)的最大取值范围与DOUBLE类型一样,但是有效的数据范围是由M,D决定的。DECIMAL的存储空间并不是固定的,由精度决定,换句话说,在一些对精度要求不高的场景下,比起占用同样字节长度的定点数,浮点数表达的数据范围可以更大一些。
- 定点数在mysql内部以字符串形式进行存储,这决定了他一定是精准的。
- 当DECIMAL类型不指定精度和标度时,默认为DECIMAL(10,0).当数据的精度超过指定范围时,同样会进行四舍五入处理。
对数据要求精确,就使用DECIMAL
11.4、位类型
类型 | 长度 | 长度范围 | 占用空间 |
---|---|---|---|
BIT | M | 1<=M<=64 | 约为(M+7)/8个字节 |
BIT类型,如果没有指定M,则默认为1,这个1位表示只能存1位的二进制值。M表示可存储的二进制位数,位数最小为1,最大值为64。
11.5、时间与日期类型
MySQL有多种表示日期和时间的数据类型,不同的版本可能有所差异,MySQL8.0版本支持的日期和时间类型主要有:YEAR、TIME、DATE、DATETIME 、TIMESTAMP
类型。
- YEAR 类型通常用来表示年
- DATE 类型通常用来表示年、月、日
- TIME 类型通常用来表示时、分、秒
- DATETIME 类型通常用来表示年、月、日、时、分、秒
- TIMESTAMP 类型通常用来表示带时区的年、月、日、时、分、秒
类型 | 名称 | 字节 | 日期格式 | 最小值 | 最大值 |
---|---|---|---|---|---|
YEAR | 年 | 1 | YYYY或YY | 1901 | 2155 |
TIME | 时间 | 3 | HH:MM:SS | -838:59:59 | 838:59:59 |
DATE | 日期 | 3 | YYYY-MM-DD | 1000-01-01 | 9999-12-03 |
DATETIME | 日期时间 | 8 | YYYY-MM-DD HH:MM:SS | 1000-01-01 00:00:00 | 9999-12-31 23:59:59 |
TIMESTAMP | 日期时间 | 4 | YYYY-MM-DD HH:MM:SS | 1970-01-01 00:00:00 UTC | 2038-01-19 03:14:07 UTC |
YEAR类型
在MySQL中,YEAR有以下几种存储格式:
-
以4位字符串或数字格式表示YEAR类型,其格式为YYYY,最小值为1901,最大值为2155。
-
以2位字符串格式表示YEAR类型,最小值为00,最大值为99。
当取值为01到69时,表示2001到2069; 当取值为70到99时,表示1970到1999; 当取值整数的0或00添加的话,那么是0000年; 当取值是日期/字符串的'0'添加的话,是2000年。
-
从MySQL5.5.27开始,2位格式的YEAR已经不推荐使用。YEAR默认格式就是“YYYY”,没必要写成YEAR(4),
-
从MySQL 8.0.19开始,不推荐使用指定显示宽度的YEAR(4)数据类型。
DATE类型
DATE类型表示日期,没有时间部分,格式为 YYYY-MM-DD ,其中,YYYY表示年份,MM表示月份,DD表示日期。需要 3个字节 的存储空间。在向DATE类型的字段插入数据时,同样需要满足一定的格式条件。
1. 以 YYYY-MM-DD 格式或者 YYYYMMDD 格式表示的字符串日期,其最小取值为1000-01-01,最大取值为9999-12-03。YYYYMMDD格式会被转化为YYYY-MM-DD格式。
2. 以 YY-MM-DD 格式或者 YYMMDD 格式表示的字符串日期,此格式中,年份为两位数值或字符串满足YEAR类型的格式条件为:当年份取值为00到69时,会被转化为2000到2069;当年份取值为70到99时,会被转化为1970到1999。
3. 使用 CURRENT_DATE() 或CURDATE()或者 NOW() 函数,会插入当前系统的日期。
就会第一种就行。
TIME类型
-
TIME类型用来表示时间,不包含日期部分。在MySQL中,需要 3个字节 的存储空间来存储TIME类型的数据,可以使用“HH:MM:SS”格式来表示TIME类型,其中,HH表示小时,MM表示分钟,SS表示秒。
-
在MySQL中,向TIME类型的字段插入数据时,也可以使用几种不同的格式。
(1)可以使用带有冒号的字符串,比如' D HH:MM:SS' 、' HH:MM:SS '、' HH:MM '、' D HH:MM '、' D HH '或' SS '格式,都能被正确地插入TIME类型的字段中。其中D表示天,其最小值为0,最大值为34。如果使用带有D格式的字符串插入TIME类型的字段时,D会被转化为小时,计算格式为D*24+HH。当使用带有冒号并且不带D的字符串表示时间时,表示当天的时间,比如12:10表示12:10:00,而不是00:12:10。 (2)可以使用不带有冒号的字符串或者数字,格式为' HHMMSS '或者 HHMMSS 。如果插入一个不合法的字符串或者数字,MySQL在存储数据时,会将其自动转化为00:00:00进行存储。比如1210,MySQL会将最右边的两位解析成秒,表示00:12:10,而不是12:10:00。 (3)使用 CURRENT_TIME() 或者 NOW() ,会插入当前系统的时间。
DATETIME类型
DATETIME类型在所有的日期时间类型中占用的存储空间最大,总共需要 8 个字节的存储空间。在格式上为DATE类型和TIME类型的组合,可以表示为 YYYY-MM-DD HH:MM:SS ,其中YYYY表示年份,MM表示月份,DD表示日期,HH表示小时,MM表示分钟,SS表示秒。
-
在向DATETIME类型的字段插入数据时,同样需要满足一定的格式条件。
1. 以 YYYY-MM-DD HH:MM:SS 格式或者 YYYYMMDDHHMMSS 格式的字符串插入DATETIME类型的字段时,最小值为1000-01-01 00:00:00,最大值为9999-12-03 23:59:59。(以YYYYMMDDHHMMSS格式的数字插入DATETIME类型的字段时,会被转化为YYYY-MM-DD HH:MM:SS格式)。 2. 以 YY-MM-DD HH:MM:SS 格式或者 YYMMDDHHMMSS 格式的字符串插入DATETIME类型的字段时,两位数的年份规则符合YEAR类型的规则,00到69表示2000到2069;70到99表示1970到1999。 3. 使用函数 CURRENT_TIMESTAMP() 和 NOW() ,可以向DATETIME类型的字段插入系统的当前日期和时间。
TIMESTAMP类型
TIMESTAMP类型也可以表示日期时间,其显示格式与DATETIME类型相同,都是 YYYY-MM-DD HH:MM:SS ,需要4个字节的存储空间。但是TIMESTAMP存储的时间范围比DATETIME要小很多,只能存储“1970-01-01 00:00:01 UTC”到“2038-01-19 03:14:07 UTC”之间的时间。其中,UTC表示世界统一时间,也叫作世界标准时间。
- 存储数据的时候需要对当前时间所在的时区进行转换,查询数据的时候再将时间转换回当前的时区。因此,使用TIMESTAMP存储的同一个时间值,在不同的时区查询时会显示不同的时间。
向TIMESTAMP类型的字段插入数据时,当插入的数据格式满足YY-MM-DD HH:MM:SS和YYMMDDHHMMSS时,两位数值的年份同样符合YEAR类型的规则条件,只不过表示的时间范围要小很多。
如果向TIMESTAMP类型的字段插入的时间超出了TIMESTAMP类型的范围,则MySQL会抛出错误信息。
开发经验
用得最多的日期时间类型,就是 DATETIME 。虽然 MySQL 也支持 YEAR(年)、 TIME(时间)、DATE(日期),以及 TIMESTAMP 类型,但是在实际项目中,尽量用 DATETIME 类型。因为这个数据类型包括了完整的日期和时间信息,取值范围也最大,使用起来比较方便。毕竟,如果日期时间信息分散在好几个字段,很不容易记,而且查询的时候,SQL 语句也会更加复杂。
此外,一般存注册时间、商品发布时间等,不建议使用DATETIME存储,而是使用 时间戳
,因为DATETIME虽然直观,但不便于计算。
11.6、文本字符串类型
MySQL中,文本字符串总体上分为 CHAR 、 VARCHAR 、 TINYTEXT 、 TEXT 、 MEDIUMTEXT 、LONGTEXT 、 ENUM 、 SET
等类型。
CHAR和VARCHAR
CHAR和VARCHAR类型都可以存储比较短的字符串。
字符串(文本)类型 | 特点 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|---|
CHAR(M) | 固定长度 | M | 0 <= M <= 255 | M个字节 |
VARCHAR(M) | 可变长度 | M | 0 <= M <= 65535 | (实际长度 + 1) 个字节 |
CHAR类型
:
- CHAR(M) 类型一般需要预先定义字符串长度。如果不指定(M),则表示长度默认是1个字符。
- 如果保存时,数据的实际长度比CHAR类型声明的长度小,则会在 右侧填充 空格以达到指定的长度。当MySQL检索CHAR类型的数据时,CHAR类型的字段会去除尾部的空格。
- 定义CHAR类型字段时,声明的字段长度即为CHAR类型字段所占的存储空间的字节数。
VARCHAR类型
: - VARCHAR(M) 定义时, 必须指定 长度M,否则报错。
- MySQL4.0版本以下,varchar(20):指的是20字节,如果存放UTF8汉字时,只能存6个(每个汉字3字节) ;MySQL5.0版本以上,varchar(20):指的是20字符。
- 检索VARCHAR类型的字段数据时,会保留数据尾部的空格。VARCHAR类型的字段所占用的存储空间为字符串实际长度加1个字节。
CHAR和VARCHAR比较
类型 | 特点 | 空间上 | 时间上 | 适用场景 |
---|---|---|---|---|
CHAR(M) | 固定长度 | 浪费存储空间 | 效率高 | 存储不大,速度要求高 |
VARCHAR(M) | 可变长度 | 节省存储空间 | 效率低 | 非CHAR的情况 |
- MyISAM 数据存储引擎和数据列:MyISAM数据表,最好使用固定长度(CHAR)的数据列代替可变长度(VARCHAR)的数据列。这样使得整个表静态化,从而使 数据检索更快 ,用空间换时间。
- MEMORY 存储引擎和数据列:MEMORY数据表目前都使用固定长度的数据行存储,因此无论使用CHAR或VARCHAR列都没有关系,两者都是作为CHAR类型处理的。
- InnoDB 存储引擎,建议使用VARCHAR类型。因为对于InnoDB数据表,内部的行存储格式并没有区分固定长度和可变长度列(所有数据行都使用指向数据列值的头指针),而且主要影响性能的因素是数据行使用的存储总量,由于char平均占用的空间多于varchar,所以除了简短并且固定长度的,其他考虑varchar。这样节省空间,对磁盘I/O和数据存储总量比较好。
11.7、TEST类型
在MySQL中,TEXT用来保存文本类型的字符串,总共包含4种类型,分别为TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT
类型。
在向TEXT类型的字段保存和查询数据时,系统自动按照实际长度存储,不需要预先定义长度。这一点和VARCHAR类型相同。
文本字符串类型 | 特点 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|---|
TINYTEXT | 小文本、可变长度 | L | 0 <= L <= 255 | L + 2 个字节 |
TEXT | 文本、可变长度 | L | 0 <= L <= 65535 | L + 2 个字节 |
MEDIUMTEXT | 中等文本、可变长度 | L | 0 <= L <= 16777215 | L + 3 个字节 |
LONGTEXT | 大文本、可变长度 | L | 0 <= L<= 4294967295(相当于4GB) | L + 4 个字节 |
由于实际存储的长度不确定,MySQL 不允许
TEXT 类型的字段做主键
。遇到这种情况,你只能采用CHAR(M)或者 VARCHAR(M)。
经验
:
TEXT文本类型,可以存比较大的文本段,搜索速度稍慢,因此如果不是特别大的内容,建议使用CHAR,VARCHAR来代替。还有TEXT类型不用加默认值,加了也没用。而且text和blob类型的数据删除后容易导致“空洞”,使得文件碎片比较多,所以频繁使用的表不建议包含TEXT类型字段,建议单独分出去,单独用一个表。
11.8、ENUM类型
ENUM类型也叫作枚举类型,ENUM类型的取值范围需要在定义字段时进行指定。设置字段值时,ENUM类型只允许从成员中选取单个值,不能一次选取多个值。其所需要的存储空间由定义ENUM类型时指定的成员个数决定。
文本字符串类型 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|
ENUM | L | 1 <= L <= 65535 | 1或2个字节 |
- 当ENUM类型包含1~255个成员时,需要1个字节的存储空间;
- 当ENUM类型包含256~65535个成员时,需要2个字节的存储空间。
- ENUM类型的成员个数的上限为65535个。
CREATE TABLE test_enum(
season ENUM('春','夏','秋','冬','unknow')
);
INSERT INTO test_enum VALUES('春');
INSERT INTO test_enum VALUES('UNKNOW');#不区分大小写
INSERT INTO test_enum VALUES(1),('3');
11.9、SET类型
SET表示一个字符串对象,可以包含0个或多个成员,但是成员个数上限为64。
成员个数范围(L表示实际成员个数) | 占用的存储空间 |
---|---|
1 <= L <= 8 | 1个字节 |
9 <= L <= 16 | 2个字节 |
17 <= L <= 24 | 3个字节 |
25 <= L <= 32 | 4个字节 |
33 <= L <= 64 | 8个字节 |
SET类型在存储数据时成员个数越多,其占用的存储空间越大。注意:SET类型在选取成员时,可以一次选择多个成员,这一点与ENUM类型不同。
CREATE TABLE test_set(
s SET ('A', 'B', 'C')
);
INSERT INTO test_set (s) VALUES ('A'), ('A,B');
#插入重复的SET类型成员时,MySQL会自动删除重复的成员
INSERT INTO test_set (s) VALUES ('A,B,C,A');
#向SET类型的字段插入SET成员中不存在的值时,MySQL会抛出错误。
INSERT INTO test_set (s) VALUES ('A,B,C,D');
11.10、二进制字符串类型
MySQL中的二进制字符串类型主要存储一些二进制数据,比如可以存储图片、音频和视频等二进制数据。
MySQL中支持的二进制字符串类型主要包括BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB 和LONGBLOB
类型。
- BINARY和VARBINARY类似于CHAR和VARCHAR,只是它们存储的是二进制字符串。
- BINARY (M)为固定长度的二进制字符串,M表示最多能存储的字节数,取值范围是0~255个字符。如果未指定(M),表示只能存储 1个字节 。例如BINARY (8),表示最多能存储8个字节,如果字段值不足(M)个字
节,将在右边填充’\0’以补齐指定长度。 - VARBINARY (M)为可变长度的二进制字符串,M表示最多能存储的字节数,总字节数不能超过行的字节长度限制65535,另外还要考虑额外字节开销,VARBINARY类型的数据除了存储数据本身外,还需要1或2个字节来存储数据的字节数。VARBINARY类型 必须指定(M) ,否则报错。
二进制字符串类型 | 特点 | 值的长度 | 占用空间 |
---|---|---|---|
BINARY(M) | 固定长度 | M | (0 <= M <= 255) |
VARBINARY(M) | 可变长度 | M | (0 <= M <= 65535) |
11.11、BLOB类型
BLOB是一个 二进制大对象 ,可以容纳可变数量的数据。
MySQL中的BLOB类型包括TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB 4种类型,它们可容纳值的最大长度不同。可以存储一个二进制的大对象,比如 图片 、 音频 和 视频 等。
需要注意的是,在实际工作中,往往不会在MySQL数据库中使用BLOB类型存储大对象数据,通常会将图片、音频和视频文件存储到 服务器的磁盘上 ,并将图片、音频和视频的访问路径存储到MySQL中。
二进制字符串类型 | 值的长度 | 长度范围 | 占用空间 |
---|---|---|---|
TINYBLOB | L | 0 <= L <= 255 | L + 1 个字节 |
BLOB | L | 0 <= L <= 65535(相当于64KB) | L + 2 个字节 |
MEDIUMBLOB | L | 0 <= L <= 16777215 (相当于16MB) | L + 3 个字节 |
LONGBLOB | L | 0 <= L <= 4294967295(相当于4GB) | L + 4 个字节 |
11.12、JSON类型
JSON(JavaScript Object Notation)是一种轻量级的 数据交换格式 。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。它易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效
率。JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在网络或者程序之间轻松地传递这个字符串,并在需要的时候将它还原为各编程语言所支持的数据格式。
在MySQL 5.7中,就已经支持JSON数据类型。在MySQL 8.x版本中,JSON类型提供了可以进行自动验证的JSON文档和优化的存储结构,使得在MySQL中存储和读取JSON类型的数据更加方便和高效。 创建数据表,表中包含一个JSON类型的字段 js 。
11.13、数据类型建议
在定义数据类型时,如果是整数,就用int,如果是小数,一定用定点类型DECIMAL(M,D);如果是日期和时间,就用DATETIME。
这样做的好处是,首先确保你的系统不会因为数据类型定义出错。不过,凡事都是有两面的,可靠性好,并不意味着高效。比如,TEXT 虽然使用方便,但是效率不如 CHAR(M) 和 VARCHAR(M)。
阿里巴巴《Java开发手册》之MySQL数据库:
- 任何字段如果为非负数,必须是 UNSIGNED
- 【 强制 】小数类型为 DECIMAL,禁止使用 FLOAT 和 DOUBLE。
说明:在存储的时候,FLOAT 和 DOUBLE 都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过 DECIMAL 的范围,建议将数据拆成整数和小数并分开存储。 - 【 强制 】如果存储的字符串长度几乎相等,使用 CHAR 定长字符串类型。
- 【 强制 】VARCHAR 是可变长字符串,不预先分配存储空间,长度不要超过 5000。如果存储长度大于此值,定义字段类型为 TEXT,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
12、约束
12.1、为什么需要约束?数据完整性!!!
数据完整性是指数据的精确性和可靠性。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信息而提出的。为了保证数据的完整性,SQL规范以约束的方式对表数据进行额外的条件限制。从以下四个方面考虑:
1.实体完整性:同一个表,不能存在两条完全相同无法区分的记录。
2.域完整性:例如,年龄范围0-120.
3.引用完整性:例如,员工所在部分,在部门表中要可以找的到。
4.用户自定义完整性:用户名唯一,密码不能为空。
什么叫约束?
对字段的限制
。
12.2、约束的分类
角度1:约束的字段个数
单列约束 vs 多列约束
角度2:约束的作用范围
列级约束(将此约束声明在对应字段的后面)vs 表级约束(在表中所有字段都声明完,在所有字段的后面声明的约束)
角度3:约束的作用或功能
1. 非空约束 not null
2. 唯一性约束 unique
3. 主键约束 primary key
4. 外键约束 foreign key
5. 检查约束 check
6. 默认值约束 default
12.3、如何查看表中的约束?
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名称';
12.4、如何添加、删除约束?
CREATE TABLE
时添加约束
ALTER TABLE
时增加约束、删除约束。
12.4.1、非空约束 not null
影响添加和修改记录
CREATE TABLE test1(
id INT NOT NULL,
#1
last_name VARCHAR(15) NOT NULL,
email VARCHAR(15)
);
INSERT INTO test1(id,last_name,email) values(1,'ds','dsf');
INSERT INTO test1(id,last_name,email) values(1,NULL,'dsf');#错误
#2
ALTER TABLE test1 MODIFY email VARCHAR(23) NOT NULL;#如果已经存在记录的email为空,会失败。
12.4.2、唯一性约束 unique
用来限制某个字段/某列的值不能重复。唯一约束,允许出现多个NULL值。关键字 UNIQUE
特点:
同一个表可以有多个唯一性约束
唯一性约束可以是某一列的值唯一,也可以是多个列的组合的值唯一
唯一性约束允许列值为空
在创建唯一性约束的时候,如果不给唯一性约束命名,就默认和列名相同
mysql会给唯一性约束默认创建一个唯一索引。
CREATE TABLE test2(
id INT UNIQUE,#列级约束
last_name VARCHAR(15),
email VARCHAR(25),
#表级约束 可以指定约束名
CONSTRAINT uk_test2_email UNIQUE(email) # UNIQUE(email)
);
#修改时添加唯一性约束,若已经有不满足唯一性约束的记录,会失败。
#1
ALTER TABLE 表名称 ADD UNIQUE KEY(字段列表);
ALTER TABLE test2 ADD UNIQUE(email);
#2
ALTER TABLE 表名称 MODIFY 字段名 类型 UNIQUE;
ALTER TABLE test2 MODIFY last_name VARCHAR(15) UNIQUE;
#复合的唯一性约束
CREATE TABLE user(
id INT,
name VARCHAR(13),
password VARCHAR(24),
CONSTRAINT uk_user_name_pwd UNIQUE(name,password)
);
#删除唯一性约束
-- 添加唯一性约束的列上会自动创建唯一索引
-- 删除唯一性约束只能通过删除唯一性索引的方式删除
-- 删除时需要指定唯一索引名,唯一索引名就和唯一约束名一样
-- 如果创建唯一约束时未指定名称,如果是单列,默认和列名相同,如果是组合列,默认就和第一个列相同,也可以自定义唯一性约束名。
ALTER TABLE test2 DROP INDEX last_name;
12.4.3、主键约束 primary key
作用:用来唯一表示表中一行记录
关键字 primary key
特点: 主键约束相当于唯一约束+非空约束的组合,主键约束不允许重复和空值。
一个表最多只能有一个主键约束,建立主键约束可以在列级,也可以在表级别创建。
主键约束对应着表中的一列或者多列(复合主键)
如果是多列组合的复合主键约束,那么这些列都不允许为空,且组合值不允许重复。
mysql的主键名总是PRIMARY,就算自己命名了主键约束也没有用。
当创建主键约束时,系统默认会在所在的列或列组合上建立对应的主键索引(能够根据主键查询的,就根据主键查询,效率高),如果删除主键约束,主键约束对应的索引也就自动删除了。
需要注意的一点是,不要修改主键字段的值。因为主键时数据记录的唯一表示,修改了主键的值,就会破坏数据的完整性。
CREATE TABLE test4(
id INT PRIMARY KEY,#列级约束
last_name VARCHAR(14)
);
CREATE TABLE test5(
id INT,
last_name,
PRIMARY KEY(id)#表级约束
);
#ALTER添加
CREATE TABLE test6(
id INT,
last_name,
);
ALTER TABLE test6 ADD PRIMARY KEY (id);
复合主键
create table 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
primary key(字段名1,字段名2) #表示字段1和字段2的组合是唯一的,也可以有更多个字段
);
删除主键约束(实际开发中,根本不会去删除主键约束)
ALTER TABLE test6 DROP PRIMARY KEY;
12.4.3.1、自增列
关键字:AUTO_INCREMENT
作用:某个字段自增
特点和要求:
- 一个表最多只能有一个自增长列
- 当需要产生唯一标识符或顺序值时,可设置自增长
- 自增长约束的列必须是键列(主键列,唯一键列)
- 自增约束的列的数据类型必须是整数类型
- 如果自增列指定了0和null,会在当前最大值基础上自增;如果自增列手动指定了其他具体值,直接赋值为具体指。
CREATE TABLE test7(
id INT PRIMARY KEY AUTO_INCREMENT
last_name VARCHAR(25)
);
INSERT INTO test7(last_name) values('tom');
CREATE TABLE test8(
id INT PRIMARY KEY
last_name VARCHAR(25)
);
#添加自增
ALTER TABLE test8 MODIFY id INT AUTO_INCREMENT;
#删除自增
ALTER TABLE test8 MODIFY id INT;
mysql8.0的新特性----自增变量的持久化
在8.0前,自增主键的值如果大于max(primary key)+1,在mysql重启后,hi重置自增主键等于max(primary key)+1,这种现象在某些情况下会导致业务主键冲突或其他难以发现的问题。但8.0后就不会有这个问题了。MySQL 8.0将自增主键的计数器持久化到 重做日志 中。每次计数器发生改变,都会将其写入重做日志中。如果数据库重启,InnoDB会根据重做日志中的信息来初始化计数器的内存值。
12.4.4、外键约束 foreign key
作用:限定某个表的某个字段的引用完整性。
特点:
- 从表的外键列,必须应用/参考主表的
主键或唯一性约束列
。为什么?因为被依赖的/被参考的值必须是唯一的。 - 在创建外键约束时,如果不给外键约束命名,默认名不是列名,而是自动产生一个外键名,也可以指定外键约束名。
- 创建表时就指定外键约束的话,先创建主表,在创建从表。
- 删除表时,先删除从表(或先删除外键约束),再删主表。
- 当主表记录被从表参考时,主表的记录将不允许删除,如果要删除数据,需要先删除从表中依赖该记录的数据,然后才可以删除主表的数据。
- 在从表中指定外键约束,并且一个表可以建立多个外键约束。
- 从表的外键列与主表被参考的列名称可以不相同,但是数据类型必须要一样,逻辑意义要一致。如果类型不一样,创建从表时,会出现错误:ERROR 1005
- 当创建外键约束时,系统默认会在所在列上建立对应的普通索引,但是索引名不是列名,不是外键约束名。
- 删除外键约束后,必须手动删除对应的索引。
#在CREATE TABLE 中添加
#主表和从表
#先创建主表
CREATE TABLE dept1(
dept_id INT ,
dept_name VARCHAR(15)
);
#再创建从表
CREATE TABLE emp1(
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(15),
department_id INT,
##表级约束
CONSTRAINT fk_emp1_dept_id FOREIGN KEY (department_id) REFERENCES dept1(dept_id)
);
# 在ALTER TABLE 中添加外键约束(注销上面的表级约束)
ALTER TABLE emp1 ADD CONSTRAINT fk_emp1_dept_id FOREIGN KEY (department_id) REFERENCES dept1(dept_id)
#会报错,因为主表的dept_id上没有主键约束或唯一性约束
ALTER TABLE dept1
ADD PRIMARY KEY (dept_id);#执行完,在创建从表
#直接添加从表数据会报错,因为外键约束,必须先添加主表数据,在添加从表约束(外键约束的数据必须在主表中存在了)。此时不能删除主表的被关联的数据。
总结
:
添加了外键约束后,主表的修改和删除会受到约束。
添加了外键约束后,从表的修改和添加受到了约束。
在从表上建立外键,要求主表必须存在。
删除主表时,要求先删除从表,或将外键约束先删除。
约束等级
:
Cascade方式:在主表上update/delete记录时,同步到子表的匹配记录。
Set null 方式:在主表上update/delete记录时,将子表上匹配记录的列设为null,但是要注意子表的外键列不能为not null
No action方式:如果子表中有匹配记录,则不允许对主表对应候选键进行update/delete操作。
Restrict方式:同no action,都是立即检查外键约束。
Set default方式:主表有变更时,子表将外键列设置成一个默认的值,但是innodb不能识别。
如果没有指定等级,就相当于是restrict方式。
对于外键约束,最好采用: ON UPDATE CASCADE ON DELETE RESTRICT
create table dept(
did int primary key, #部门编号
dname varchar(50) #部门名称
);
create table emp(
eid int primary key, #员工编号
ename varchar(5), #员工姓名
deptid int, #员工所在的部门
foreign key (deptid) references dept(did) on update cascade on delete set null
#把修改操作设置为级联修改等级,把删除操作设置为set null等级
);
删除外键约束
:
#(1)第一步先查看约束名和删除外键约束
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名称';#查看某个表的约束名
ALTER TABLE 从表名 DROP FOREIGN KEY 外键约束名;
#(2)第二步查看索引名和删除索引。(注意,只能手动删除)
SHOW INDEX FROM 表名称; #查看某个表的索引名
ALTER TABLE 从表名 DROP INDEX 索引名;
#例子
SELECT * FROM information_schema.table_constraints WHERE table_name = 'emp1';
ALTER TABLE emp1 DROP FOREIGN KEY fk_emp1_dept_id;
SHOW INDEX FROM emp1;
ALTER TABLE emp1 DROP INDEX fk_emp1_dept_id;
开发场景
问题1:如果两个表有关系(一对一,一对多),他们之间是否一定要建外键约束? 不是
问题2:建和不建外键约束有什么区别?
建立外键约束,操作(创建表,删除表,添加,更新,删除)会受到限制,从语法层面受到限制。不建立外键约束,操作不受限制,要保证数据完整性,只能依靠程序员自觉或者是在java程序中进行限定。
问题3:那么建和不建外键约束和查询有没有关系?没有
在 MySQL 里,外键约束是有成本的,需要消耗系统资源。对于大并发的 SQL 操作,有可能会不适合。比如大型网站的中央数据库,可能会 因为外键约束的系统开销而变得非常慢 。所以, MySQL 允许你不使用系统自带的外键约束,在 应用层面 完成检查数据一致性的逻辑。也就是说,即使你不用外键约束,也要想办法通过应用层面的附加逻辑,来实现外键约束的功能,确保数据的一致性。
阿里开发规范
:
【 强制 】不得使用外键与级联,一切外键概念必须在应用层解决
。
说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于 单机低并发 ,不适合 分布式 、 高并发集群 ;级联更新是强阻塞,存在数据库 更新风暴 的风险;外键影响数据库的 插入速度 。
12.4.5、检查约束 check
作用:检查某个字段的值是否符合要求,一般指的是范围。
关键字:check
说明:mysql5.7的检查约束对数据验证没有任何作用,8.0可以使用检查约束。
CREATE TABLE test10(
id INT,
last_name VARCHAR(15),
salary DECIMAL(10,2) CHECK(salary > 2000)
);
12.4.6、默认值约束 default
作用:给某个字段/某列指定默认值,一旦设置了默认值,在插入数据时,如果此字段没有显式的赋值,就赋值默认值。
#1
CREATE TABLE test11(
id INT,
last_name VARCHAR(15),
salary DECIMAL(10,2) DEFAULT 2000
);
CREATE TABLE test11(
id INT,
last_name VARCHAR(15),
salary DECIMAL(10,2) DEFAULT 2000
);
#2
ALTER TABLE test11 MODIFY salary DECIMAL(10,2) DEFAULT 2000
删除默认值约束
alter table 表名称 modify 字段名 数据类型 ;#删除默认值约束,也不保留非空约束
alter table 表名称 modify 字段名 数据类型 not null; #删除默认值约束,保留非空约束
12.4.7、面试
1. 为什么建表时,加not null default'' 或 default 0 ?
答: 不想让表中出现null值。
2. 为什么不想要null值?
答:不好比较,null是一种特殊值,比较时只能用专门的is null 和 is not null来比较,碰到运算符,通常返回null值。效率不高,影响提高索引效果。
3. 带AUTO_INCREMENT约束的字段值是从1开始的吗?
答: 默认是从1开始的,但是插入记录的时候也可以指定。
4. 并不是每个表都可以任意选择存储引擎?
答:外键约束不能跨引擎使用。mysql支持多种存储引擎,每个表都可以指定一个不用的存储引擎,需要注意的是:外键约束是用来保证数据的参照完整性的,如果表之间需要关联外键,却指定了不同的存储引擎,那么这些表之间不能创建外键约束。所以说,存储引擎的选择也不是完全随意的。
13、视图
常见的数据库对象
13.1、视图的理解
自己理解:相当于对于一个表,对于一些字段不想让不相关的人看到,就需要将表的一部分字段展现出来。同时对这些字段的改变同时也需要改变原始的数据,所以并不能备份一部分出来在上做操作,就可以使用视图。
视图只是一种虚拟表,本身不具备数据,占用很少的内存,是SQL中的一个重要概念。
视图建立在已有表的基础上,视图赖以建立的这些表称为基表。
视图的创建与删除只影响视图本身,不影响对应的基表。但是当对视图中的数据进行增加,删除,修改操作时,数据表中的数据会响应的发生变化,反之亦然。
向视图提供数据内如的语句为select语句,可以将视图理解为存储起来的select语句:咋数据库中,视图不会保存数据,数据真正保存在数据表中。党对视图中的数据进行增加,删除,修改操作时,数据表的会相应变化,反之亦然。
视图是向用户提供基表数据的另一种表现形式。通常情况下,小型项目的数据库可以不使用视图,但是在大型项目中,以及数据表比较复杂的情况下,视图的价值就凸显出来了,他可以帮助我们把经常查询的结果集放到虚拟表中,提升使用效率。理解和使用起来就非常方便。
视图优点:简化查询,控制数据的访问等。
13.2、视图的创建
在CREATE VIEW 语句中嵌入子查询
CREATE [OR REPLACE]
[ALGOPITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW 视图名称 [(字段列表)]
AS 查询语句
[WITH [CASCADED|LOCAL] CHECK OPTION]
#简化版
CREATE VIEW 视图名称
AS 查询语句
13.2.1、单表视图的创建
#准备好数据表 emps
#1
CREATE VIEW vu_emp1
AS
SELECT employee_id,last_name,salary
FROM emps;
#2 视图字段名确定
CREATE VIEW vu_emp2
AS
SELECT employee_id emp_id,last_name lname,salary # 查询语句中的字段别名会是视图的字段名
FROM emps
WHERE salary > 8000;
#3 视图字段名确定
CREATE VIEW vu_emp1(emp_id,name,monthly_sal)
AS
SELECT employee_id,last_name,salary
FROM emps
WHERE salary > 8000;
#4 视图中的字段在基表中可能没有对应的字段
CREATE VIEW vu_emp1
AS
SELECT department_id,AVG(salary) avg_sal
FROM emps
WHERE department_id IS NOT NULL
GROUP BY department_id;
13.2.2、多表视图的创建
#准备好emps和depts(就是各复制一份employees ,departments)
CREATE VIEW vu_emp_dept
AS
SELECT e.employee_id,e.department_id,d.department_name
FROM emps e JOIN depts d
ON e.department_id = d.department_id;
13.2.3、利用视图对数据进行格式化
CREATE VIEW vu_emp_dept1
AS
SELECT CONCAT(e.lastname,'(',d.department_name,')') emp_info
FROM emps e JOIN depts d
ON e.department_id = d.department_id;
13.2.4、基于视图创建视图
CREATE VIEW vu_emp5
AS
SELECT employee_id,last_name,salary
FROM vu_emp1;
#当然也可以基于多个视图创建视图
13.3、 查看视图
查看数据库的视图对象
。
#1
SHOW TABLES;#查询数据库的表对象、视图对象
#2
DESC 视图名称; #查看视图结构
#3
SHOW TABLE STATUS LIKE '视图名称'\G;#查看视图的属性信息,只有comment为VIEW,其他为null,说明这是一个虚表
#4
SHOW CREATE VIEW 视图名称; #查看详细的定义信息
13.4、更新视图记录
一般情况下可以更新视图记录:
更新(增,删,改)视图中的记录,会导致基表中的记录更新
更新基表中的记录,也会导致视图中的记录更新
不能更新视图中的记录:
比如对前面平均值的视图做更新,更新就会失败。
要使视图可更新,视图中的行和底层基本表中的行之间必须存在 一对一 的关系。另外当视图定义出现如下情况时,视图不支持更新操作:
- 在定义视图的时候指定了“
ALGORITHM = TEMPTABLE
”,视图将不支持INSERT和DELETE操作; - 视图中不包含基表中所有被定义为非空又未指定默认值的列,视图将不支持INSERT操作;
- 在定义视图的SELECT语句中使用了 JOIN联合查询 ,视图将不支持INSERT和DELETE操作;
- 在定义视图的SELECT语句后的字段列表中使用了 数学表达式 或 子查询 ,视图将不支持INSERT,也不支持UPDATE使用了数学表达式、子查询的字段值;
- 在定义视图的SELECT语句后的字段列表中使用 DISTINCT 、 聚合函数 、 GROUP BY 、 HAVING 、UNION 等,视图将不支持INSERT、UPDATE、DELETE;
- 在定义视图的SELECT语句中包含了子查询,而子查询中引用了FROM后面的表,视图将不支持INSERT、UPDATE、DELETE;
- 视图定义基于一个 不可更新视图 ;
- 常量视图。
总结:虽然可以更新视图中的数据,但是总的来说,视图作为虚拟表,主要用于方便查询,不建议更新视图的数据。对视图的更新,都是通过对基表的操作来完成的。记住视图更新不是一定成功的就行。
13.5、修改视图
#方式1:使用CREATE OR REPLACE子句修改视图
CREATE OR REPLACE VIEW vu_emp1 #如果存在,就替换
(id_number,name,sal,department_id)
AS
SELECT employee_id,first_name||' ' || last_name,salary,department_id
FROM employees
WHERE department_id = 80;
#方式2
ALTER VIEW vu_emp1
AS
SELECT employee_id,first_name||' ' || last_name,salary,department_id
FROM employees
13.6、删除视图
DROP VIEW IF EXISTS vu_emp3;
注意:基于视图创建的新的视图,若将原视图删掉,会导致新视图查询的失败。这样的新视图需要手动删除或修改,否则影响使用。
13.7、总结
视图优点:
①操作简单
:将经常使用的查询语句定义为视图,可以是开发人员不需要关心视图对应的数据表结构、表与表之间的关联关系、也不需要关心数据表之间的业务逻辑和查询条件,而只是简单地操作视图即可,极大简化了开发人员对数据库的操作。
②减少数据冗余
:视图跟实际数据表不一样,他存储的是查询语句。所以,在使用的时候,我们通过定义视图的查询语句来获取结果集。而视图本身不存储数据,不占用数据存储的资源,减少了数据冗余。
③数据安全
:mysql将用户对数据的访问限制在某些数据的结果集上,而这些数据的结果集可以使用视图来实现。用户不必要直接查询或操作数据集。这可以理解为视图的隔离性。视图相当于在用户和实际的数据表之间加了一层虚拟表。同时,mysql可以根据权限将用户对数据的访问权限限制在某些视图上,用户不需要查询数据表,可以直接通过视图获取数据表中的信息。这在一定程度上保障了数据表中数据的安全性。
④适应灵活多变的需求
:当业务系统的需求发生变化后,如果需要改动数据表的结构,则工作量相对较大,可以使用视图来减少改动的工作量。
⑤能够分解复杂的查询逻辑
:数据库如果存在复杂的查询逻辑,则可以将问题进行分解,穿件多个视图来获取数据再将创建的多个视图结合起来,完成任务。
视图的不足:
如果我们在实际数据表的基础上创建了视图,那么,如果实际数据表的结构变更了,我们就需要及时对相关的视图进行相应的维护。特别是嵌套的视图(就是在视图的基础上创建视图),维护会变得比较复杂, 可读性不好 ,容易变成系统的潜在隐患。因为创建视图的 SQL 查询可能会对字段重命名,也可能包含复杂的逻辑,这些都会增加维护的成本。
实际项目中,如果视图过多,会导致数据库维护成本的问题。
所以,在创建视图的时候,你要结合实际项目需求,综合考虑视图的优点和不足,这样才能正确使用视图,使系统整体达到最优。
14、存储过程与存储函数
—是一种自定义函数
MySQL5.0开始支持存储过程和存储函数。他们能够将复杂的SQL逻辑封装在一起,应用程序无需关注他们内部复杂的SQL逻辑,而只需要简单的调用存储过程和函数即可。
14.1、存储过程
含义
:存储过程的英文 Stored Procedure.思想简单,就是一组经过预先编译的SQL语句的封装。
执行过程
:存储过程预先存储在服务器上,需要执行时,客户端只需要向服务器发送调用存储过程的命令,服务器就可以把预先存储好的这一系列SQL语句全部执行。
优点
:1.简化操作,提高了sql语句的复用性;2.减少了操作过程的失误,提高了效率;3.减少网络传输量(客户端不需要把所有sql语句发送给服务器);4.减少了sql语句在网络上的暴露风险,提高了数据查询的安全性。
相比于视图,有着同样的优点,清晰,安全,还减少了网络传输量。不同在于,视图是虚拟表,通常不对底层数据表直接操作,而存储过程是程序化的SQL,可以直接对底层数据表做操作,相比于面向集合的操作方式,能够实现一些更复杂的数据处理。
相比于(存储)函数,一旦存储过程被创建出来,就可以像使用函数一样简单,相比较于函数,存储过程没有返回值
分类
:存储过程的参数类型可以是IN,OUT,INOUT(参数返回在一个变量上)。
1.没有参数(无参数无返回)
2.仅仅带IN类型(有参数无返回)
3.仅仅带OUT类型(无参数有返回)
4.即带IN又带OUT(有参数有返回)
5.带INOUT(有参数有返回)
注意:IN,OUT,INOUT都可以多个
基本语法
:
CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...)
[characteristcs ...]
BEGIN
存储过程体
END
#说明
# IN 当前参数为输入参数,存储过程只是读入这个参数的值。如果没有定义参数类型,默认就是IN。
#OUT 当前参数为输出参数,执行完成后,调用这个存储过程的客户端或者程序可以读取这个参数的返回值。
#INOUT 当前参数既可以是输入参数,也可以是输出参数。
# 形参类型可以是mysql的任意类型。
# characteristics 表示创建存储过程时指定的约束条件,取值信息如下:
LANGUAGE SQL
| [NOT] DETERMINISTIC
| {CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}
| SQL SECURITY { DEFINER | INVOKER}
| COMMENT 'string'
LANGUAGE SQL
#说明存储过程执行体是由SQL语句组成的,当前系统支持的语言是SQL
[NOT] DETERMINISTIC
# 指明存储过程执行的结果是否确定。DETERMINISTIC表示结果是确定的。每次执行存储过程时,相同的输入会得到相同的输出。NOT DETERMINISTIC 表示结果是不确定的,系统的输入可能得到不同的输出。如果没有指定任意一个值,默认为NOT DETERMINISTIC。
{CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA}
#指明子程序使用SQL语句的限制;CONTAINS SQL表示当前存储过程的子程序包含SQL语句,但是并不包含读写数据的SQL语句;NO SQL表示当前存储过程的子程序中不包含SQL语句;READS SQL DATA 表示当前存储过程的子程序包含读数据的SQL语句;MODIFIES SQL DATA示当前存储过程的子程序包含写数据的SQL语句。默认情况下系统指定为CONTAINS SQL。
SQL SECURITY { DEFINER | INVOKER}
#表示当前存储过程的权限,即指明哪些用户可以执行当前存储过程。DEFINER 表示只有当前存储过程的创建者或者定义者才能够执行当前存储过程;INVOKER表示拥有当前存储过程访问权限的用户能够执行当前存储过程。
COMMENT 'string'
#注释信息,可以用来描述此存储过程。
存储过程中可以有多条SQL语句,如果仅仅只有一条,则可以省略BEGIN和END. 编写存储过程并不是一件简单的事情,可能存储过程中需要复杂的SQL语句。
BEGIN…END:BEGIN…END 中间包含了多个语句,每个语句都以(;)号为结束符。
DECLARE:DECLARE 用来声明变量,使用的位置在于 BEGIN…END 语句中间,而且需要在其他语句使用之前进行变量的声明。
SET:赋值语句,用于对变量进行赋值。
SELECT… INTO:把从数据表中查询的结果存放到变量中,也就是为变量赋值。
需要设置新的结束标志
DELIMITER 新的结束标记
因为mysql默认语句结束符号是; ,为了避免与存储过程中SQL语句结束符冲突,需要使用该关键字改变存储过程的结束符。避免使用\
。
DELIMITER //
CREATE PROCEDURE avg_employee_salary ()
BEGIN
SELECT AVG(salary) AS avg_salary FROM emps;
END //
DELIMITER ;
14.1.1、创建存储过程
14.1.1.1、 无参数类型
#准备工作,创建emps
#创建存储过程select_all_data(),查看emps表的所有数据---无参数类型
DELIMITER $
CREATE PROCEDURE select_all_data()
BEGIN
SELECT * FROM emps;
END $
DELIMITER ;
14.1.1.2、 OUT参数类型
#创建存储过程show_min_salary(),查看emps表的最低薪资,并将最低值通过OUT参数ms输出。---OUT型参数
DELIMITER $
CREATE PROCEDURE select_all_data( OUT ms DOUBLE)
BEGIN
SELECT MIN(salary) INTO ms
FROM emps;
END $
DELIMITER ;
14.1.1.3、 IN参数类型
#创建存储过程show_someone_salary(),查看emps表的某个员工的工资,使用IN参数empname输入员工的姓名。----IN型参数
DELIMITER $
CREATE PROCEDURE show_someone_salary( IN empname VARCHAR(20))
BEGIN
SELECT salary
FROM emps
WHERE last_name = empname;
END $
DELIMITER ;
14.1.1.4、 IN,OUT参数类型
#创建存储过程show_someone_salary2(),查看emps表的某个员工的工资,用IN参数empname输入员工姓名,用OUT参数empsalary输出工资
DELIMITER $
CREATE PROCEDURE show_someone_salary2( IN empname VARCHAR(20),OUT empsalary DECIMAL(10,2))
BEGIN
SELECT salary INTO empsalary
FROM emps
WHERE last_name = empname;
END $
DELIMITER ;
14.1.1.5、 INOUT参数类型
#创建存储过程show_mgr_name(),查询某个员工领导的姓名,用INOUT参数empname输入员工的姓名并且输出领导的姓名
DELIMITER $
CREATE PROCEDURE show_mgr_name( INOUT empname VARCHAR(25))
BEGIN
SELECT last_name INTO empname
FROM emps
WHERE employee_id = (
SELECT manager_id
FROM emps
WHERE last_name = empname
);
END $
DELIMITER ;
14.1.2、调用存储过程
#无参数
CALL select_all_data();
#OUT参数
CALL select_all_data(@ms);#表示用户定义变量,后面具体展开将变量
#IN参数
CALL show_someone_salary('Abel');#也可以定义变量来调用,无所谓啦。
#IN参数,OUT参数
SET @empname = 'Abel';
CALL show_someone_salary2(@empname,@empsalary);
SELECT @empsalary;#查看值
#INOUT参数
SET @empname = 'Abel';
CALL show_mgr_name(@empname);
SELECT @empsalary;#查看值
在 MySQL 中,存储过程不像普通的编程语言(比如 VC++、Java 等)那样有专门的集成开发环境。因此,你可以通过 SELECT 语句,把程序执行的中间结果查询出来,来调试一个 SQL 语句的正确性。调试成功之后,把 SELECT 语句后移到下一个 SQL 语句之后,再调试下一个 SQL 语句。这样 逐步推进 ,就可以完成对存储过程中所有操作的调试了。当然,你也可以把存储过程中的 SQL 语句复制出来,逐段单独调试。
14.2、存储函数
语法格式
CREATE FUNCTION 函数名(参数名 参数类型,...)
RETURNS 返回值类型
[characteristics ...]
BEGIN
函数体 #包含return语句
END
说明:
1、参数列表:指定参数为IN、OUT或INOUT只对PROCEDURE
是合法的,FUNCTION
中总是默认为IN参数。
2、RETURNS type 语句表示函数返回数据的类型;
RETURNS子句只能对FUNCTION做指定,对函数而言这是 强制 的。它用来指定函数的返回类型,而且函数体必须包含一个 RETURN value 语句。
3、characteristic 创建函数时指定的对函数的约束。取值与创建存储过程时相同,这里不再赘述。
4、函数体也可以用BEGIN…END来表示SQL代码的开始和结束。如果函数体只有一条语句,也可以省略BEGIN…END。
14.2.1、创建存储函数
注意:若在创建存储函数中报错“ you might want to use the less safe log_bin_trust_function_creators variable ”
,有两种处理方法:
方式1
:加上必要的函数特性“[NOT] DETERMINISTIC”和“{CONTAINS SQL | NO SQL | READS SQL DATA |MODIFIES SQL DATA}”
方式2
:
SET GLOBAL log_bin_trust_function_creators = 1;
#创建存储函数,名称为email_by_name(),参数定义为空,该函数查询Abel的email,并返回,数据类型为字符串型。
DELIMITER //
CREATE FUNCTION email_by_name()
RETURNS VARCHAR(25)
DETERMINISTIC
CONTAINS SQL
BEGIN
RETURN (SELECT email FROM emps WHERE last_name = 'Abel');
END //
DELIMITER ;
#调用
SELECT email_by_name();
#创建存储函数,名称为email_by_id(),参数传入emp_id,该函数查询emp_id的email,并返回,数据类型为字符串型。
DELIMITER //
CREATE FUNCTION email_by_id(emp_id INT)
RETURNS VARCHAR(25)
DETERMINISTIC
CONTAINS SQL
BEGIN
RETURN (SELECT email FROM emps WHERE employee_id = emp_id);
END //
DELIMITER ;
#调用
SET @emp_id = 102;
SELECT email_by_id(102);
#创建存储函数count_by_id(),参数传入dept_id,该函数查询dept_id部门的员工人数,并返回,数据类型为整型。
DELIMITER //
CREATE FUNCTION count_by_id(dept_id INT)
RETURNS INT
LANGUAGE SQL
NOT DETERMINISTIC
READS SQL DATA
SQL SECURITY DEFINER
COMMENT '查询部门平均工资'
BEGIN
RETURN (SELECT COUNT(*) FROM emps WHERE department_id = dept_id);
END //
DELIMITER ;
#调用
SET @dept_id = 50;
SELECT count_by_id(@dept_id);
14.2.2、调用存储函数
在MySQL中,存储函数的使用方法与MySQL内部函数的使用方法是一样的。换言之,用户自己定义的存储函数与MySQL内部函数是一个性质的。区别在于,存储函数是 用户自己定义 的,而内部函数是MySQL的 开发者定义
的。
SELECT 函数名(实参列表)
14.3、对比存储函数和存储过程
对象 | 关键字 | 调用语法 | 返回值 | 应用场景 |
---|---|---|---|---|
存储过程 | PRECEDURE | CALL | 0个或多个 | 一般用于更新 |
存储函数 | FUNCTIOPN | SELECT | 只能一个 | 一般用于查询结果为一个值并返回时 |
此外,存储函数可以放在查询语句中使用,存储过程不行。反之,存储过程的功能更加强大,包括能够执行对表的操作(比如创建表,删除表等)和事务操作,这些功能是存储函数不具备的。
14.4、存储过程和函数的查看、修改、删除
14.4.1、查看
怎么知道我们创建的存储过程、存储函数是否成功了呢?
①使用SHOW CREATE语句查看存储过程和函数的创建信息
基本语法
:
SHOW CREATE {PROCEDURE | FUNCTION} 存储过程名或函数名
SHOW CREATE FUNCTION test_db.CountProc \G
②使用SHOW STATUS语句查看存储过程和函数的状态信息
基本语法
:
SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']
#这个语句返回子程序的特征,如数据库、名字、类型、创建者及创建和修改日期。
③从information_schema.Routines表中查看存储过程和函数的信息
基本语法
:
SELECT * FROM information_schema.Routines
WHERE ROUTINE_NAME='存储过程或函数的名' [AND ROUTINE_TYPE = {'PROCEDURE|FUNCTION'}];
说明
:如果在MySQL数据库中存在存储过程和函数名称相同的情况,最好指定ROUTINE_TYPE查询条件来指明查询的是存储过程还是函数。
14.4.2、修改
修改存储过程或函数,不影响存储过程或函数功能,只是修改相关特性
。使用ALTER语句实现。
ALTER {PROCEDURE | FUNCTION} 存储过程或函数的名 [characteristic ...]
其中,characteristic指定存储过程或函数的特性,其取值信息与创建存储过程、函数时的取值信息略有不同。具体特性含义见前面
14.4.3、删除
删除存储过程和函数,可以使用DROP语句,其语法结构如下:
DROP {PROCEDURE | FUNCTION} [IF EXISTS] 存储过程或函数的名
IF EXISTS:如果程序或函数不存储,它可以防止发生错误,产生一个用SHOW WARNINGS查看的警告。
14.5、关于存储过程使用的争议
优点
:
1、存储过程可以一次编译多次使用。存储过程只在创建时进行编译,之后的使用都不需要重新编译,这就提升了 SQL 的执行效率。
2、可以减少开发工作量。将代码 封装 成模块,实际上是编程的核心思想之一,这样可以把复杂的问题拆解成不同的模块,然后模块之间可以 重复使用 ,在减少开发工作量的同时,还能保证代码的结构清晰。
3、存储过程的安全性强。我们在设定存储过程的时候可以 设置对用户的使用权限 ,这样就和视图一样具有较强的安全性。
4、可以减少网络传输量。因为代码封装到存储过程中,每次使用只需要调用存储过程即可,这样就减少了网络传输量。
5、良好的封装性。在进行相对复杂的数据库操作时,原本需要使用一条一条的 SQL 语句,可能要连接多次数据库才能完成的操作,现在变成了一次存储过程,只需要 连接一次即可 。
缺点
:
1、可移植性差。存储过程不能跨数据库移植,比如在 MySQL、Oracle 和 SQL Server 里编写的存储过程,在换成其他数据库时都需要重新编写。
2、调试困难。只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程来说,开发和维护都不容易。虽然也有一些第三方工具可以对存储过程进行调试,但要收费。
3、存储过程的版本管理很困难。比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。
4、它不适合高并发的场景。高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护, 增加数据库的压力 ,显然就不适用了。
小结
:
存储过程既方便,又有局限性。尽管不同的公司对存储过程的态度不一,但是对于我们开发人员来说,不论怎样,掌握存储过程都是必备的技能之一。
15、变量,流程控制和游标
15.1、变量
在MySQL数据库的存储过程和函数中,可以使用变量来存储查询或计算的中间结果数据,或者输出最终的结果数据。
在 MySQL 数据库中,变量分为 系统变量 以及 用户自定义变量 。
15.1.1、系统变量
变量由系统定义,不是用户定义,属于服务器 层面。启动MySQL服务,生成MySQL服务实例期间,MySQL将为MySQL服务器内存中的系统变量赋值,这些系统变量定义了当前MySQL服务实例的属性、特征。这些系统变量的值要么是 编译MySQL时参数 的默认值,要么是 配置文件 (例如my.ini等)中的参数值。
系统变量
分为全局系统变量
(需要添加 global 关键字)以及会话系统变量
(需要添加 session 关键字),有时也把全局系统变量简称为全局变量,有时也把会话系统变量称为local变量。如果不写,默认会话级别
。静态变量(在 MySQL 服务实例运行期间它们的值不能使用 set 动态修改)属于特殊的全局系统变量。
每一个MySQL客户机成功连接MySQL服务器后,都会产生与之对应的会话。会话期间,MySQL服务实例会在MySQL服务器内存中生成与该会话对应的会话系统变量,这些会话系统变量的初始值是全局系统变量值的复制。
全局系统变量针对于所有会话(连接)有效,但 不能跨重启
会话1对某个全局系统变量值的修改会导致会话2中同一个全局系统变量值的修改。
在mysql中,有些变量只能是全局的,有些即可以是全局的也可以是会话级的,有些只能是会话级的。
15.1.1.1、 查看系统变量
#查看所有全局变量
SHOW GLOBAL VARIABLES;
#查看所有会话变量
SHOW SESSION VARIABLES;
或
SHOW VARIABLES;#默认查询会话系统变量
#查看满足条件的部分系统变量。
SHOW GLOBAL VARIABLES LIKE '%标识符%';
#查看满足条件的部分会话变量
SHOW SESSION VARIABLES LIKE '%标识符%';
SHOW GLOBAL VARIABLES LIKE 'admin_%';
作为 MySQL 编码规范,MySQL 中的系统变量以 两个“@” 开头,其中“@@global”仅用于标记全局系统变量,“@@session”仅用于标记会话系统变量。“@@”首先标记会话系统变量,如果会话系统变量不存在,则标记全局系统变量。
#查看指定的系统变量的值
SELECT @@global.变量名;
#查看指定的会话变量的值
SELECT @@session.变量名;
#或者
SELECT @@变量名;
15.1.1.2、 修改系统变量
有些时候,数据库管理员需要修改系统变量的默认值,以便修改当前会话或者MySQL服务实例的属性、
特征。具体方法:
方式1:修改MySQL 配置文件 ,继而修改MySQL系统变量的值(该方法需要重启MySQL服务)
方式2:在MySQL服务运行期间,使用“set”命令重新设置系统变量的值
SET @@global.变量名=变量值;
#方式2:
SET GLOBAL 变量名=变量值;
#为某个会话变量赋值
#方式1:
SET @@session.变量名=变量值;
#方式2:
SET SESSION 变量名=变量值;
15.1.1.3、MySQL 8.0 新特性—全局变量持久化
在MySQL数据库中,全局变量可以通过SET GLOBAL语句来设置。例如,设置服务器语句超时的限制,可以通过设置系统变量max_execution_time来实现:
SET GLOBAL MAX_EXECUTION_TIME=2000;
使用SET GLOBAL语句设置的变量值只会 临时生效 。 数据库重启 后,服务器又会从MySQL配置文件中读取变量的默认值。 MySQL 8.0版本新增了 SET PERSIST 命令。例如,设置服务器的最大连接数为1000:
SET PERSIST global max_connections = 1000;
MySQL会将该命令的配置保存到数据目录下的 mysqld-auto.cnf 文件中,下次启动时会读取该文件,用其中的配置来覆盖默认的配置文件。
15.1.2、用户变量
用户变量是用户自己定义的,作为 MySQL 编码规范,MySQL 中的用户变量以 一个“@” 开头。根据作用范围不同,又分为 会话用户变量 和 局部变量 。
- 会话用户变量:作用域和会话变量一样,只对
当前连接
会话有效。 - 局部变量:只在 BEGIN 和 END 语句块中有效。局部变量只能在
存储过程和函数
中使用。
15.1.2.1、会话用户变量
变量的定义
:
#方式1:“=”或“:=”
SET @用户变量 = 值;
SET @用户变量 := 值;
#方式2:“:=” 或 INTO关键字
SELECT @用户变量 := 表达式 [FROM 等子句];
SELECT 表达式 INTO @用户变量 [FROM 等子句];
SET @a1 = 1;
SET @a2 := 2;
SET @a3 := @a1 + @a2;
SELECT @count := COUNT(*) FROM employees;
SELECT AVG(salary) INTO @avg_sal FROM employees;
查看用户变量的值 (查看、比较、运算等)
:
SELECT @用户变量;
SELECT @a1;
SELECT @big; #查看某个未声明的变量时,将得到NULL值
15.1.2.2、局部变量
定义
:可以使用 DECLARE 语句定义一个局部变量
作用域
:仅仅在定义它的 BEGIN … END 中有效
位置
:只能放在 BEGIN … END 中,而且只能放在第一句
BEGIN
#声明局部变量
DECLARE 变量名1 变量数据类型 [DEFAULT 变量默认值];# 如果没有DEFAULT子句,初始值为NULL
DECLARE 变量名2,变量名3,... 变量数据类型 [DEFAULT 变量默认值];
#为局部变量赋值
SET 变量名1 = 值;
SELECT 值 INTO 变量名2 [FROM 子句];
#查看局部变量的值
SELECT 变量1,变量2,变量3;
END
例子:声明局部变量,并分别赋值为employees表中employee_id为102的last_name和salary
DELIMITER //
CREATE PROCEDURE test_loc()
BEGIN
#声明局部变量
DECLARE emp_name VARCHAR(25);
DECLARE SAL DOUBLE(10,2) DEFAULT 0.0;
#赋值
SELECT last_name,salary INTO emp_name,sal FROM employees WHERE employee_id = 102;
#使用
SELECT emp_name,sal;
END //
DELIMITER ;
15.1.2.3、两者对比
对象 | 作用域 | 定义位置 | 语法 |
---|---|---|---|
会话用户变量 | 当前会话 | 会话的任何地方 | 加@符号,不用指定类型 |
局部变量 | 定义它的BEGIN END中 | BEGIN END的第一句话 | 一般不用加@,需要指定类型 |
15.2、定义条件与处理程序
定义条件
是事先定义程序执行过程中可能遇到的问题, 处理程序
定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继续执行。这样可以增强存储程序处理问题的能力,避免程序异常停止运行。
说明:定义条件和处理程序在存储过程、存储函数中都是支持的。
15.2.1、定义条件
定义条件就是给MySQL中的错误码命名,这有助于存储的程序代码更清晰。它将一个 错误名字 和 指定的错误条件 关联起来。这个名字可以随后被用在定义处理程序的 DECLARE HANDLER 语句中。
定义条件使用DECLARE语句,语法格式如下:
DECLARE 错误名称 CONDITION FOR 错误码(或错误条件)
错误码的说明:
MySQL_error_code 和 sqlstate_value 都可以表示MySQL的错误。
- MySQL_error_code是数值类型错误代码。
- sqlstate_value是长度为5的字符串类型错误代码。
举例1:定义“Field_Not_Be_NULL”错误名与MySQL中违反非空约束的错误类型是“ERROR 1048 (23000)”对应。
#使用MySQL_error_code
DECLARE Field_Not_Be_NULL CONDITION FOR 1048;
#使用sqlstate_value
DECLARE Field_Not_Be_NULL CONDITION FOR SQLSTATE '23000';
15.2.2、定义处理过程
可以为SQL执行过程中发生的某种类型的错误定义特殊的处理程序。定义处理程序时,使用DECLARE语句
的语法如下:
DECLARE 处理方式 HANDLER FOR 错误类型 处理语句
处理方式
:处理方式有3个取值:CONTINUE、EXIT、UNDO。
CONTINUE
:表示遇到错误不处理,继续执行。EXIT
:表示遇到错误马上退出。UNDO
:表示遇到错误后撤回之前的操作。MySQL中暂时不支持这样的操作。
错误类型
(即条件)可以有如下取值:
SQLSTATE '字符串错误码'
:表示长度为5的sqlstate_value类型的错误代码;MySQL_error_code
:匹配数值类型错误代码;错误名称
:表示DECLARE … CONDITION定义的错误条件名称(上面定义的)。SQLWARNING
:匹配所有以01开头的SQLSTATE错误代码;NOT FOUND
:匹配所有以02开头的SQLSTATE错误代码;SQLEXCEPTION
:匹配所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE错误代码;
处理语句
:如果出现上述条件之一,则采用对应的处理方式,并执行指定的处理语句。语句可以是像“ SET 变量 = 值 ”这样的简单语句,也可以是使用 BEGIN … END 编写的复合语句。
#方法1:捕获sqlstate_value
DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02' SET @info = 'NO_SUCH_TABLE';
#方法2:捕获mysql_error_value
DECLARE CONTINUE HANDLER FOR 1146 SET @info = 'NO_SUCH_TABLE';
#方法3:先定义条件,再调用
DECLARE no_such_table CONDITION FOR 1146;
DECLARE CONTINUE HANDLER FOR NO_SUCH_TABLE SET @info = 'NO_SUCH_TABLE';
15.2.3、案例
创建一个名称为“InsertDataWithCondition”的存储过程,代码如下。在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到sqlstate_value值为23000时,执行EXIT操作,并且将@proc_value的值设置为-1。
DELIMITER //
CREATE PROCEDURE InsertDataWithCondition()
BEGIN
DECLARE duplicate_entry CONDITION FOR SQLSTATE '23000' ;
DECLARE EXIT HANDLER FOR duplicate_entry SET @proc_value = -1;
SET @x = 1;
INSERT INTO departments(department_name) VALUES('测试');
SET @x = 2;
INSERT INTO departments(department_name) VALUES('测试');
SET @x = 3;
END //
DELIMITER ;
15.3、 流程控制
解决复杂问题不可能通过一个 SQL 语句完成,我们需要执行多个 SQL 操作。流程控制语句的作用就是控制存储过程中 SQL 语句的执行顺序,是我们完成复杂操作必不可少的一部分。只要是执行的程序,流程就分为三大类:
顺序结构
:程序从上往下依次执行分支结构
:程序按条件进行选择执行,从两条或多条路径中选择一条执行循环结构
:程序满足一定条件下,重复执行一组语句
针对于MySQL 的流程控制语句主要有 3 类。注意:只能用于存储程序
。
条件判断语句
:IF 语句和 CASE 语句循环语句
:LOOP、WHILE 和 REPEAT 语句跳转语句
:ITERATE 和 LEAVE 语句
15.3.1、 分支结构之 IF
IF语法结构
:
IF 表达式1 THEN 操作1
[ELSEIF 表达式2 THEN 操作2]……
[ELSE 操作N]
END IF
#声明存储过程“update_salary_by_eid1”,定义IN参数emp_id,输入员工编号。
#判断该员工薪资如果低于9000元并且入职时间超过5年,就涨薪500元;否则就涨薪100元。
DELIMITER //
CREATE PROCEDURE update_salary_by_eid1(IN emp_id INT)
BEGIN
DECLARE emp_salary DOUBLE;
DECLARE hire_year DOUBLE;
SELECT salary INTO emp_salary
FROM employees
WHERE employee_id = emp_id;
SELECT DATEDIFF(CURDATE(),hire_date)/365 INTO hire_year
FROM employees
WHERE employee_id = emp_id;
IF emp_salary < 8000 AND hire_year > 5
THEN UPDATE employees SET salary = salary + 500 WHERE employee_id = emp_id;
ELSE
UPDATE employees SET salary = salary + 100 WHERE employee_id = emp_id;
END IF;
END //
DELIMITER ;
15.3.2、分支结构之 CASE
CASE语法
:
#情况一:类似于switch
CASE 表达式
WHEN 值1 THEN 结果1或语句1(如果是语句,需要加分号)
WHEN 值2 THEN 结果2或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)
#情况二:类似于多重if
CASE
WHEN 条件1 THEN 结果1或语句1(如果是语句,需要加分号)
WHEN 条件2 THEN 结果2或语句2(如果是语句,需要加分号)
...
ELSE 结果n或语句n(如果是语句,需要加分号)
END [case](如果是放在begin end中需要加上case,如果放在select后面不需要)
#声明存储过程“update_salary_by_eid2”,定义IN参数emp_id,输入员工编号。
#判断该员工薪资如果低于9000元,就更新薪资为9000元;
#薪资大于等于9000元且低于10000的,但是奖金比例为NULL的,就更新奖金比例为0.01;
#其他的涨薪100元。
DELIMITER //
CREATE PROCEDURE update_salary_by_eid2(IN emp_id INT)
BEGIN
DECLARE emp_sal DOUBLE;
DECLARE bonus DECIMAL(3,2);
SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
SELECT commission_pct INTO bonus FROM employees WHERE employee_id = emp_id;
CASE
WHEN emp_sal<9000
THEN UPDATE employees SET salary=9000 WHERE employee_id = emp_id;
WHEN emp_sal<10000 AND bonus IS NULL
THEN UPDATE employees SET commission_pct=0.01 WHERE employee_id = emp_id;
ELSE
UPDATE employees SET salary=salary+100 WHERE employee_id = emp_id;
END CASE;
END //
DELIMITER ;
#声明存储过程update_salary_by_eid3,定义IN参数emp_id,输入员工编号。#判断该员工的入职年限,如果是0年,薪资涨50;如果是1年,薪资涨100;如果是2年,薪资涨200;
#如果是3年,薪资涨300;如果是4年,薪资涨400;其他的涨薪500。
DELIMITER //
CREATE PROCEDURE update_salary_by_eid3(IN emp_id INT)
BEGIN
DECLARE emp_sal DOUBLE;
DECLARE hire_year DOUBLE;
SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
SELECT ROUND(DATEDIFF(CURDATE(),hire_date)/365) INTO hire_year FROM employees
WHERE employee_id = emp_id;
CASE hire_year
WHEN 0 THEN UPDATE employees SET salary=salary+50 WHERE employee_id = emp_id;
WHEN 1 THEN UPDATE employees SET salary=salary+100 WHERE employee_id = emp_id;
WHEN 2 THEN UPDATE employees SET salary=salary+200 WHERE employee_id = emp_id;
WHEN 3 THEN UPDATE employees SET salary=salary+300 WHERE employee_id = emp_id;
WHEN 4 THEN UPDATE employees SET salary=salary+400 WHERE employee_id = emp_id;
ELSE UPDATE employees SET salary=salary+500 WHERE employee_id = emp_id;
END CASE;
END //
DELIMITER ;
15.3.3、循环结构之LOOP
LOOP循环语句用来重复执行某些语句。LOOP内的语句一直重复执行直到循环被退出(使用LEAVE子句),跳出循环过程。
LOOP语法结构
:
[loop_label:] LOOP
循环执行语句
END LOOP [loop_label]
#其中,loop_label表示LOOP语句的标注名称,可以省略。
#当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程“update_salary_loop()”,声明OUT参数num,输出循环次数。
#存储过程中实现循环给大家涨薪,薪资涨为原来的1.1倍。直到全公司的平均薪资达到12000结束。并统计循环次数。
DELIMITER //
CREATE PROCEDURE update_salary_loop(OUT num INT)
BEGIN
DECLARE avg_salary DOUBLE;
DECLARE loop_count INT DEFAULT 0;
SELECT AVG(salary) INTO avg_salary FROM employees;
label_loop:LOOP
IF avg_salary >= 12000 THEN LEAVE label_loop;
END IF;
UPDATE employees SET salary = salary * 1.1;
SET loop_count = loop_count + 1;
SELECT AVG(salary) INTO avg_salary FROM employees;
END LOOP label_loop;
SET num = loop_count;
END //
DELIMITER ;
15.3.4、循环结构之WHILE
WHILE语句创建一个带条件判断的循环过程。WHILE在执行语句执行时,先对指定的表达式进行判断,如果为真,就执行循环内的语句,否则退出循环。
WHILE语句结构
:
[while_label:] WHILE 循环条件 DO
循环体
END WHILE [while_label];
while_label为WHILE语句的标注名称;如果循环条件结果为真,WHILE语句内的语句或语句群被执行,直至循环条件为假,退出循环。
#市场环境不好时,公司为了渡过难关,决定暂时降低大家的薪资。声明存储过程“update_salary_while()”,声明OUT参数num,输出循环次数。
#存储过程中实现循环给大家降薪,薪资降为原来的90%。直到全公司的平均薪资达到5000结束。并统计循环次数。
DELIMITER //
CREATE PROCEDURE update_salary_while(OUT num INT)
BEGIN
DECLARE avg_sal DOUBLE ;
DECLARE while_count INT DEFAULT 0;
SELECT AVG(salary) INTO avg_sal FROM employees;
WHILE avg_sal > 5000 DO
UPDATE employees SET salary = salary * 0.9;
SET while_count = while_count + 1;
SELECT AVG(salary) INTO avg_sal FROM employees;
END WHILE;
SET num = while_count;
END //
DELIMITER ;
15.3.5、循环结构之REPEAT
REPEAT语句创建一个带条件判断的循环过程。与WHILE循环不同的是,REPEAT 循环首先会执行一次循环,然后在 UNTIL 中进行表达式的判断,如果满足条件就退出,即 END REPEAT;如果条件不满足,则会就继续执行循环,直到满足退出条件为止。
REPEAT语句结构
:
[repeat_label:] REPEAT
循环体语句
UNTIL 结束循环的条件表达式
END REPEAT [repeat_label]
#repeat_label为REPEAT语句的标注名称,该参数可以省略;REPEAT语句内的语句或语句群被重复,直至expr_condition为真。
#当市场环境变好时,公司为了奖励大家,决定给大家涨工资。声明存储过程“update_salary_repeat()”,声明OUT参数num,输出循环次数。
#存储过程中实现循环给大家涨薪,薪资涨为原来的1.15倍。直到全公司的平均薪资达到13000结束。并统计循环次数。
DELIMITER //
CREATE PROCEDURE update_salary_repeat(OUT num INT)
BEGIN
DECLARE avg_sal DOUBLE ;
DECLARE repeat_count INT DEFAULT 0;
SELECT AVG(salary) INTO avg_sal FROM employees;
REPEAT
UPDATE employees SET salary = salary * 1.15;
SET repeat_count = repeat_count + 1;
SELECT AVG(salary) INTO avg_sal FROM employees;
UNTIL avg_sal >= 13000
END REPEAT;
SET num = repeat_count;
END //
DELIMITER ;
15.3.6、三种循环语句对比
- 三种循环都可以省略名称,但是如果循环中添加了循环控制语句(LEAVE或ITERATE),则必须添加名称。
- LOOP:一般用于简单的死循环。
- WHILE:先判断后执行。
- REPEAT:先执行后判断,无条件至少执行一次。
15.3.7、跳转语句之LEAVE语句
LEAVE语句:可以用在循环语句内,或者以 BEGIN 和 END 包裹起来的程序体内,表示跳出循环或者跳出程序体的操作。如果你有面向过程的编程语言的使用经验,你可以把LEAVE 理解为 break。
LEAVE语法结构
:
LEAVE 标记名
其中,label参数表示循环的标志。LEAVE和BEGIN … END或循环一起被使用。
#创建存储过程 “leave_begin()”,声明INT类型的IN参数num。给BEGIN...END加标记名,并在BEGIN...END中使用IF语句判断num参数的值。
#如果num<=0,则使用LEAVE语句退出BEGIN...END;
#如果num=1,则查询“employees”表的平均薪资;
#如果num=2,则查询“employees”表的最低薪资;
#如果num>2,则查询“employees”表的最高薪资。
#IF语句结束后查询“employees”表的总人数。
DELIMITER //
CREATE PROCEDURE leave_begin(IN num INT)
begin_label: BEGIN
IF num<=0
THEN LEAVE begin_label;
ELSEIF num=1
THEN SELECT AVG(salary) FROM employees;
ELSEIF num=2
THEN SELECT MIN(salary) FROM employees;
ELSE
SELECT MAX(salary) FROM employees;
END IF;
SELECT COUNT(*) FROM employees;
END //
DELIMITER ;
#当市场环境不好时,公司为了渡过难关,决定暂时降低大家的薪资。声明存储过程“leave_while()”,声明OUT参数num,输出循环次数,
#存储过程中使用WHILE循环给大家降低薪资为原来薪资的90%,直到全公司的平均薪资小于等于10000,并统计循环次数。
DELIMITER //
CREATE PROCEDURE leave_while(OUT num INT)
BEGIN
#
DECLARE avg_sal DOUBLE;#记录平均工资
DECLARE while_count INT DEFAULT 0; #记录循环次数
SELECT AVG(salary) INTO avg_sal FROM employees; #① 初始化条件
while_label:WHILE TRUE DO #② 循环条件
#③ 循环体
IF avg_sal <= 10000 THEN
LEAVE while_label;
END IF;
UPDATE employees SET salary = salary * 0.9;
SET while_count = while_count + 1;
#④ 迭代条件
SELECT AVG(salary) INTO avg_sal FROM employees;
END WHILE;
#赋值
SET num = while_count;
END //
DELIMITER ;
15.3.8、跳转语句之ITERATE语句
ITERATE语句:只能用在循环语句(LOOP、REPEAT和WHILE语句)内,表示重新开始循环,将执行顺序转到语句段开头处。如果你有面向过程的编程语言的使用经验,你可以把 ITERATE 理解为 continue,意思为“再次循环”。
ITERATE语法结构
ITERATE label
label参数表示循环的标志。ITERATE语句必须跟在循环标志前面。
定义局部变量num,初始值为0。循环结构中执行num + 1操作。
如果num < 10,则继续执行循环;
如果num > 15,则退出循环结构;
ITERATE label
DELIMITER //
CREATE PROCEDURE test_iterate()
BEGIN
DECLARE num INT DEFAULT 0;
my_loop:LOOP
SET num = num + 1;
IF num < 10
THEN ITERATE my_loop;
ELSEIF num > 15
THEN LEAVE my_loop;
END IF;
END LOOP my_loop;
END //
DELIMITER ;
15.4、游标
15.4.1、 什么是游标(或光标)
虽然我们也可以通过筛选条件 WHERE 和 HAVING,或者是限定返回记录的关键字 LIMIT 返回一条记录,但是,却无法在结果集中像指针一样,向前定位一条记录、向后定位一条记录,或者是 随意定位到某一条记录 ,并对记录的数据进行处理。
这个时候,就可以用到游标。游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录进行定位,并对指向的记录中的数据进行操作的数据结构。游标让 SQL 这种面向集合的语言有了面向过程开发的能力。
在 SQL 中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。这里游标 充当了指针的作用 ,我们可以通过操作游标来对数据行进行操作。
MySQL中游标可以在存储过程和函数中使用。
15.4.2、使用游标
游标必须在声明处理程序之前被声明,并且变量和条件还必须在声明游标或处理程序之前
被声明。
如果我们想要使用游标,一般需要经历四个步骤。不同的 DBMS 中,使用游标的语法可能略有不同。
第一步,声明游标
:
在MySQL中,使用DECLARE关键字来声明游标,其语法的基本形式如下:
DECLARE cursor_name CURSOR FOR select_statement;
第二步,打开游标
:
打开游标的语法如下:
OPEN cursor_name
第三步,使用游标
(从游标中取得数据):
语法如下:
FETCH cursor_name INTO var_name [, var_name] ...
注意:游标的查询结果集中的字段数,必须跟 INTO 后面的变量数一致,否则,在存储过程执行的时候,MySQL 会提示错误。
第四步,关闭游标
:
CLOSE cursor_name
有 OPEN 就会有 CLOSE,也就是打开和关闭游标。当我们使用完游标后需要关闭掉该游标。因为游标会占用系统资源 ,如果不及时关闭,游标会一直保持到存储过程结束,影响系统运行的效率。而关闭游标的操作,会释放游标占用的系统资源。关闭游标之后,我们就不能再检索查询结果中的数据行,如果需要检索只能再次打开游标。
#创建存储过程“get_count_by_limit_total_salary()”,声明IN参数 limit_total_salary,DOUBLE类型;声明OUT参数total_count,INT类型。
#函数的功能可以实现累加薪资最高的几个员工的薪资值,直到薪资总和达到limit_total_salary参数的值,返回累加的人数给total_count。
DELIMITER //
CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_salary DOUBLE,OUT
total_count INT)
BEGIN
DECLARE sum_salary DOUBLE DEFAULT 0; #记录累加的总工资
DECLARE cursor_salary DOUBLE DEFAULT 0; #记录某一个工资值
DECLARE emp_count INT DEFAULT 0; #记录循环个数
#定义游标
DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;
#打开游标
OPEN emp_cursor;
REPEAT
#使用游标(从游标中获取数据)
FETCH emp_cursor INTO cursor_salary;
SET sum_salary = sum_salary + cursor_salary;
SET emp_count = emp_count + 1;
UNTIL sum_salary >= limit_total_salary
END REPEAT;
SET total_count = emp_count;
#关闭游标
CLOSE emp_cursor;
END //
DELIMITER ;
15.4.3、 小结
游标是 MySQL 的一个重要的功能,为 逐条读取 结果集中的数据,提供了完美的解决方案。跟在应用层面实现相同的功能相比,游标可以在存储程序中使用,效率高,程序也更加简洁。但同时也会带来一些性能问题,比如在使用游标的过程中,会对数据行进行 加锁
,这样在业务并发量大的时候,不仅会影响业务之间的效率,还会 消耗系统资源 ,造成内存不足,这是因为游标是在内存中进行的处理。建议:养成用完之后就关闭的习惯,这样才能提高系统的整体效率。
16、触发器
触发器是由 事件来触发 某个操作,这些事件包括 INSERT 、 UPDATE 、 DELETE 事件。所谓事件就是指用户的动作或者触发某项行为。如果定义了触发程序,当数据库执行这些语句时候,就相当于事件发生了,就会 自动
激发触发器执行相应的操作。
16.1、创建触发器
创建触发器的语法结构是:
CREATE TRIGGER 触发器名称
{BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名
FOR EACH ROW
触发器执行的语句块;
说明:
-
表名
:表示触发器监控的对象。 -
BEFORE|AFTER
:表示触发的时间。BEFORE 表示在事件之前触发;AFTER 表示在事件之后触发。 -
INSERT|UPDATE|DELETE
:表示触发的事件。INSERT:表示插入记录时触发; UPDATE:表示更新记录时触发; DELETE:表示删除记录时触发。
-
触发器执行的语句块
:可以是单条SQL语句,也可以是由BEGIN…END
#1.创建数据表:
CREATE TABLE test_trigger (
id INT PRIMARY KEY AUTO_INCREMENT,
t_note VARCHAR(30)
);
CREATE TABLE test_trigger_log (
id INT PRIMARY KEY AUTO_INCREMENT,
t_log VARCHAR(30)
);
#2.创建触发器:创建名称为before_insert的触发器,向test_trigger数据表插入数据之前,向test_trigger_log数据表中插入before_insert的日志信息。
DELIMITER //
CREATE TRIGGER before_insert
BEFORE INSERT ON test_trigger
FOR EACH ROW
BEGIN
INSERT INTO test_trigger_log (t_log)
VALUES('before_insert');
END //
DELIMITER ;
#3.向test_trigger数据表中插入数据
INSERT INTO test_trigger (t_note) VALUES ('测试 BEFORE INSERT 触发器');
#4.创建名称为after_insert的触发器,向test_trigger数据表插入数据之后,向test_trigger_log数据表中插入after_insert的日志信息。
ELIMITER //
CREATE TRIGGER after_insert
AFTER INSERT ON test_trigger
FOR EACH ROW
BEGIN
INSERT INTO test_trigger_log (t_log)
VALUES('after_insert');
END //
DELIMITER ;
#5.向test_trigger数据表中插入数据
INSERT INTO test_trigger (t_note) VALUES ('测试 AFTER INSERT 触发器');
#定义触发器“salary_check_trigger”,基于员工表“employees”的INSERT事件,在INSERT之前检查将要添加的新员工薪资是否大于他领导的薪资,如果大于领导薪资,则报sqlstate_value为'HY000'的错误,从而使得添加失败。
DELIMITER //
CREATE TRIGGER salary_check_trigger
BEFORE INSERT ON employees FOR EACH ROW
BEGIN
DECLARE mgrsalary DOUBLE;
SELECT salary INTO mgrsalary FROM employees WHERE employee_id = NEW.manager_id;
IF NEW.salary > mgrsalary THEN
SIGNAL SQLSTATE 'HY000' SET MESSAGE_TEXT = '薪资高于领导薪资错误';
END IF;
END //
DELIMITER ;
上面触发器声明过程中的NEW关键字代表INSERT添加语句的新记录。
16.2、查看触发器
查看触发器是查看数据库中已经存在的触发器的定义、状态和语法信息等。
#方式1:查看当前数据库的所有触发器的定义
SHOW TRIGGERS\G
#方式2:查看当前数据库中某个触发器的定义
SHOW CREATE TRIGGER 触发器名
#方式3:从系统库information_schema的TRIGGERS表中查询“salary_check_trigger”触发器的信息。
SELECT * FROM information_schema.TRIGGERS;
16.3、删除触发器
触发器也是数据库对象,删除触发器也用DROP语句,语法格式如下:
DROP TRIGGER IF EXISTS 触发器名称;
16.4 触发器的优缺点
优点:
- 1、触发器可以确保数据的完整性。
- 2、触发器可以帮助我们记录操作日志。
- 3、触发器还可以用在操作数据前,对数据进行合法性检查。
缺点:
- 1、触发器最大的一个问题就是可读性差。
- 2、相关数据的变更,可能会导致触发器出错。
16.5、注意点
注意,如果在子表中定义了外键约束,并且外键指定了ON UPDATE/DELETE CASCADE/SET NULL子句,此时修改父表被引用的键值或删除父表被引用的记录行时,也会引起子表的修改和删除操作,此时基于子表的UPDATE和DELETE语句定义的触发器并不会被激活。外键约束引起的子表的修改删除操作并不会激活子表的触发器。
17、MySQL 8.0其它新特性(了解)
17.1、窗口函数
窗口函数语法结构:
函数 OVER([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
OR
函数 OVER 窗口名 … WINDOW 窗口名 AS ([PARTITION BY 字段名 ORDER BY 字段名 ASC|DESC])
OVER 关键字指定函数窗口的范围。如果省略后面括号中的内容,则窗口会包含满足WHERE条件的所有记录,窗口函数会基于所有满足WHERE条件的记录进行计算。如果OVER关键字后面的括号不为空,则可以使用如下语法设置窗口。
窗口名:为窗口设置一个别名,用来标识窗口。
PARTITION BY子句:指定窗口函数按照哪些字段进行分组。分组后,窗口函数可以在每个分组中分别执行。
ORDER BY子句:指定窗口函数按照哪些字段进行排序。执行排序操作使窗口函数按照排序后的数据记录的顺序进行编号。
FRAME子句:为分区中的某个子集定义规则,可以用来作为滑动窗口使用。
1、序号函数
:
ROW_NUMBER()
函数----能够对数据中的序号进行顺序显示。
RANK()
函数----能够对序号进行并列排序,并且会跳过重复的序号,比如序号为1、1、3。
DENSE_RANK()
函数----对序号进行并列排序,并且不会跳过重复的序号,比如序号为1、1、2。
2、分布函数
:
PERCENT_RANK()
函数----是等级值百分比函数。按照如下方式进行计算。(rank - 1) / (rows - 1)
CUME_DIST()
函数----主要用于查询小于或等于某个值的比例。
3、前后函数
:
LAG(expr,n)
函数----返回当前行的前n行的expr的值。
LEAD(expr,n)
函数----返回当前行的后n行的expr的值。
4、首尾函数
:
FIRST_VALUE(expr)
函数----返回第一个expr的值。
LAST_VALUE(expr)
函数----返回最后一个expr的值。
5、其他函数
:
NTH_VALUE(expr,n)
函数----返回第n个expr的值。
NTILE(n)
函数----将分区中的有序数据分为n个桶,记录桶编号。
小结
:
窗口函数的特点是可以分组,而且可以在分组内排序。另外,窗口函数不会因为分组而减少原表中的行数,这对我们在原表数据的基础上进行统计和排序非常有用。
17.2、公用表表达式
公用表表达式(或通用表表达式)简称为CTE(Common Table Expressions)。CTE是一个命名的临时结果集,作用范围是当前语句。CTE可以理解成一个可以复用的子查询,当然跟子查询还是有点区别的,CTE可以引用其他CTE,但子查询不能引用其他子查询。所以,可以考虑代替子查询。
依据语法结构和执行方式的不同,公用表表达式分为 普通公用表表达式
和 递归公用表表达式
2 种。
17.2.1、普通公用表表达式
普通公用表表达式的语法结构
:
WITH CTE名称
AS (子查询)
SELECT|DELETE|UPDATE 语句;
#查询员工所在的部门的详细信息。
#子查询实现
SELECT * FROM departments
WHERE department_id IN (
SELECT DISTINCT department_id
FROM employees
);
#普通公用表表达式实现
WITH emp_dept_id AS (SELECT DISTINCT department_id FROM employees)
SELECT *
FROM departments d JOIN emp_dept_id e
ON d.department_id = e.department_id;
公用表表达式可以起到子查询的作用。以后如果遇到需要使用子查询的场景,你可以在查询之前,先定义公用表表达式,然后在查询中用它来代替子查询。而且,跟子查询相比,公用表表达式有一个优点,就是定义过公用表表达式之后的查询,可以像一个表一样多次引用公用表表达式,而子查询则不能。
17.2.2、递归公用表表达式
递归公用表表达式也是一种公用表表达式,只不过,除了普通公用表表达式的特点以外,它还有自己的特点,就是可以调用自己。它的语法结构是:
WITH RECURSIVE
CTE名称 AS (子查询)
SELECT|DELETE|UPDATE 语句;
递归公用表表达式由 2 部分组成,分别是种子查询和递归查询,中间通过关键字 UNION [ALL]进行连接。这里的种子查询,意思就是获得递归的初始值。这个查询只会运行一次,以创建初始数据集,之后递归查询会一直执行,直到没有任何新的查询数据产生,递归返回。
案例:针对于我们常用的employees表,包含employee_id,last_name和manager_id三个字段。如果a是b的管理者,那么,我们可以把b叫做a的下属,如果同时b又是c的管理者,那么c就是b的下属,是a的下下属。
下面我们尝试用查询语句列出所有具有下下属身份的人员信息。
如果用我们之前学过的知识来解决,会比较复杂,至少要进行 4 次查询才能搞定:
第一步,先找出初代管理者,就是不以任何别人为管理者的人,把结果存入临时表;
第二步,找出所有以初代管理者为管理者的人,得到一个下属集,把结果存入临时表;
第三步,找出所有以下属为管理者的人,得到一个下下属集,把结果存入临时表。
第四步,找出所有以下下属为管理者的人,得到一个结果集。
如果第四步的结果集为空,则计算结束,第三步的结果集就是我们需要的下下属集了,否则就必须继续进行第四步,一直到结果集为空为止。比如上面的这个数据表,就需要到第五步,才能得到空结果集。而且,最后还要进行第六步:把第三步和第四步的结果集合并,这样才能最终获得我们需要的结果集。
如果用递归公用表表达式,就非常简单了。我介绍下具体的思路。
用递归公用表表达式中的种子查询,找出初代管理者。字段 n 表示代次,初始值为 1,表示是第一代管理者。用递归公用表表达式中的递归查询,查出以这个递归公用表表达式中的人为管理者的人,并且代次的值加 1。直到没有人以这个递归公用表表达式中的人为管理者了,递归返回。在最后的查询中,选出所有代次大于等于 3 的人,他们肯定是第三代及以上代次的下属了,也就是下下属了。这样就得到了我们需要的结果集。这里看似也是 3 步,实际上是一个查询的 3 个部分,只需要执行一次就可以了。而且也不需要用临时表保存中间结果,比刚刚的方法简单多了。
WITH RECURSIVE cte
AS
(
SELECT employee_id,last_name,manager_id,1 AS n FROM employees WHERE employee_id = 100
-- 种子查询,找到第一代领导
UNION ALL
SELECT a.employee_id,a.last_name,a.manager_id,n+1 FROM employees AS a JOIN cte
ON (a.manager_id = cte.employee_id) -- 递归查询,找出以递归公用表表达式的人为领导的人
)
SELECT employee_id,last_name FROM cte WHERE n >= 3;
总之,递归公用表表达式对于查询一个有共同的根节点的树形结构数据,非常有用。它可以不受层级的限制,轻松查出所有节点的数据。如果用其他的查询方式,就比较复杂了。
小结
:
公用表表达式的作用是可以替代子查询,而且可以被多次引用。递归公用表表达式对查询有一个共同根节点的树形结构数据非常高效,可以轻松搞定其他查询方式难以处理的查询。