diff --git "a/04 \346\235\216\346\230\216\345\201\245/20231025 MySQL\350\277\233\351\230\266\347\254\224\350\256\260\357\274\210\346\200\273\347\273\223\357\274\211.md" "b/04 \346\235\216\346\230\216\345\201\245/20231025 MySQL\350\277\233\351\230\266\347\254\224\350\256\260\357\274\210\346\200\273\347\273\223\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..983cfcd272b3241c88b4cf83b25c3f7835a87fed --- /dev/null +++ "b/04 \346\235\216\346\230\216\345\201\245/20231025 MySQL\350\277\233\351\230\266\347\254\224\350\256\260\357\274\210\346\200\273\347\273\223\357\274\211.md" @@ -0,0 +1,1095 @@ +## 一. 数据库设计 + +### 表之间的关系: + + 1. 一对一:将其中任一表中的主键,放到另一张表当外键 + 2. 一对多:将一所在表的主键,放到多的表当外键 + 3. 多对多:必须有第三张表,将两张表的主键放到第三张表当外键 + +### ER图: + +​ 实体关系图,提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。 + +​ ER图的三要素:实体、属性、联系 + +### powerDesigner使用: + +​ 第一步,创建概念模型(类似ER图,以用户的角度) CDM + +​ 第二步,转换成逻辑模型(以计算机的角度) LDM + +​ 第三步,转换成物理模型(以数据库的角度) PDM + +​ 第四步,生成 DDL + +### RBAC: + +​ 一种数据库设计思想,根据==通用的角度==设计数据库(基于角色的访问控制权限) + +组成: + +- 用户(`User`):定义需要权限鉴别操作的主体 +- 角色(`Role`):定义一批相关联权限的集合 +- 权限(`Permission`):定义需要鉴别权限操作的对象 +- 用户-角色关联(`User-Role`):定义用户所具有的角色 +- 角色-权限关联(`Role-Permission`):定义角色所具有的权限 + +### SKU: + +​ 由一个商品的不同属性组成一个最小存储单位,可以理解为就是一个商品具体规格,一般用于电商网站 + +如:白色的苹果16 512G内存 10核CPU + +## 二. 视图 + +#### 视图介绍: + +​ 视图(view)是一个虚拟表,为其命名后,用户使用时只需查此虚拟表即可获取结果集,并可以将其当作表来使用。此虚拟表的数据,来源于原表当中。 + +#### 视图的作用: + +​ 简化代码,可以把重复使用的查询封装成视图重复使用,同时可以使复杂的查询易于理解和使用。 + +​ 安全原因,如果一张表中有很多数据不希望让人看到,此时可以使用视图。 + +#### 创建视图语句 + +~~~ mysql +create or replace view 视图名 +as +select name,age from student; +# 查看表和视图 +show full tables; +~~~ + +#### 修改视图语句 + +~~~ mysql +alter view 视图名 as 新的查询语句 +~~~ + + #### 更新视图: + +​ 因为视图表是根据原表数据来的,所有对视图表进行增、删、改操作,也就相当于对原表进行操作。即,有一定的操作是会执行失败的。 + +​ ==所有,一般情况下,最好将视图作为查询数据的虚拟表,而不是通过视图更新数据。== + +#### 重命名视图 + +~~~ mysql +rename table 视图名 to 新视图名; +~~~ + +#### 删除视图 + +~~~ MySQL +drop view if exists 视图名; +~~~ + + + +## 三. 存储过程 + +**简单理解:就是将sql语句分装成一个函数(方法)** + +#### 创建存储过程格式 + +~~~ mysql +delimiter $$ # 设置$$为语句结尾标识 +create procedure proc01() # 创建存储过程,并命名 +begin + select * from student; # 要执行的sql语句 +end $$ # $$ 到这表示语句结尾 +delimiter ; # 将语句结尾标识设回 ; + +# 调用存储过程 +call proc01() +~~~ + +#### 变量定义 + +1.局部变量:用户自定义,==在begin与end块中有效== + +~~~ mysql +delimiter $$ +create procedure proc02() +begin + declare var_name varchar(5) default '张三'; # 定义变量,默认值为‘张三’ + set var_name = '李四'; # 改变该变量的值 + select var_name; # 李四 +end $$ + +# 调用存储过程 +call proc02(); +~~~ + +2.用户变量:用户自定义,==当前会话(连接)有效==,类似java的成员变量 + +~~~ mysql +delimiter $$ +create procedure proc03() +begin + set var_name = '李四'; # 设置变量值为‘李四’ +end $$ + +# 调用存储过程 +call proc03(); # 运行成功,设置了变量的值 +select var_name; # 李四 +~~~ + +3.系统变量-全局变量:由系统提供,==在整个数据库有效== + +~~~ mysql +# 查看所有全局变量 +show global variables; +# 查看指定的全局变量 +select @@global.全局变量名; +# 修改全局变量值 +set global 全局变量名 = 要修改的值; +set @@global.全局变量名 = 要修改的值; +~~~ + +4.系统变量 - 会话变量:由系统提供,==当前会话(连接)有效== + +~~~ mysql +# 查看所有全局变量 +show session variables; +# 查看指定的全局变量 +select @@session.全局变量名; +# 修改全局变量值 +set session 全局变量名 = 要修改的值; +set @@session.全局变量名 = 要修改的值; +~~~ + +#### 存储过程的传参 + +**in :表示传入的参数** + +只有一个参数 + +~~~ mysql +delimiter $$ +create procedure proc04(in name varchar(5)) # in 表示传入参数 +begin + select * from student where student.name = name; # 将传递的参数赋值并利用 +end $$ +delimiter ; + +call proc04("张三"); # 传递参数,执行语句,得到张三的所有信息 +call proc04("李四"); # 传递参数,执行语句,得到李四的所有信息 +~~~ + +有多个参数 + +~~~ mysql +# 需求:查询指定部门,大于多少薪资的员工信息 (两个参数但是可变的) +delimiter $$ +create procedure proc05(in param_dname varchar(10),in param_sal decimal(7,2)) +begin + select * from dept d,emp e where d.dname = param_dname and e.sal > param_sal; +end $$ +delimiter ; + +call proc05("学工部",20000); # 调用分装好的语句,查询学工部薪资大于20000的员工信息 +call proc05("销售部",10000); +~~~ + +**out :表示从存储过程内部传值给调用者** + +一个out参数 + +~~~ mysql +# 需求:传入员工编号,返回员工姓名 +delimiter $$ +create procedure proc06(in in_empno int, out out_ename varchar(5)) +begin + select ename into out_ename from emp where empno = in_empno; #into 表示将查询的结果赋值给out_ename +end $$ +delimiter ; + +call proc06(1001,@o_ename); # 传入员工编号,定义一个变量接收返回的结果 +select @o_ename; # 查询变量值 +~~~ + +多个out参数 + +~~~ mysql +# 需求:传入员工编号,返回员工姓名、薪资 +delimiter $$ +create procedure proc07(in in_empno int, out out_ename varchar(5), out out_sal decimal(7,2)) +begin + select ename,sal into out_ename,out_sal from emp where empno = in_empno; # 赋值字段一一对应,字段之间逗号隔开 +end $$ +delimiter ; + +call proc07(1001,@o_ename,@o_sal); # 定义变量接收返回结果 +select @o_ename; +select @o_sal; +~~~ + +**inout:表示从外部传入的参数进过修改后可以返回的变量,既可以使用传入变量的值,也可以修改传入变量的值** + +案例一 + +~~~ mysql +# 需求:传入一个数字,传出这个数字的10倍值 +delimiter $$ +create procedure proc08(inout num int) +begin + set num = num * 10; +end $$ +delimiter ; + +set @inout_num = 2; # 设一个变量用来接收变化的值,并给初始值 +call proc08(@inout_num); # 调用存储过程,并将值传入 +select @inout_num; # 得到改变的值 20 +~~~ + +案例二 + +~~~ mysql +# 需求:传入员工名,拼接部门号,传入薪资,求出年薪 +# 拼接要求: 30_张三 +delimiter $$ +create procedure proc08(inout inout_ename varchar(5),inout inout_sal int) +begin + # concat_ws 是一个拼接的函数,可以指定拼接格式 + select concat_ws('_',deptno,ename) into inout_ename from emp where emp.ename = inout_ename; + set inout_sal = inout_sal * 12; +end $$ +delimiter ; +# 定义变量,并赋初始值 +set @inout_ename = "张三"; +set @inout_sal = 3000; +# 调用存储过程,得到结果 +call proc08(@inout_ename,@inout_sal); +# 打印结果 +select @inout_ename; +select @inout_sal; +~~~ + +#### 流程控制-判断 + +#### if 语句 + +~~~ mysql +# 输入员工姓名,判断工资的等级 +delimiter $$ +create procedure proc09(in in_ename varchar(5)) +begin + # 定义两个变量,分别接收薪资、等级 + declare var_sal decimal(7,2); + declare result varchar(20); + # 判断 + if var_sal < 10000 + then + set result = '试用薪资'; + elseif var_sal < 20000 + then + set result = '转正薪资'; + else + set result = '元老薪资'; + end if; + select result; +end $$ +delimiter ; +# 调用存储过程 +call proc09('张三'); # 试用薪资 +~~~ + +#### case 语句 + +~~~ mysql +# 支付方式选择 +delimiter $$ +create procedure proc10(in pay_type int) +begin + case pay_type + when 1 then select '微信支付'; + when 2 then select '支付宝支付'; + when 3 then select '银行卡支付'; + else select '其他支付方式'; + end case; +end $$ +delimiter ; +call proc10(1); # 微信支付 +~~~ + +#### 流程语句-循环-while + +~~~ mysql +# 循环插入n条数据 +delimiter $$ +create procedure proc11(in insertCount int) +begin + declare i int default 1; + label:while i <= insertCount do + insert into user(uid,username,psw) values(i,concat('user-',i),'123456'); + #if i = 5 then + #leave lable; 跳出循环 + #或 + #iterate lable; 跳出本次循环,继续循环上面语句,此时就会死循环,i始终是5 + #end if; + set i = i + 1; + end while label; + select '循环结束'; +end $$ +delimiter ; +call proc11(10) #循环插入10条数据 +~~~ + +#### repeat (死循环) + +~~~ mysql +delimiter $$ +create procedure proc12(in insertCount int) +begin + declare i int default 1; + label:repeat + insert into user(uid,username,psw) values(i,concat('user-',i),'123456'); + set i = i + 1; + until i > insertCount; # 当 i>10 的时候才跳出循环,如果没有这一句,就是死循环 + end repeat label; + select '循环结束'; +end $$ +delimiter ; +call proc12(10) +~~~ + +#### loop(死循环) + +~~~ mysql +delimiter $$ +create procedure proc13(in insertCount int) +begin + declare i int default 1; + label:loop + insert into user(uid,username,psw) values(i,concat('user-',i),'123456'); + set i = i + 1; + # 判断,当 i>10 的时候跳出循环,如果没有这一段,就是死循环 + /* if i > insertCount + then + leave label; 跳出循环 + end if; */ + end loop label; + select '循环结束'; +end $$ +delimiter ; +call proc12(10) +~~~ + +**注意:** + + 1. if 和 case 的区别:case可以写在 select 语句中(如判断男女),而 if 不行 + + 2. 定义变量 declare 关键字 前面不能有其他语句 + +3. 如果要用 leave(跳出整个) 或 iterate(跳出本次)两个关键字,就要加标记符 + +#### 存储函数 + +语法: + +```sql +delimiter $$ +create function f1(myid int) # 函数传参默认是 in 类型,需要省略 + returns varchar(20) # 返回值类型,只能传出一个 + deterministic # 指名函数内有无 SQL语句 + contains sql # no sql 就是说明没有 SQL语句 +begin + return (select name from student where id = myid); +end $$ +delimiter ; +``` + +#### 游标(指针) + +​ 游标(cursor)是用来存储查询结果集的数据类型,在存储过程和函数中可以使用光标对结果集进行循环的处理。光标的使用包括光标的声明、open、fetch 和 close + +1. 声明游标 + +```sql +declare 游标名 cursor for select * from student; +``` + +2. 打开游标 + +```sql +open 游标名; +``` + +3. 使用游标 + +```sql +fetch 游标名 into 变量1,变量...; +# 注意: +# 变量的个数是根据定义游标时查询的字段个数决定 +# 每 fetch 一次取一行,依次向下取 +``` + +4. 关闭游标 + +```sql +close 游标名; +``` + +例: + +~~~ mysql +# 需求:输入一个部门名,查询该部门员工编号、名字、薪资,将查询结果集添加游标 +delimiter $$ +create procedure proc14_cursor(in in_dname varchar(10)) +begin + # 定义局部变量,接收查询字段的数据 + declare var_empno int; + declare var_ename varchar(5); + declare var_sal decimal(7,2); + # 声明游标 + declare my_cursor cursor for + select empno,ename,sal + from dept d,emp e + where d.deptno = e.deptno and d.dname = in_dname; + # 打开游标 + open my_cursor; + # 通过游标获取值,因为一次只能获取一行的值,所有要循环 + # 因为loop是死循环,当游标指向最后一行数据的下面时,会查不到数据,报1329错误,不影响结果 + label:loop + fetch my_cursor into var_empno,var_ename,var_sal; + select var_empno,var_ename,var_sal; + end loop label; + # 关闭游标 + close my_cursor; +end $$ +delimiter ; +call proc14_cursor('销售部') +~~~ + +#### 异常处理-handler(解决上面异常问题) + +~~~ mysql +# 在语法中,变量声明、游标声明、handler声明是必须按照先后顺序书写,否则会报错 +delimiter $$ +create procedure proc15_cursor_handler(in in_dname varchar(10)) +begin + # 定义局部变量,接收查询字段的数据 + declare var_empno int; + declare var_ename varchar(5); + declare var_sal decimal(7,2); + -- 定义标记值 + declare flag int default 1; + # 声明游标 + declare my_cursor cursor for + select empno,ename,sal + from dept d,emp e + where d.deptno = e.deptno and d.dname = in_dname; + -- 定义异常的处理方式 + /* + 1.异常处理完之后程序该怎么执行 + continue : 继续执行剩余的代码 + exit : 直接终止程序 + undo :不支持 + 2.触发条件 + 条件码:1329 等 + 条件名:SQLWARNING NOT FOUND SQLEXCEPTION + 3.异常触发之后执行什么代码 + 设置 flag 的值为 0 + */ + declare continue handler for 1329 set flag = 0; + # 打开游标 + open my_cursor; + # 通过游标获取值,因为一次只能获取一行的值,所有要循环 + # 因为loop是死循环,当游标指向最后一行数据的下面时,会查不到数据,报1329错误,不影响结果 + label:loop + fetch my_cursor into var_empno,var_ename,var_sal; + -- 判断flag的值,如果为1,则执行,为0,不执行 + if flag = 1 then + select var_empno,var_ename,var_sal; + else + leave label; + end if; + end loop label; + # 关闭游标 + close my_cursor; +end $$ +delimiter ; +call proc15_cursor_handler('销售部') +~~~ + +## 四. 单行函数 + +#### 1.随机数 + +```mysql +select floor(rand() * 10) # floor:向下取整 [0,10) +``` + +#### 2.获取字符串长度 + +```mysql +select length('字符串') # 一个汉字等于3个字节 +``` + +#### 3.拼接字符串 + +语法1: + +```mysql +select concat('1','2','闽大') # 12闽大 +``` + +语法2: + +```mysql +select concat_ws('_','大','家','好') # 指定字符拼接 大_家_好 +``` + +#### 4.去除字符串中的空格 + +```mysql +select trim('字符串') # 去除两端的空格 +select rtrim('字符串') # 去除右边的空格 +select ltrim('字符串') # 去除左边的空格 +``` + +#### 5.替换函数 + +```mysql +replace('原始字符串','要替换的字符串','替换成什么字符串') +replace('abc:afg','a','b') # bbc:bfg +``` + +#### 6.将字符串改为全大写或全小写 + +```mysql +select upper('abcDEfg') # ABCDEFG +select lower('ABcDEFg') # abcdefg +``` + +#### 7.截取字符串 + +```mysql +select left('123456',3) # 123 +select right('123456',3) # 456 +select substring('350781202309272566',7,8) # 20230927 +#表示从左往右数,第一个冒号截取,以该冒号结束截取 +select substring_index('兴趣:爱好:打羽毛球',':',1) #兴趣 +#表示从右往左数,第一个冒号截取,以该冒号结束截取 +select substring_index('兴趣:爱好:打羽毛球',':',-1) #打羽毛球 +``` + +## 五. 日期函数 + +```mysql +now() # 获取当前时间 +datediff('第一个时间','第二个时间') # 获取时间差(天数) +``` + +#### 1.获取时间 + +```sql +# 获取当前日期时间 +select NOW(); # 2021-04-02 09:25:29 +# 获取当前日期 +SELECT CURDATE(); # 2021-04-02 +# 获取当前时间 +SELECT CURTIME(); # 09:26:10 + +# 对于时间2021-04-02 09:25:29,分别获取其年、月、日、时、分、秒 +SELECT EXTRACT(YEAR FROM NOW()); # 2021 +SELECT EXTRACT(MONTH FROM NOW()); # 4 +SELECT EXTRACT(DAY FROM NOW()); # 2 +SELECT EXTRACT(HOUR FROM NOW()); # 9 +SELECT EXTRACT(MINUTE FROM NOW()); # 25 +SELECT EXTRACT(SECOND FROM NOW()); # 29 + +# 或者从日期格式字符串中获取 +SELECT EXTRACT(SECOND FROM '2021-04-02 10:37:14.123456'); # 14 +``` + +#### 2.日期增加、减少 + +```sql +# 时间减少1小时(前一小时) +select date_sub(now(), INTERVAL 1 hour); + +# 日期增加1天 +select date_add(now(), INTERVAL 1 day); + +# 其他间隔 +INTERVAL 1 YEAR +INTERVAL 1 MONTH +INTERVAL 1 DAY +INTERVAL 1 HOUR +INTERVAL 1 MINUTE +INTERVAL 1 SECOND +``` + +------ + +#### 3.日期格式化、字符串转日期 + +```sql +# 格式化参考: +select DATE_FORMAT(now(),'%Y-%m-%d %H:%i:%s'); +select DATE_FORMAT(now(),'%Y-%m-%d %H:00:00'); + +#字符串转日期 +select str_to_date('2021-04-02 10:37:14', '%Y-%m-%d %H:%i:%s'); # 2021-04-02 10:37:14 +``` + +#### 其他参考函数 + +以下较全的MySQL日期函数可做参考(原文链接:https://blog.csdn.net/qinshijangshan/article/details/72874667) + +```sql +-- MySQL日期时间处理函数 +-- 当前日期:2017-05-12(突然发现今天512,是不是会拉防空警报) +SELECT NOW() FROM DUAL;-- 当前日期时间:2017-05-12 11:41:47 +-- 在MySQL里也存在和Oracle里类似的dual虚拟表:官方声明纯粹是为了满足select ... from...这一习惯问题,mysql会忽略对该表的引用。 +-- 那么MySQL中就不用DUAL了吧。 +SELECT NOW();-- 当前日期时间:2017-05-12 11:41:55 +-- 除了 now() 函数能获得当前的日期时间外,MySQL 中还有下面的函数: +SELECT CURRENT_TIMESTAMP();-- 2017-05-15 10:19:31 +SELECT CURRENT_TIMESTAMP;-- 2017-05-15 10:19:51 +SELECT LOCALTIME();-- 2017-05-15 10:20:00 +SELECT LOCALTIME;-- 2017-05-15 10:20:10 +SELECT LOCALTIMESTAMP();-- 2017-05-15 10:20:21(v4.0.6) +SELECT LOCALTIMESTAMP;-- 2017-05-15 10:20:30(v4.0.6) +-- 这些日期时间函数,都等同于 now()。鉴于 now() 函数简短易记,建议总是使用 now()来替代上面列出的函数。 + +SELECT SYSDATE();-- 当前日期时间:2017-05-12 11:42:03 +-- sysdate() 日期时间函数跟 now() 类似, +-- 不同之处在于:now() 在执行开始时值就得到了;sysdate() 在函数执行时动态得到值。 +-- 看下面的例子就明白了: +SELECT NOW(), SLEEP(3), NOW(); +SELECT SYSDATE(), SLEEP(3), SYSDATE(); + + +SELECT CURDATE();-- 当前日期:2017-05-12 +SELECT CURRENT_DATE();-- 当前日期:等同于 CURDATE() +SELECT CURRENT_DATE;-- 当前日期:等同于 CURDATE() + +SELECT CURTIME();-- 当前时间:11:42:47 +SELECT CURRENT_TIME();-- 当前时间:等同于 CURTIME() +SELECT CURRENT_TIME;-- 当前时间:等同于 CURTIME() + +-- 获得当前 UTC 日期时间函数 +SELECT UTC_TIMESTAMP(), UTC_DATE(), UTC_TIME() +-- MySQL 获得当前时间戳函数:current_timestamp, current_timestamp() +SELECT CURRENT_TIMESTAMP, CURRENT_TIMESTAMP();-- 2017-05-15 10:32:21 | 2017-05-15 10:32:21 + + +-- MySQL 日期时间 Extract(选取) 函数 +SET @dt = '2017-05-15 10:37:14.123456'; +SELECT DATE(@dt);-- 获取日期:2017-05-15 +SELECT TIME('2017-05-15 10:37:14.123456');-- 获取时间:10:37:14.123456 +SELECT YEAR('2017-05-15 10:37:14.123456');-- 获取年份 +SELECT MONTH('2017-05-15 10:37:14.123456');-- 获取月份 +SELECT DAY('2017-05-15 10:37:14.123456');-- 获取日 +SELECT HOUR('2017-05-15 10:37:14.123456');-- 获取时 +SELECT MINUTE('2017-05-15 10:37:14.123456');-- 获取分 +SELECT SECOND('2017-05-15 10:37:14.123456');-- 获取秒 +SELECT MICROSECOND('2017-05-15 10:37:14.123456');-- 获取毫秒 +SELECT QUARTER('2017-05-15 10:37:14.123456');-- 获取季度 +SELECT WEEK('2017-05-15 10:37:14.123456');-- 20 (获取周) +SELECT WEEK('2017-05-15 10:37:14.123456', 7);-- ****** 测试此函数在MySQL5.6下无效 +SELECT WEEKOFYEAR('2017-05-15 10:37:14.123456');-- 同week() +SELECT DAYOFYEAR('2017-05-15 10:37:14.123456');-- 135 (日期在年度中第几天) +SELECT DAYOFMONTH('2017-05-15 10:37:14.123456');-- 5 (日期在月度中第几天) +SELECT DAYOFWEEK('2017-05-15 10:37:14.123456');-- 2 (日期在周中第几天;周日为第一天) +SELECT WEEKDAY('2017-05-15 10:37:14.123456');-- 0 +SELECT WEEKDAY('2017-05-21 10:37:14.123456');-- 6(与dayofweek()都表示日期在周的第几天,只是参考标准不同,weekday()周一为第0天,周日为第6天) +SELECT YEARWEEK('2017-05-15 10:37:14.123456');-- 201720(年和周) + +SELECT EXTRACT(DAY_MINUTE FROM '2017-05-15 10:37:14.123456');-- 151037(日时分) +SELECT EXTRACT(DAY_SECOND FROM '2017-05-15 10:37:14.123456');-- 15103714(日时分秒) +SELECT EXTRACT(DAY_MICROSECOND FROM '2017-05-15 10:37:14.123456');-- 15103714123456(日时分秒毫秒) +SELECT EXTRACT(HOUR_MINUTE FROM '2017-05-15 10:37:14.123456');-- 1037(时分) +SELECT EXTRACT(HOUR_SECOND FROM '2017-05-15 10:37:14.123456');-- 103714(时分秒) +SELECT EXTRACT(HOUR_MICROSECOND FROM '2017-05-15 10:37:14.123456');-- 103714123456(日时分秒毫秒) +SELECT EXTRACT(MINUTE_SECOND FROM '2017-05-15 10:37:14.123456');-- 3714(分秒) +SELECT EXTRACT(MINUTE_MICROSECOND FROM '2017-05-15 10:37:14.123456');-- 3714123456(分秒毫秒) +SELECT EXTRACT(SECOND_MICROSECOND FROM '2017-05-15 10:37:14.123456');-- 14123456(秒毫秒) +-- MySQL Extract() 函数除了没有date(),time() 的功能外,其他功能一应具全。 +-- 并且还具有选取‘day_microsecond' 等功能。 +-- 注意这里不是只选取 day 和 microsecond,而是从日期的 day 部分一直选取到 microsecond 部分。 + + +SELECT DAYNAME('2017-05-15 10:37:14.123456');-- Monday(返回英文星期) +SELECT MONTHNAME('2017-05-15 10:37:14.123456');-- May(返回英文月份) +SELECT LAST_DAY('2016-02-01');-- 2016-02-29 (返回月份中最后一天) +SELECT LAST_DAY('2016-05-01');-- 2016-05-31 + +-- DATE_ADD(date,INTERVAL expr type) 从日期加上指定的时间间隔 +-- type参数可参考:http://www.w3school.com.cn/sql/func_date_sub.asp +SELECT DATE_ADD('2017-05-15 10:37:14.123456',INTERVAL 1 YEAR);-- 表示:2018-05-15 10:37:14.123456 +SELECT DATE_ADD('2017-05-15 10:37:14.123456',INTERVAL 1 QUARTER);-- 表示:2017-08-15 10:37:14.123456 +SELECT DATE_ADD('2017-05-15 10:37:14.123456',INTERVAL 1 MONTH);-- 表示:2017-06-15 10:37:14.123456 +SELECT DATE_ADD('2017-05-15 10:37:14.123456',INTERVAL 1 WEEK);-- 表示:2017-05-22 10:37:14.123456 +SELECT DATE_ADD('2017-05-15 10:37:14.123456',INTERVAL 1 DAY);-- 表示:2017-05-16 10:37:14.123456 +SELECT DATE_ADD('2017-05-15 10:37:14.123456',INTERVAL 1 HOUR);-- 表示:2017-05-15 11:37:14.123456 +SELECT DATE_ADD('2017-05-15 10:37:14.123456',INTERVAL 1 MINUTE);-- 表示:2017-05-15 10:38:14.123456 +SELECT DATE_ADD('2017-05-15 10:37:14.123456',INTERVAL 1 SECOND);-- 表示:2017-05-15 10:37:15.123456 +SELECT DATE_ADD('2017-05-15 10:37:14.123456',INTERVAL 1 MICROSECOND);-- 表示:2017-05-15 10:37:14.123457 + + +-- DATE_SUB(date,INTERVAL expr type) 从日期减去指定的时间间隔 +SELECT DATE_SUB('2017-05-15 10:37:14.123456',INTERVAL 1 YEAR);-- 表示:2016-05-15 10:37:14.123456 +SELECT DATE_SUB('2017-05-15 10:37:14.123456',INTERVAL 1 QUARTER);-- 表示:2017-02-15 10:37:14.123456 +SELECT DATE_SUB('2017-05-15 10:37:14.123456',INTERVAL 1 MONTH);-- 表示:2017-04-15 10:37:14.123456 +SELECT DATE_SUB('2017-05-15 10:37:14.123456',INTERVAL 1 WEEK);-- 表示:2017-05-08 10:37:14.123456 +SELECT DATE_SUB('2017-05-15 10:37:14.123456',INTERVAL 1 DAY);-- 表示:2017-05-14 10:37:14.123456 +SELECT DATE_SUB('2017-05-15 10:37:14.123456',INTERVAL 1 HOUR);-- 表示:2017-05-15 09:37:14.123456 +SELECT DATE_SUB('2017-05-15 10:37:14.123456',INTERVAL 1 MINUTE);-- 表示:2017-05-15 10:36:14.123456 +SELECT DATE_SUB('2017-05-15 10:37:14.123456',INTERVAL 1 SECOND);-- 表示:2017-05-15 10:37:13.123456 +SELECT DATE_SUB('2017-05-15 10:37:14.123456',INTERVAL 1 MICROSECOND);-- 表示:2017-05-15 10:37:14.123455 + +-- 经特殊日期测试,DATE_SUB(date,INTERVAL expr type)可放心使用 +SELECT DATE_SUB(CURDATE(),INTERVAL 1 DAY);-- 前一天:2017-05-11 +SELECT DATE_SUB(CURDATE(),INTERVAL -1 DAY);-- 后一天:2017-05-13 +SELECT DATE_SUB(CURDATE(),INTERVAL 1 MONTH);-- 一个月前日期:2017-04-12 +SELECT DATE_SUB(CURDATE(),INTERVAL -1 MONTH);-- 一个月后日期:2017-06-12 +SELECT DATE_SUB(CURDATE(),INTERVAL 1 YEAR);-- 一年前日期:2016-05-12 +SELECT DATE_SUB(CURDATE(),INTERVAL -1 YEAR);-- 一年后日期:20178-06-12 +-- MySQL date_sub() 日期时间函数 和 date_add() 用法一致,并且可以用INTERNAL -1 xxx的形式互换使用; +-- 另外,MySQL 中还有两个函数 subdate(), subtime(),建议,用 date_sub() 来替代。 + +-- MySQL 另类日期函数:period_add(P,N), period_diff(P1,P2) +-- 函数参数“P” 的格式为“YYYYMM” 或者 “YYMM”,第二个参数“N” 表示增加或减去 N month(月)。 +-- MySQL period_add(P,N):日期加/减去N月。 +SELECT PERIOD_ADD(201705,2), PERIOD_ADD(201705,-2);-- 201707 20170503 +-- period_diff(P1,P2):日期 P1-P2,返回 N 个月。 +SELECT PERIOD_DIFF(201706, 201703);-- +-- datediff(date1,date2):两个日期相减 date1 - date2,返回天数 +SELECT DATEDIFF('2017-06-05','2017-05-29');-- 7 +-- TIMEDIFF(time1,time2):两个日期相减 time1 - time2,返回 TIME 差值 +SELECT TIMEDIFF('2017-06-05 19:28:37', '2017-06-05 17:00:00');-- 02:28:37 + + +-- MySQL日期转换函数 +SELECT TIME_TO_SEC('01:00:05'); -- 3605 +SELECT SEC_TO_TIME(3605);-- 01:00:05 + +-- MySQL (日期、天数)转换函数:to_days(date), from_days(days) +SELECT TO_DAYS('0000-00-00'); -- NULL +SELECT TO_DAYS('2017-06-05'); -- 736850 +SELECT FROM_DAYS(0); -- '0000-00-00' +SELECT FROM_DAYS(736850); -- '2017-06-05' + +-- MySQL Str to Date (字符串转换为日期)函数:str_to_date(str, format) +SELECT STR_TO_DATE('06.05.2017 19:40:30', '%m.%d.%Y %H:%i:%s');-- 2017-06-05 19:40:30 +SELECT STR_TO_DATE('06/05/2017', '%m/%d/%Y'); -- 2017-06-05 +SELECT STR_TO_DATE('2017/12/3','%Y/%m/%d') -- 2017-12-03 +SELECT STR_TO_DATE('20:09:30', '%h:%i:%s') -- NULL(超过12时的小时用小写h,得到的结果为NULL) + +-- 日期时间格式化 +SELECT DATE_FORMAT('2017-05-12 17:03:51', '%Y年%m月%d日 %H时%i分%s秒');-- 2017年05月12日 17时03分51秒(具体需要什么格式的数据根据实际情况来;小写h为12小时制;) +SELECT TIME_FORMAT('2017-05-12 17:03:51', '%Y年%m月%d日 %H时%i分%s秒');-- 0000年00月00日 17时03分51秒(time_format()只能用于时间的格式化) +-- STR_TO_DATE()和DATE_FORMATE()为互逆操作 + +-- MySQL 拼凑日期、时间函数:makdedate(year,dayofyear), maketime(hour,minute,second) +SELECT MAKEDATE(2017,31); -- '2017-01-31' +SELECT MAKEDATE(2017,32); -- '2017-02-01' +SELECT MAKETIME(19,52,35); -- '19:52:35' + +-- MySQL 时区(timezone)转换函数:convert_tz(dt,from_tz,to_tz) +SELECT CONVERT_TZ('2017-06-05 19:54:12', '+08:00', '+00:00'); -- 2017-06-05 11:54:12 + + +-- MySQL (Unix 时间戳、日期)转换函数 +-- unix_timestamp(), unix_timestamp(date), from_unixtime(unix_timestamp), from_unixtime(unix_timestamp,format) +-- 将具体时间时间转为timestamp +SELECT UNIX_TIMESTAMP();-- 当前时间的时间戳:1494815779 +SELECT UNIX_TIMESTAMP('2017-05-15');-- 指定日期的时间戳:1494777600 +SELECT UNIX_TIMESTAMP('2017-05-15 10:37:14');-- 指定日期时间的时间戳:1494815834 + +-- 将时间戳转为具体时间 +SELECT FROM_UNIXTIME(1494815834);-- 2017-05-15 10:37:14 +SELECT FROM_UNIXTIME(1494815834, '%Y年%m月%d日 %h时%分:%s秒');-- 获取时间戳对应的格式化日期时间 + +-- MySQL 时间戳(timestamp)转换、增、减函数 +SELECT TIMESTAMP('2017-05-15');-- 2017-05-15 00:00:00 +SELECT TIMESTAMP('2017-05-15 08:12:25', '01:01:01');-- 2017-05-15 09:13:26 +SELECT DATE_ADD('2017-05-15 08:12:25', INTERVAL 1 DAY);-- 2017-05-16 08:12:25 +SELECT TIMESTAMPADD(DAY, 1, '2017-05-15 08:12:25');-- 2017-05-16 08:12:25; MySQL timestampadd() 函数类似于 date_add()。 + +SELECT TIMESTAMPDIFF(YEAR, '2017-06-01', '2016-05-15');-- -1 +SELECT TIMESTAMPDIFF(MONTH, '2017-06-01', '2016-06-15');-- -11 +SELECT TIMESTAMPDIFF(DAY, '2017-06-01', '2016-06-15');-- -351 +SELECT TIMESTAMPDIFF(HOUR, '2017-06-01 08:12:25', '2016-06-15 00:00:00');-- -8432 +SELECT TIMESTAMPDIFF(MINUTE, '2017-06-01 08:12:25', '2016-06-15 00:00:00');-- -505932 +SELECT TIMESTAMPDIFF(SECOND, '2017-06-01 08:12:25', '2016-06-15 00:00:00');-- -30355945 +``` + +## 六. 触发器 + +触发器(trigger)是与表有关的数据库对象,指在insert/update/delete之前(BEFORE)或之后(AFTER),触发并执行触发器中定义的SQL语句集合。 + +事件A 对user表新增一条数据 姓名name 年龄age 性别sex + +事件B 对userlogs记录一条user表的操作 new.name,new.age + +触发器的这种特性可以协助应用在数据库端确保数据的完整性 , 日志记录 , 数据校验等操作。 + +使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。**触发器只支持行级触发** + +### 触发器的类型 + +| 类型 | NEW和OLD | +| ------ | ---------------------------------------- | +| insert | new代表将要新增或已新增的数据 | +| update | old代表更新前的数据、new代表更新后的数据 | +| delete | old代表将要删除或已删除的数据 | + +### 语法 + +创建触发器 + +```sql +create trigger 触发器名称 +before/after(触发时机) insert/update/delete(触发类型) +on 表名 for each row -- 行级触发器 +begin + 触发的语句... +end; +``` + +查看 + +```sql +show triggers; +``` + +删除 + +```sql +drop trigger 触发器名称; +``` + +### 自己定义错误代码: + +```sql +signal sqlstate 'MD001' set message_text = '行数超出5行,不能新增'; +``` + +## 七. 窗口函数 + +mysql 8.0新增窗口函数(开窗函数)。 + +非聚合窗口函数:是相对于聚合函数来说的,特性为非聚合。一次只处理一行数据。 + +窗口聚合函数:窗口聚合函数在单元行上计算某个字段的结果时,可将窗口范围内的数据输入到聚合函数中,并且不会改变行数。 + +### 语法 + +```sql +窗口函数名称(参数) over(partition by ... order by... 窗口大小) +``` + +partition by 分组,等价于group by + +order by 排序 + +### 序号函数 + +row_number() :顺序排序,显示每条数据的行号,如:1 2 3 + +rank() :值相同的话,序号也会相同,会跳过,如:1 1 3 + +dense_rank() :值相同的话,序号也会相同,不会跳过,如:1 1 2 + +### 开窗聚合函数 + +```sql +sum()|avg()|min()|max()|count()| over (partition by ... order by...) +``` + +案例: + +```sql +# 获取各部门薪金总和(合计) +select ename,hiredate,deptno,sal, +sum(sal) over(partition by deptno) 'sum' +from emp; + +# 获取各部门薪金总和(累加) +select ename,hiredate,deptno,sal, +sum(sal) over(partition by deptno order by sal) 'sum' +from emp; +``` + +### 窗口大小 + +```sql +# 开窗范围 +# 获取各部门薪金总和(范围:初始行至当前行) +select ename,hiredate,deptno,sal, +sum(sal) over(partition by deptno rows between unbounded preceding and current row) 'sum' +from emp; + +-- rows 启用窗口大小 +-- between ... and ... 范围区间 +-- unbounded preceding 起始行 +-- current row 当前行 +``` + +不同案例: + +```sql +# 范围:前3行 + 当前行 +sum(sal) over(partition by deptno rows between 3 preceding and current row) + +# 范围:前3行 + 当前行 + 后1行 +sum(sal) over(partition by deptno rows between 3 preceding and 1 following) +-- following指当前行之后的行 + +# 范围:当前行至最后一行 +sum(sal) over(partition by deptno rows between current row and unbounded following) +-- unbounded following 最终行 +``` + +### 分布函数 + +**cume_dist()** + +分组内 <= 或 >= 当前值的行数占比(包含当前值本身) + +```sql +# 查询各部门员工小于等于当前薪资的比例 + +select ename,deptno,sal, +cume_dist() over(partition by deptno order by sal) '占比' +from emp; + +-- order by 控制计算的列 +-- asc 小于等于 +-- desc 大于等于 +``` + +### 前后函数 + +返回当前行的前某一行的值,或者后某一行的值 + +lag(列名,前第n行) + +lead(列名,后第n行) + +```sql +select ename,deptno,sal, +lag(sal,1) over(partition by deptno order by sal) '前1', +lag(sal,2) over(partition by deptno order by sal) '前2' +from emp; +``` + +### 头尾函数 + +返回第一个或最后一个列的值 + +first_value(列名) + +last_value(列名) + +```sql +-- 查询各个部门中,第一个和最后一个入职的员工姓名 +select ename,deptno,sal,hiredate, +first_value(ename) over(partition by deptno order by hiredate) 'first', +last_value(ename) over(partition by deptno order by hiredate) 'last' +from emp; +``` + +### 其他函数 + +nth_value(列名,n) —— 返回截止到当前行,该列的第n个值 + +```sql +-- 查找出各个部门,薪资排名第2的员工姓名 +select ename,deptno,sal, +nth_value(ename) over(partition by deptno order by sal) '第二名' +from emp; + +-- 查询全公司,薪资排名第2的员工姓名 +-- 1 +select ename,deptno,sal, +nth_value(ename,2) over(order by sal) '第二名' +from emp; +-- 2 +select ename,deptno,sal, +nth_value(ename,2) over() '第二名' +from emp order by sal; +``` + +ntile(n) —— 将分区中的有序数据分为n个等级,记录等级数 + +```sql +-- 将每个部门员工按照入职日期分成3组 +select ename,deptno,sal, +ntile(3) over(partition by deptno order by hiredate) '组别' +from emp; +``` + +## 八. 事务 + +为了完成某个业务而对数据库进行一系列操作,这些操作要么全部成功,要么全部失败。 + +#### 事务的四个特性 + +==原子性==:事务包含的这一系列操作,要么全部成功,要么全部失败(由DBMS的事务管理子系统来实现)。 + +==一致性==:事务完成之后,不会将非法的数据写入数据库(由DBMS的完整性子系统执行测试任务)。 + +==隔离性==:多个事务可以在一定程度上并发执行(由DBMS的并发控制子系统实现)。 + +==持久性==:事务完成之后,数据要永久保存(一般会保存在硬盘上)(由DBMS的恢复管理子系统实现的)。 + +#### 隔离级别 + +隔离级别从低到高依次是"读未提交"、“读已提交”、“可重复读取”和“序列化”,隔离级别越高,性能越低。mysql 数据库默认隔离级别是“可重复读取”,oracle是“读已提交”。数据库底层使用的“加锁”的机制来实现不同的隔离级别,包括对整个表加锁,对表中的行加锁。 + +### 语法: + +**查看全部系统变量** + +~~~ mysql +show variables; +# 查询事务的状态 +show variables like 'autocommit'; +~~~ + +**关闭自动提交** + +~~~ mysql +set autocommit = off | 0; +~~~ + +**开启自动提交** + +~~~ mysql +set autocommit = on | 1; +~~~ + +**回滚和提交** + +~~~ mysql +# 回滚 +rollback; +# 提交 +commit; +~~~ + +**临时开启一个事务**:不受 autocommit 状态影响 + +~~~ mysql +start transaction; # 表示从这行开始,直到 rollback 或 commit 结束 +# 设置保存点 +savepoint p1; # 保存点:设置某部分回滚 +delete from emp where id = 3; # 增、删、改语句 +# 指定回滚 +rollback to p1 # 表示从这个保存点往后的所有回滚 +~~~ + +**开启一个只读事务** + +~~~ mysql +start taransaction read only; +~~~ +