目录
一、MySQL高级01
1.1 索引
1.1.1 索引概述
MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护者满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找快速获取到相应数据。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。索引是数据库中用来提高性能的最常用的工具。
1.1.2 索引特点
优势
1) 类似于书籍的目录索引,提高数据检索的效率,降低数据库的IO成本。
2) 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。
劣势
1) 实际上索引也是一张表,该表中保存了主键与索引字段,并指向实体类的记录,所以索引列也是要占用空间的。
2) 虽然索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。
1.1.3 索引结构
索引是在MySQL的存储引擎层(InnoDB)中实现的,而不是在服务器层实现的。所以每种存储引擎的索引都不一定完全相同,也不是所有的存储引擎都支持所有的索引类型的。
MySQL目前提供了以下4种索引:
- BTREE 索引 : 最常见的索引类型,大部分索引都支持 B 树索引。
- HASH 索引:只有Memory引擎支持 , 使用场景简单 。
- R-tree 索引(空间索引):空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少,不做特别介绍。
- Full-text (全文索引) :全文索引也是MyISAM的一个特殊索引类型,主要用于全文索引,InnoDB从Mysql5.6版本开始支持全文索引。
MyISAM、InnoDB、Memory三种存储引擎对各种索引类型的支持
索引 | InnoDB引擎 | MyISAM引擎 | Memory引擎 |
---|---|---|---|
BTREE索引 | 支持 | 支持 | 支持 |
HASH 索引 | 不支持 | 不支持 | 支持 |
R-tree 索引 | 不支持 | 支持 | 不支持 |
Full-text | 5.6版本之后支持 | 支持 | 不支持 |
我们平常所说的索引,如果没有特别指明,都是指B+树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引、复合索引、前缀索引、唯一索引默认都是使用 B+tree 索引,统称为 索引。
1.1.4 BTREE结构(B树)
BTree又叫多路平衡搜索树,一颗m叉的BTree特性如下:
- 树中每个节点最多包含m个孩子。
- 除根节点与叶子节点外,每个节点至少有[ceil(m/2)]个孩子。
(叶子节点: 当前的节点下面没有子节点)
- 若根节点不是叶子节点,则至少有两个孩子。
- 所有的叶子节点都在同一层。
- 每个非叶子节点由n个key与n+1个指针组成,其中[ceil(m/2)-1] <= n <= m-1
以5叉BTree为例,key的数量:公式推导[ceil(m/2)-1] <= n <= m-1。所以 2 <= n <=4 。当n>4时,中间节点分裂到父节点,两边节点分裂。
下面以插入 C N G A H E K Q M F W L T Z D P R X Y S 数据为例。
演变过程如下:
1). 插入前4个字母 C N G A
2). 插入H,n>4,中间元素G字母向上分裂到新的节点
3). 插入E,K,Q不需要分裂
4). 插入M,中间元素M字母向上分裂到父节点
5). 插入F,W,L,T不需要分裂
6). 插入Z,中间元素T向上分裂到父节点中
7). 插入D,中间元素D向上分裂到父节点中。然后插入P,R,X,Y不需要分裂
8). 最后插入S,NPQR节点n>5,中间节点Q向上分裂,但分裂后父节点DGMT的n>5,中间节点M向上分裂
到此,该BTREE树就已经构建完成了, BTREE树 和 二叉树 相比, 查询数据的效率更高, 因为对于相同的数据量来说,BTREE的层级结构比二叉树小,因此搜索速度快。
1.1.5 B+TREE结构(B+树)
B+Tree为BTree的变种,B+Tree与BTree的区别为:
1). n叉B+Tree最多含有n个key,而BTree最多含有n-1个key。
2). B+Tree的叶子节点保存所有的key信息,依key大小顺序排列。
3). 所有的非叶子节点都可以看作是key的索引部分。
由于B+Tree只有叶子节点保存key信息,查询任何key都要从root走到叶子。所以B+Tree的查询效率更加稳定。
MySQL中的B+TREE
MySql索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能。
MySQL中的 B+Tree 索引结构示意图:
1.1.6 索引分类
1) 单列索引 :即一个索引只包含单个列,一个表可以有多个单列索引
2) 唯一索引 :索引列的值必须唯一,但允许有空值
3) 复合索引 :即一个索引包含多个列
主键索引和唯一索引有什么不同?
主键索引不可以有空值,唯一索引可以有空值
1.1.7 索引语法
sql语句在可视化工具和数据库中执行都是可以的
1、创建索引
语法:
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name [USING index_type]ON tbl_name(index_col_name,...)
示例:
-- 为city表的city_name创建一个唯一索引
CREATE UNIQUE INDEX index_city_name ON city(city_name);
2、查看索引
语法:
show index from table_name;
示例:
查看city表中的索引信息
这里可以在可视化工具中(图1),也可以在数据库中(图2),在数据库中查看时,为了有更高的可读性,会使用 \G 让结果分行显示
3、删除索引
语法:
DROP INDEX index_name ON tbl_name;
示例:
删除city表上的唯一索引index_city_name
4、alter命令
1). alter table tb_name add primary key(column_list);
该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL
2). alter table tb_name add unique index_name(column_list);
这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)
3). alter table tb_name add index index_name(column_list);
添加普通索引, 索引值可以出现多次。
4). alter table tb_name add fulltext index_name(column_list);
该语句指定了索引为FULLTEXT, 用于全文索引
1.1.8 索引设计原则
索引的设计可以遵循一些已有的原则,创建索引的时候请尽量考虑符合这些原则,便于提升索引的使用效率,更高效的使用索引。
- 对查询频次较高,且数据量比较大的表建立索引。
- 索引字段的选择,最佳候选列应当从where子句的条件中提取,如果where子句中的组合比较多,那么应当挑选最常用、过滤效果最好的列的组合。
- 使用唯一索引,区分度越高,使用索引的效率越高。
- 索引可以有效的提升查询数据的效率,但索引数量不是多多益善,索引越多,维护索引的代价自然也就水涨船高。对于插入、更新、删除等DML操作比较频繁的表来说,索引过多,会引入相当高的维护代价,降低DML操作的效率,增加相应操作的时间消耗。另外索引过多的话,MySQL也会犯选择困难病,虽然最终仍然会找到一个可用的索引,但无疑提高了选择的代价。
- 使用短索引,索引创建之后也是使用硬盘来存储的,因此提升索引访问的I/O效率,也可以提升总体的访问效率。假如构成索引的字段总长度比较短,那么在给定大小的存储块内可以存储更多的索引值,相应的可以有效的提升MySQL访问索引的I/O效率。
利用最左前缀,N个列组合而成的复合索引,那么相当于是创建了N个索引,如果查询时where子句中使用了组成该索引的前几个字段,那么这条查询SQL可以利用组合索引来提升查询效率。
创建复合索引:
CREATE INDEX idx_name_email_status ON tb_seller(NAME,email,STATUS);
就相当于
对name 创建索引 ;
对name , email 创建了索引 ;
对name , email, status 创建了索引 ;
1.2 视图
1.2.1 视图概述
视图(View)是一种虚拟存在的表。视图并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。通俗的讲,视图就是一条SELECT语句执行后返回的结果集。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。
视图相对于普通的表的优势主要包括以下几项。
- 简单:使用视图的用户完全不需要关心后面对应的表的结构、关联条件和筛选条件,对用户来说已经是过滤好的复合条件的结果集。
- 安全:使用视图的用户只能访问他们被允许查询的结果集,对表的权限管理并不能限制到某个行某个列,但是通过视图就可以简单的实现。
- 数据独立:一旦视图的结构确定了,可以屏蔽表结构变化对用户的影响,源表增加列对视图没有影响;源表修改列名,则可以通过修改视图来解决,不会造成对访问者的影响。
1.2.2 创建或修改视图
1、创建视图
-- 创建视图
-- 语法:
CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW view_name [(column_list)]
AS select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION]
/** 选项
CREATE:表示新建视图;
REPLACE:表示替换已有视图
ALGORITHM :表示视图选择算法
view_name :视图名
column_list:属性列
select_statement:表示select语句
WITH [CASCADED | LOCAL] CHECK OPTION 决定了是否允许更新数据使记录不再满足视图的条件。
-- LOCAL : 只要满足本视图的条件就可以更新。
-- CASCADED : 必须满足所有针对该视图的所有视图的条件才可以更新。 默认值.
注:可选的ALGORITHM子句是对标准SQL的MySQL扩展。
如果没有ALGORITHM子句,「默认算法是UNDEFINED(未定义的)」。算法会影响MySQL处理视图的方式。
① 对于MERGE,会将引用视图的语句的文本与视图定义合并起来,使得视图定义的某一部分取代语句的对应部分。
② 对于TEMPTABLE,视图的结果将被置于临时表中,然后使用它执行语句。
③ 对于UNDEFINED,MySQL自己选择所要使用的算法。如果可能,它倾向于MERGE而不是TEMPTABLE,这是因为MERGE通常更有效,而且如果使用了临时表,视图是不可更新的。LOCAL和CASCADED为可选参数,决定了检查测试的范围,默认值为CASCADED。
*/
-- 创建视图示例:查询每个城市信息及其对应的国家信息
CREATE OR REPLACE VIEW view_test AS
SELECT city.*,country.country_name FROM city,country WHERE city.country_id = country.country_id
2、查看视图
查看视图必须要有SHOW VIEW权限
查看视图的方法包括:DESCRIBE、SHOW TABLE STATUS、SHOW CREATE VIEW、SHOW TABLES
-- 1、查看视图的名称及语句
SHOW CREATE VIEW view_test;
-- 2、既显示表名,也显示视图名
SHOW TABLES;
-- 3、既显示表的信息,也显示视图的信息
SHOW TABLE STATUS;
-- 4、查看视图基本信息,DESCRIBE一般都简写成DESC
DESCRIBE view_test;
3、 修改视图
语法:
ALTER [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW view_name [(column_list)]
AS select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION]
示例:
-- 修改视图示例,修改一下要查询的内容
ALTER VIEW view_test AS
select * from country;
-- 查看视图的内容
SELECT * FROM view_test;
4、删除视图
语法:
DROP VIEW [IF EXISTS]
view_name [, view_name] ...
[RESTRICT | CASCADE]
-- DROP VIEW能够删除1个或多个视图。必须在每个视图上拥有DROP权限
-- 可以使用关键字IF EXISTS来防止因不存在的视图而出错
示例:
删除视图 view_test
mysql> drop view if exists view_test; #第一次删除(视图存在)
Query OK, 0 rows affected (0.00 sec)
mysql> drop view if exists view_test; #第二次删除(视图已经不存在)
Query OK, 0 rows affected, 1 warning (0.00 sec)
1.3 存储过程和函数
1.3.1 存储过程概述
存储过程和函数是 事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程和函数可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
存储过程和函数的区别在于函数必须有返回值,而存储过程没有。
函数 : 是一个有返回值的过程 ;
过程 : 是一个没有返回值的函数 ;
1.3.2 创建存储过程
语法:
CREATE PROCEDURE procedure_name ([proc_parameter[,...]])
begin
-- SQL语句
end ;
示例:
-- 创建存储过程
drop PROCEDURE IF EXISTS pro_test1;
create procedure pro_test1()
begin
select 'Hello Mysql' ;
end;
1.3.3 调用存储过程
-- 调用存储过程
call pro_test1();
1.3.4 查看存储过程
-- 1、查询test数据库中的所有的存储过程 mysql版本较低(8以前)时可以使用这个方法
select `name` from mysql.proc where db = 'test' and `type` = 'procedure'
-- 2、查询存储过程的状态信息
show procedure status;
-- 3、查询某个存储过程的定义
show create procedure test.pro_test1;
1.3.5 删除存储过程
DROP PROCEDURE [IF EXISTS] sp_name;
1.4 编写存储过程
存储过程是可以编程的,意味着可以使用变量,表达式,控制结构 , 来完成比较复杂的功能。
1.4.1 变量(DECLARE)
通过 DECLARE 可以定义一个局部变量,该变量的作用范围只能在 BEGIN…END 块中。
语法:
DECLARE var_name[,...] type [DEFAULT value]
关键字 变量名 变量类型 默认值
注:declare(变量声明)操作必须放在set(变量赋值)操作前面。
存储过程中变量名不能和列名一致
示例:
-- 存储过程声明局部变量
DROP PROCEDURE IF EXISTS pro_test2;
CREATE PROCEDURE pro_test2()
BEGIN
DECLARE num1,num2 int; --可以一次声明多个变量
SET num1 = 10;
SET num2 = 9;
SELECT num1+num2;
END;
-- 调用存储过程
CALL pro_test2();
1.4.2 变量赋值
1、使用set赋值
直接赋值使用 SET,可以赋常量或者赋表达式,具体
语法如下:
SET var_name = expr [, var_name = expr] ...
-- 使用set赋值
DROP PROCEDURE IF EXISTS pro_test3;
CREATE PROCEDURE pro_test3()
BEGIN
DECLARE username VARCHAR(255); -- varchar类型变量需要指定长度
SET username = 'lwl';
SELECT username;
END;
-- 调用存储过程
CALL pro_test3();
2、使用select...into赋值
-- 查询城市的数量,将值赋给定义好的变量
-- 使用select..into进行赋值
DROP PROCEDURE IF EXISTS pro_test4;
CREATE PROCEDURE pro_test4()
BEGIN
DECLARE num INT;
SELECT COUNT(*) INTO num FROM city;
SELECT num;
END;
-- 调用存储过程
CALL pro_test4();
1.4.3 if条件判断
语法:
if search_condition then statement_list
[elseif search_condition then statement_list] ...
[else statement_list]
end if;
示例:
-- 根据定义的身高变量,判定当前身高的所属的身材类型
-- 180 及以上 ----------> 身材高挑
-- 170 - 180 ---------> 身材适中
-- 170 以下 ----------> 还能发育一下
DROP PROCEDURE IF EXISTS pro_test5;
CREATE PROCEDURE pro_test5()
BEGIN
DECLARE height int; -- 声明 一个变量定义身高
DECLARE desc1 VARCHAR(100); -- 声明一个变量接收描述
SET height = 185;
IF height >= 180 THEN
SET desc1 = '身材高挑';
ELSEIF 180 > height >= 170 THEN
SET desc1 = '身材适中';
ELSE
SET desc1 = '还能发育一下';
END IF;
SELECT desc1;
END;
-- 调用存储过程
CALL pro_test5();
1.4.4 传递参数
IN-输入
语法:
create procedure procedure_name([in/out/inout] 参数名 参数类型)
IN : 该参数可以作为输入,也就是需要调用方传入值 , 默认
OUT: 该参数作为输出,也就是该参数可以作为返回值
INOUT: 既可以作为输入参数,也可以作为输出参数
示例:
在调用存储过程的时候输入身高参数,根据定义的身高变量,判定当前身高的所属的身材类型。
-- 输入(IN)参数测试
DROP PROCEDURE IF EXISTS pro_test6;
CREATE PROCEDURE pro_test6(in height INT)
BEGIN
DECLARE desc1 VARCHAR(100); -- 声明一个变量接收描述
IF height >= 180 THEN
SET desc1 = '身材高挑';
ELSEIF 180 > height >= 170 THEN
SET desc1 = '身材适中';
ELSE
SET desc1 = '还能发育一下';
END IF;
SELECT CONCAT('身高是:',height,'身材是:',desc1);
END;
-- 调用存储过程
CALL pro_test6(185);
OUT-输出
根据定义的身高变量,判定当前身高的所属的身材类型
-- 输出(OUT)参数测试
DROP PROCEDURE IF EXISTS pro_test7;
CREATE PROCEDURE pro_test7(in height INT,OUT desc1 VARCHAR(100))
BEGIN
IF height >= 180 THEN
SET desc1 = '身材高挑';
ELSEIF 180 > height >= 170 THEN
SET desc1 = '身材适中';
ELSE
SET desc1 = '还能发育一下';
END IF;
END;
-- 调用存储过程
CALL pro_test7(185,@desc1);
-- 查询输出参数
SELECT @desc1;
@description : 这种变量要在变量名称前面加上“@”符号,叫做用户会话变量,代表整个会话过程他都是有作用的,这个类似于全局变量一样。
@@global.sort_buffer_size : 这种在变量前加上 "@@" 符号, 叫做 系统变量
示例:
根据国家的id查询出城市的个数,输入参数是国家id,输出参数是城市个数
-- 输出(OUT)参数测试
DROP PROCEDURE IF EXISTS pro_test8;
CREATE PROCEDURE pro_test8(in countid INT,OUT num INT)
BEGIN
-- 在存储过程中变量名不能和列名一致
SELECT COUNT(*) INTO num FROM city WHERE country_id = countid;
END;
-- 调用存储过程
CALL pro_test8(1,@countid);
-- 查询输出参数
SELECT @countid;
1.4.5 case结构
方式一:
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list] ...
[ELSE statement_list]
END CASE;
方式二:
CASE
WHEN search_condition THEN statement_lis
[WHEN search_condition THEN statement_list] ...
[ELSE statement_list]
END CASE;
示例:
给定一个月份, 然后计算出所在的季度
-- 测试case方式一;
-- 给定一个月份,计算出所在的季度
DROP PROCEDURE IF EXISTS pro_test10;
CREATE PROCEDURE pro_test10(IN mouth int,OUT resu VARCHAR(100))
BEGIN
CASE mouth
WHEN 1 THEN
SET resu='第一季度';
WHEN 5 THEN
SET resu='第二季度';
WHEN 7 THEN
SET resu='第三季度';
ELSE
SET resu='其他季度';
END CASE;
END;
-- 调用存储过程
CALL pro_test10(2,@res);
SELECT @res;
-- 测试case方式二;
-- 给定一个月份,计算出所在的季度
DROP PROCEDURE IF EXISTS pro_test9;
CREATE PROCEDURE pro_test9(IN mouth int,OUT resu VARCHAR(100))
BEGIN
CASE
WHEN mouth >= 1 AND mouth <=3 THEN
SET resu='第一季度';
WHEN mouth >= 4 AND mouth <=6 THEN
SET resu='第二季度';
WHEN mouth >= 7 AND mouth <=9 THEN
SET resu='第三季度';
WHEN mouth >= 10 AND mouth <=12 THEN
SET resu='第四季度';
END CASE;
END;
-- 调用存储过程
CALL pro_test9(6,@res);
SELECT @res;
1.4.6 while循环
语法:
while search_condition do
statement_list
end while;
示例:
-- 使用while循环计算从1加到n的值
DROP PROCEDURE IF EXISTS pro_test11;
CREATE PROCEDURE pro_test11(IN n INT,OUT total INT)
BEGIN
DECLARE num INT;
SET num = 1;
SET total = 0;
WHILE num<=n DO
SET total = total+num;
SET num=num+1;
END WHILE;
END;
-- 调用存储过程
CALL pro_test11(100,@total);
SELECT @total;
1.4.7 repeat结构
有条件的循环控制语句, 当满足条件的时候退出循环 。while 是满足条件才执行,repeat 是满足条件就退出循环。
语法:
REPEAT
statement_list
UNTIL search_condition (后面没有分号)
END REPEAT;
示例:
计算从1加到n的值
-- 使用repeat结构计算从1加到n的值
DROP PROCEDURE IF EXISTS pro_test12;
CREATE PROCEDURE pro_test12(IN n INT,OUT total INT)
BEGIN
SET total = 0;
REPEAT
SET total = total + n;
SET n = n - 1;
UNTIL n = 0
END REPEAT;
END;
-- 调用存储过程
CALL pro_test12(10,@total);
SELECT @total;
1.4.8 loop和leave语句
LOOP 实现简单的循环,退出循环的条件需要使用其他的语句定义,通常可以使用 LEAVE 语句实现,具体语法如下:
语法:
LOOP
statement_list
END LOOP
如果不在 statement_list 中增加退出循环的语句,那么 LOOP 语句可以用来实现简单的死循环。
leave用来从标注的流程构造中退出,通常和 BEGIN ... END 或者循环一起使用。
下面是一个使用 LOOP 和 LEAVE 的简单例子 , 退出循环:
-- LOOP和LEAVE的示例:计算从1加到n的值
DROP PROCEDURE IF EXISTS pro_test13;
CREATE PROCEDURE pro_test13(IN n INT,OUT total INT)
BEGIN
SET total = 0;
-- ins 这里是自定义命名,只是为了标识
ins:LOOP
IF n <= 0 THEN -- 如果满足条件就退出循环
LEAVE ins; -- 如果没有跳出循环,将会一直进行loop循环
END IF; -- 结束判断语句
SET total = total + n;
SET n = n - 1;
END LOOP ins;
END;
-- 调用存储过程
CALL pro_test13(10,@total13);
SELECT @total13;
1.4.9 游标/光标
游标是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用光标对结果集进行循环的处理。光标的使用包括光标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下。
语法:
声明光标:
DECLARE cursor_name CURSOR FOR select_statement;
OPEN光标:
OPEN cursor_name;
FETCH光标:
FETCH cursor_name INTO var_name [, var_name] ...
CLOSE光标:
CLOSE cursor_name;
示例:
-- 建表
create table emp(
id int(11) not null auto_increment ,
name varchar(50) not null comment '姓名',
age int(11) comment '年龄',
salary int(11) comment '薪水',
primary key(`id`)
)engine=innodb default charset=utf8 ;
insert into emp(id,name,age,salary) values(null,'金毛狮王',55,3800),(null,'白眉鹰王',60,4000),(null,'青翼蝠王',38,2800),(null,'紫衫龙王',42,1800);
获取数据,并将数据逐行展示
-- 使用emp表测试游标/光标
-- 逐行获取并将数据进行展示
DROP PROCEDURE IF EXISTS pro_test14;
CREATE PROCEDURE pro_test14()
BEGIN
DECLARE e_id int(11);
DECLARE e_name VARCHAR(255);
DECLARE e_age int(11);
DECLARE e_salary int(11);
-- 声明光标
DECLARE emp_result CURSOR FOR SELECT * FROM emp;
-- 打开光标
OPEN emp_result;
-- FETCH光标
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
-- 关闭光标
CLOSE emp_result;
END;
-- 调用存储过程
CALL pro_test14();
结果如下图
通过循环结构(repeat),获取游标中的数据:
-- 使用emp表测试游标/光标
-- 通过循环结构(repeat) 并将数据进行展示
DROP PROCEDURE IF EXISTS pro_test15;
CREATE PROCEDURE pro_test15()
BEGIN
DECLARE e_id int(11);
DECLARE e_name VARCHAR(255);
DECLARE e_age int(11);
DECLARE e_salary int(11);
-- 给退出循环的判定条件一个初始值
DECLARE has_data int DEFAULT 1; -- 默认值为1
-- 声明光标
DECLARE emp_result CURSOR FOR SELECT * FROM emp;
DECLARE EXIT HANDLER FOR NOT found SET has_data = 0;
-- 打开光标
OPEN emp_result;
-- 开启循环
REPEAT
-- FETCH光标
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
UNTIL has_data = 0
-- 关闭循环
END REPEAT;
-- 关闭光标
CLOSE emp_result;
END;
-- 调用存储过程
CALL pro_test15();
结果如下图
通过循环结构(loop和leave) 并将数据进行展示
-- 使用emp表测试游标/光标
-- 通过循环结构(loop和leave) 并将数据进行展示
DROP PROCEDURE IF EXISTS pro_test16;
CREATE PROCEDURE pro_test16()
BEGIN
DECLARE e_id int(11);
DECLARE e_name VARCHAR(255);
DECLARE e_age int(11);
DECLARE e_salary int(11);
-- 给退出循环的判定条件一个初始值
DECLARE has_data int DEFAULT 1; -- 默认值为1
-- 声明光标
DECLARE emp_result CURSOR FOR SELECT * FROM emp;
DECLARE EXIT HANDLER FOR NOT found SET has_data = 0;
-- 打开光标
OPEN emp_result;
-- 开启循环,这里的ins(自定义)标识着循环开始
ins:LOOP
IF has_data = 0 THEN
LEAVE ins;
END IF;
-- FETCH光标
fetch emp_result into e_id,e_name,e_age,e_salary;
select concat('id=',e_id , ', name=',e_name, ', age=', e_age, ', 薪资为: ',e_salary);
-- 关闭循环
END LOOP ins;
-- 关闭光标
CLOSE emp_result;
END;
-- 调用存储过程
CALL pro_test16();
结果如下图
三次测试结果是以相同的方式展现的
1.4.10 存储函数
语法:
CREATE FUNCTION function_name([param type ... ])
RETURNS type
BEGIN
-- SQL语句
END;
案例:
DROP FUNCTION IF EXISTS fun_test1;
CREATE FUNCTION fun_test(countryId INT)
RETURNS INT
BEGIN
DECLARE cnum INT;
SELECT COUNT(*) INTO cnum FROM city WHERE country_id = countryId;
RETURN cnum;
END;
-- 调用函数
SELECT fun_test1(1);
报错:
1418 - This function has none of DETERMINISTIC, NOSQL, or READS SQL DATA in its declaration and binary logging isenabled (you *might* want to use the less safelog_bin_trust_function_creators variable)错误。
原因是开启了log-bin日志,创建函数时,函数中没有包含DETERMINISTIC, NOSQL和 READS SQL DATA声明,即没有涉及修改数据。mysql的设置默认是 不允许创建自定义函数的,这是出于安全性的考虑,所以只要设置信息用户的操作就可以了
1、查出log_bin_trust_function_creators的值
mysql--> show variables like 'log_bin_trust_function_creators'
log_bin_trust_function_creators OFF -- 开始为OFF
2、修改该值为ON
mysql-->set global log_bin_trust_function_creators=1;
解决问题,不过要使该参数重启之后不失效,要在my.cnf修改为ON
修改之后即可成功定义函数
1.5 MyBatis调用存储过程
1、sql中编写的存储过程
-- 根据定义的身高变量,判定当前身高的所属的身材类型
-- 180 及以上 ----------> 身材高挑
-- 170 - 180 ---------> 身材适中
-- 170 以下 ----------> 还能发育一下
-- 输出(OUT)参数测试
DROP PROCEDURE IF EXISTS pro_test7;
CREATE PROCEDURE pro_test7(in height INT,OUT desc1 VARCHAR(100))
BEGIN
IF height >= 180 THEN
SET desc1 = '身材高挑';
ELSEIF 180 > height >= 170 THEN
SET desc1 = '身材适中';
ELSE
SET desc1 = '还能发育一下';
END IF;
END;
-- 调用存储过程
CALL pro_test7(185,@desc1);
-- 查询输出参数
SELECT @desc1;
2、pom.xml包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.13.RELEASE</version>
<scope>test</scope>
</dependency>
<!--注释lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!--junit,进行单元测试的包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--mybatis对数据库进行持久化操作,也可以使用MybatisPlus,对应的配置文件进行更换即可-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--mybatis组件-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!--根据数据库表自动生成框架-1-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--根据数据库表自动生成框架-2-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
3、自动生成代码的类:
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class GenerateTest {
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("L");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://192.168.111.127:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("用户名");
dsc.setPassword("密码");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
//pc.setModuleName(scanner("模块名"));
pc.setParent("com.example.testmysql2");
//pc.setXml("");
pc.setEntity("entity");//实体的包
pc.setMapper("dao");//dao的包
pc.setService("service");//service的包
pc.setServiceImpl("service.impl");//实现类的包
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
// String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
//不在java文件夹下面写入mapper文件
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);// _ tab_user tabUser
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
// 写于父类中的公共字段
//strategy.setSuperEntityColumns("id");// id @TabId
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.execute();
}
}
4、配置文件:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://IP地址:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=用户名
spring.datasource.password=密码
mybatis.mapper-locations=classpath:/mapper/*.xml
# mybatis的日志配置
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
5、mapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.testmysql2.dao.CityMapper">
<select id="getPro" resultType="map" statementType="CALLABLE" parameterType="map">
{call pro_test7(#{height,jdbcType=INTEGER,mode=IN},#{desc1,jdbcType=VARCHAR,mode=OUT})}
</select>
</mapper>
6、mapper接口:
public interface CityMapper extends BaseMapper<City> {
Map getPro(Map map);
}
7、启动类:
@SpringBootApplication
@MapperScan("com.example.testmysql2.dao")
public class Testmysql2Application {
public static void main(String[] args) {
SpringApplication.run(Testmysql2Application.class, args);
}
}
8、service接口及实现类(这里不使用service层也可以)
接口:
public interface ICityService extends IService<City> {
Map getPro(Map map);
}
实现类:
@Service
public class CityServiceImpl extends ServiceImpl<CityMapper, City> implements ICityService {
@Resource
private CityMapper cityMapper;
@Override
public Map getPro(Map map) {
Map pro = cityMapper.getPro(map);
return pro;
}
}
9、测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestMySql {
@Resource
private ICityService cityService;
@Test
public void testa(){
Map maps = new HashMap();
maps.put("height",185);
maps.put("desc1","");
cityService.getPro(maps);
System.out.println("maps = " + maps.get("desc1"));
}
}