文章目录
- 十三、视图
- 1、视图是什么?
- 2、视图的特性?
- 3、视图的作用?
- 4、视图的用途?
- 5、视图的使用?
- 1、基本语法
- 2、创建视图
- 3、调用视图
- 4、视图练习
- (1) 利用试图简化复杂的联结
- (2) 利用视图重新格式化检索出的数据
- (3) 利用试图过滤不想要的数据
- (4) 使用视图与计算字段
- (5) 更新视图(添加字段、重命名)
- (6) 视图叠加视图
- (7) 利用视图更新表数据(增删改)
- (8) 删除视图
- 十四、存储过程
- 1、创建存储过程
- 2、调用存储过程
- 3、删除存储过程
- 4、存储过程体
- 1、变量的使用
- (1)声明变量:
- (2)变量赋值:
- 2、定义条件和处理程序
- (1)定义条件
- (2)定义处理程序
- (3)定义条件并处理程序
- 3、游标
- (1)声明游标
- (2)打开游标
- (3)使用游标
- (4)关闭游标
- 4、流程控制的使用
- (1)IF语句
- (2)CASE语句
- (3)LOOP语句
- (4)LEAVE语句
- (5)ITERATE语句
- (6)REPEAT语句
- (7)WHILE语句
- 5、 MySQL通用分页存储过程
- 6、存储过程调用
- 1、 不带查询条件和排序
- 2、 带查询条件和排序
- 7、入参出参代码示例
- (1)无参数代码示例
- (2)引入参数代码示例
- (3)返回参数代码示例
- (4)即可引入参数也可以返回参数 代码示例
- 8、流程控制代码实例
- (1)IF语句
- (2)LOOP死循环
- (3)拼接字符串
- (4)REPEAT语句
- (5)WHILE语句
- 9、练习
- (1)查询数据
- (2)更新数据
- (3)循环创建表
- 十五、事务
- 1、 事务的概念
- 2、 MySQL中的事务
- 3、 事务满足的条件
- 4、 事务控制语句
- 5、 事务测试
十三、视图
1、视图是什么?
视图是虚拟表,本身不存储数据,而是按照指定的方式进行查询。
通俗的讲:视图就是SELECT语句执行后返回的结果集。
视图与表的区别:
- 表中的数据占用物理空间,视图不占用。
- 表中的数据是真实存在的,视图的数据是通过执行时动态生成的。
2、视图的特性?
视图是对若干张基本表的引用
一张虚表,查询语句执行的结果,不存储具体的数据(基本表数据发生了改变,视图也会跟着改变)
可以跟基本表一样,进行增删改查操作(增删改操作存在条件限制)
3、视图的作用?
- **简单:**使用视图的用户完全不需要关心后面对应的表的结构、关联条件和筛选条件,对用户来说已经是过滤好的复合条件的结果集。
- **安全:**使用视图的用户只能访问他们被允许查询的结果集,对表的权限管理并不能限制到某个行某个列,但是通过视图就可以简单的实现。
- **数据独立:**一旦视图的结构确定了,可以屏蔽表结构变化对用户的影响,源表增加列对视图没有影响;源表修改列名,则可以通过修改视图来解决,不会造成对访问者的影响。
- 总而言之,使用视图的大部分情况是为了保障数据安全性,提高查询效率。
4、视图的用途?
- **权限控制:**不希望用户访问表中某些含敏感信息的列,比如salary、identity等
- **多表连接:**关键信息来源于多个复杂关联表可以创建视图提取我们需要的信息简化操作;
5、视图的使用?
1、基本语法
create view 自定义视图名 as 查询表语句;# 创建视图:
alter view 自定义视图名 as 查询表语句; # 修改视图:
rename table 旧视图名 to 新视图名; # 重命名视图:
drop view 视图名; # 删除视图:
# (通过视图进行的修改,必须也能通过该视图看到修改后的结果)
WITH CHECK OPTION # 确保视图的一致性
# 如果视图建立错误可以通过alter view修改
alter view 视图名称 as 查询语句; # 修改视图格式
rename table 视图名 to 视图名; # 修改视图名称
drop view 视图名称; # 删除视图
2、创建视图
# 根据客户表创建视图名为customerInfo
# select语句执行返回的结果集进行缩影然后映射到customerInfo
# 注意:视图仅供参考,数据依旧在原始表
create view customerInfo as select * from customer;
3、调用视图
# 调用视图
# 从创建的视图中查询(把视图当成一个表)
select * from customers_order_vies;
# 此时返回的结果集是所有购买过产品的客户
# 调用视图时依旧可以使用where子句过滤
select * from customers_order_vies where prod_id = 'TNT2';
# 通过where子句从视图中检索特定的数据
# mysql机制会把指定的where子句追加到视图查询中已有where子句中
4、视图练习
(1) 利用试图简化复杂的联结
# 首先以表连接为例
# 查询订购了TNT2产品的客户
# 任何需要这个数据的人都必须了解相关的表结构
# 并且要知道如何创建查询和定义对比的关联条件
select cust_name, cust_contact
from customers, orders, orderitems
where customers.cust_id = orders.cust_id
and orderitems.order_num = orders.order_num
and prod_id = 'TNT2';
# 根据客户表创建视图名为product_customers
# select语句执行返回的结果集进行缩影然后映射到product_customers
# 注意:视图仅供参考,数据依旧在原始表
create view customers_order_vies as
select cust_name, cust_contact
from customers, orders, orderitems
where customers.cust_id = orders.cust_id
and orderitems.order_num = orders.order_num;
# and prod_id = 'TNT2'
# 这样看似比表连接还要复杂繁琐,但是这仅是定义我们需要的数据
# 再次需要时直接查询product_customers视图再定义where子句即可
(2) 利用视图重新格式化检索出的数据
# 创建视图vendor_locations
# 查询供应商的名字并在名字后显示所属国家(拼接新格式)
create view vendor_locations as
select concat(RTrim(vend_name),'(',RTrim(vend_country),')') as vend_title
from vendors
order by vend_name;
# 调用视图vendor_locations
select * from vendor_locations;
# 此时返回的结果集是所有供应商
(3) 利用试图过滤不想要的数据
# 创建视图vendor_locations
# 查询Email不为空的客户(id、name、email)
create view customer_emaillist as select cust_id, cust_name, cust_email
from customers
where cust_email is not null;
(4) 使用视图与计算字段
# 视图对于简化计算字段的使用特别有用
# 检索订单号为20005特定订单中的物品,计算每种物品的总价格
select prod_id,
quantity,
item_price,
quantity*item_price as expanded_price
from orderitems
where order_num ='20005';
# 考虑复用性 视图暂不定义订单编号条件
# 创建视图orderitems_expanded
# 查询定单信息(产品id、产品数量、产品价格、计算字段(总价格))
create view orderitems_expanded as
select prod_id,
quantity,
item_price,
quantity*item_price as expanded_price
from orderitems;
# 调用视图,并给出条件(查询定单编号为20005的详细内容)
select *
from orderitems_expanded
where order_num = '20005';
# 调用试图使用的where子句会和视图内的where子句自动合并
(5) 更新视图(添加字段、重命名)
# 创建视图(客户详细订单信息)
create view customers_orders_info_view as
select c.cust_id, c.cust_name, c.cust_email,
o.order_num,o.order_date,
p.prod_name,p.prod_price
from customers c, products p, orders o, orderitems os
where c.cust_id = o.cust_id
and o.order_num = os.order_num
and p.prod_id = os.prod_id;
# 更新视图(客户详细订单信息)
# (追加字段产品编号prod_id)
# (直接在select语句中追加需要的列名)
alter view customers_orders_info_view as
select c.cust_id, c.cust_name, c.cust_email,
o.order_num,o.order_date,
p.prod_id,p.prod_name,p.prod_price
from customers c, products p, orders o, orderitems os
where c.cust_id = o.cust_id
and o.order_num = os.order_num
and p.prod_id = os.prod_id;
# 此时调用视图customers_orders_info_view
# 发现字段已经添加
select * from customers_orders_info_view;
# 修改视图名称
rename table customers_orders_info_view to custtomer_orderInfo_view;
# 此时调用视图customers_orders_info_view
select * from customers_orders_info_view;
# 报错!不存在,说明改成功了!
# 调用新视图名称
select * from custtomer_orderInfo_view;
# 调用成功
(6) 视图叠加视图
# 查询所有购买过产品的客户
create view customerOrderInfo_all as
select c.*,
o.cust_id as o_custNo,
o.order_date,
o.order_num
from customers c,orders o where c.cust_id = o.cust_id;
# 调用旧视图 返回所有有订单记录的用户
select * from customerOrderInfo_one;
# 再视图的基础上再做一层视图
# 此时是单独为10001的用户做一个视图(如果他持续购买产品该视图会更新)
create view customerOrderInfo_one as
select *
from customerOrderInfo
where cust_id = '10001';
# 调用新视图 返回10001用户的订单记录
select * from customerOrderInfo_one;
# 事实证明
# 视图的基础上可以继续创建视图
# 可以用来分别保存某一字段或数据等特定的结果集
(7) 利用视图更新表数据(增删改)
# 查询表结构数据类型
desc products;
# 插入表数据
create view customerInfo as select * from products;
# 注意:插入信息时依旧需要遵循原始表字段的顺序和表结构约束
insert into customerInfo values('kk','1002','kkpen','19','Mr.KK its pen');
# 创建一个仅有id,name的客户表进行插入测试
create view customerInfo_test as select prod_id,prod_name from products;
# 事实证明更新(增删改)等操作需要对原始表结果有一定的了解
# 此时因为知道prod_desc可以为空所以知道null值
# 依旧需要遵循原始表的约束
insert into customerInfo values('kkk','1002','kkkpen','19',null);
# 删除表数据
delete from customerInfo where prod_id = 'kk';
# 调用视图
select * from customerInfo;
# 发现数据成功被删除
# 更新表数据
update customerInfo set prod_price = prod_price+1 ;
# 调用视图
select * from customerInfo;
# 发现数据成功被修改
# 总结:
# 如果想要通过视图对物理表(原始表)的数据进行增删改操作
# 那么必须要求视图中的字段和原始表中的字段保持一致且符合约束
(8) 删除视图
# 删除视图格式
# drop view 视图名称;
drop view customerInfo;
十四、存储过程
1、创建存储过程
语法:
CREATE PROCEDURE 存储过程名 (参数列表)
BEGIN
SQL语句代码块
END
# 其中参数列表的形式如下:
# [ in | out | inout ] param_name type
# in 输入参数 即参数要传到存储过程中的过程里面
# out 输出参数 即参数要传到存储过程的外部
# inout 输入输出参数 即参数有值传进来也有值传出去
# param_name 参数名称
# type 参数类型
代码示例:
# 创建一个查询tb_user全部数据的存储过程
# 如果存在sp_test存储过程就删除
DROP PROCEDURE IF EXISTS sp_test;
# 设置定界符//
DELIMITER //
# 创建存储过程sp_test 注意结尾不带分号;
CREATE PROCEDURE sp_test()
# 起始关键字
BEGIN
# sql语句
SELECT * FROM tb_user;
# 结束关键字//
END //
# 重新设置定界符;
DELIMITER ;
注意:
- 由括号包围的参数必须总是存在。如果没有参数,也该使用一个空参数列。
- 每个参数默认都是一个in参数,要指定为其他参数,可在参数名称之前使用关键词out或inout
- delimiter // 语句的作用时将MySQL的结束符设置为//;
- 因为MySQL默认的语句结束符为分号;,存储过程中的SQL语句需要分号来结束
- 为了避免与存储过程中sql语句结束符冲突,需要重新设定,并以 end // 结束存储过程。
- 存储过程定义完毕后需要再次使用 delimiter ;恢复默认定界符。
2、调用存储过程
语法:
CALL 存储过程名(参数列表);
示例:
# call 唤醒一个存储过程开始工作
call procedure_name()
call procedure_name(param1,param2……)
# CALL语句是用来调用一个先前用CREATE PROCEDURE创建的存储过程。
# CALL语句可以用声明为OUT或INOUT参数的参数给它的调用者传回值。
# 存储过程名称后面必须加括号,哪怕该存储过程没有参数传递。
3、删除存储过程
语法:
DROP PROCEDURE IF EXISTS 存储过程名;
示例:
drop procedure procedure_name;
注意:该语句用来移除一个存储程序,但不能在存储过程中删除另一个存储过程,只能调用。
mysql中已经创建好了的存储过程想要修改,需要先删除,再重新创建。
4、存储过程体
存储过程体可以使用各种sql语句和过程式语句的组合,来封装数据库应用中复杂的业务逻辑和处理规则,以实现数据库应用的灵活编程。
1、变量的使用
在存储过程和函数中,可以定义和使用变量。
可以使用declare关键字来声明变量,然后可以为其赋值。
注意:这些变量的作用域是begin……end代码块中。
注意:在语法中,声明变量、游标声明、handler声明必须按照顺序书写,否则就报错。
(1)声明变量:
声明语法:
# declare 声明变量
# stu_name自定义变量名,可以同时声明多个变量
# type 指定变量的类型
# default 设置默认值 没有default默认值为null
DECLARE var_name[...] type [DEFAULT value]
代码示例:
declare stu_name varchar(30);
declare stu_gender char(2) default '男';
……
# 局部变量只能存在存储过程体中的begin……end代码块中声明和使用
# 局部变量必须在存储过程体的开头处声明
# 局部变量不用于用户变量
# 两者的区别:
# 局部变量:声明时变量名前不用使用@符号,并且只能在begin……end中使用
# 用户变量:声明变量时需要在变量名前加@符号,同时已声明的变量存在整个会话中。
(2)变量赋值:
赋值语法:
# set 关键字,设置某变量
# var_name 变量名,指定需要赋值的变量,可以同时赋值多个变量
SET var_name = value [,var_name = value]...
代码示例:
# 声明变量时直接为其初始化赋值
declare stu_name varchar(30) default '张三';
# 先声明变量 后赋值
declare stu_gender char(2);
set stu_gender = '男';
# 还可以使用select into语句为变量赋值
# stuNum 学生表中的学号字段
# into 从……插入……
# stu_name 自定义变量名
# where子句 参数指查询条件
declare stu_name varchar(30);
select stuNum into stu_name from student where stuNum = 1;
2、定义条件和处理程序
特定的条件需要特定的处理。
这些条件可以联系到错误,以及子程序中的一般流程控制。
定义条件是事先定义程序执行过程中遇到的问题。
处理程序定义了在遇到这些问题的时候应该采取的处理方式,并且保证存储过程或函数在遇到警告或错误时,能够继续执行下去。
这样可以增强存储程序处理问题的能力,避免程序异常停止运行。
(1)定义条件
定义语法:
# condition_name 代表条件的名称
# condition_type 代表条件的类型
# sqlstate_value 代表mysql抛出的错误,接收为长度5的字符串错误代码
# mysql_error_code 代表mysql抛出的错误,接授为数值类型错误代码
# 例如:ERROR1142(42000)中
# sqlstate_value的值是42000,mysql_error_code的值是1142。
DECLARE condition_name CONDITION for [condition_type]
[condition_type]:
SQLSTATE[VALUE] sqlstate_value | mysql_error_code
代码示例:
#【示例】定义ERROR1148(42000)错误,名称为command_not_allowed。
# 方法一:使用sqlstate_value
declare command_not_allowd condition for sqlstate '42000'
# 方法二:使用mysql_error_code
declare command_allowed condition for sqlstate 1148
# 这仅仅是定义了条件以及捕捉错误,它将一个名字和指定的错误条件关联起来
# 这个名字随后被用在定义处理程序的declare handler语句中处理
(2)定义处理程序
定义语法:
# handler_type 参数是指明错误的处理方式(该参数有3个取值)
# ↓
# continue 表示遇到错误后不进行处理,继续向下执行
# exit 表示遇到错误后马上退出执行
# undo 表示遇到错误后撤回之前的操作,mysql暂时不支持该处理方式
#
# condition_value 参数指明错误的类型(该参数有6个取值)
# ↓
# sqlstate_value 代表mysql抛出的错误,接收为长度5的字符串错误代码
# mysql_error_code 代表mysql抛出的错误,接授为数值类型错误代码
# condition_name 是DECLARE定义的条件名称
# SQLWARNING 表示所有以01开头的sqlstate_value值
# NOT FOUND 表示所有以02开头的sqlstate_value值
# SQLEXCEPTION 表示所有没有被SQLWARNING或NOT FOUND捕获的sqlstate_value值
# sp_statement 表示一些存储过程或函数的执行语句
DECLARE handler_type HANDLER FOR condition_value[,...] sp_statement
handler_type:
CONTINUE | EXIT | UNDO
condition_value:
SQLSTATE [VALUE] sqlstate_value | condition_name | SQLWARNING
| NOT FOUND | SQLEXCEPTION | mysql_error_code
代码示例:
# 方法一:捕获sqlstate_value
# 遇到sqlstate_value值为42000,执行CONTINUE操作,并且输出"CAN NOT FIND"信息
DECLARE CONTINUE HANDLER FOR SQLSTATE '42000'
SET @info='CAN NOT FIND';
# 方法二:捕获mysql_error_code
# 遇到mysql_error_code值为1148,执行CONTINUE操作,并且输出"CAN NOT FIND"信息
DECLARE CONTINUE HANDLER FOR 1148SET @info='CAN NOT FIND';
# 方法三:先定义条件,然后调用
# 先定义条件然后再调用条件,这里先定义can_not_find条件遇到1148错误就执行CONTINUE操作
DECLARE can_not_find CONDITION FOR 1146 ;
DECLARE CONTINUE HANDLER FOR can_not_find SET
@info='CAN NOT FIND';
# 方法四:使用SQLWARNING
# 使用SQLWARNING捕获所有以01开头的sqlstate_value值
# 然后执行EXIT操作,并且输出"ERROR"信息
DECLARE EXIT HANDLER FOR SQLWARNING SET @info='ERROR';
# 方法五:使用NOT FOUND
# 使用NOT FOUND捕获所有以02开头的sqlstate_value值
# 然后执行EXIT操作,并且输出"CAN NOT FIND"信息
DECLARE EXIT HANDLER FOR NOT FOUND SET @info='CAN NOT FIND';
# 方法六:使用SQLEXCEPTION
# SQLEXCEPTION捕获所有没有被SQLWARNING或NOT FOUND捕获的sqlstate_value值
# 然后执行EXIT操作,并且输出"ERROR"信息
DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info='ERROR';
(3)定义条件并处理程序
# 创建一个表 只有一个字段 且为主键
CREATE TABLE table_name(
s1 INT,
PRIMARY KEY(s1)
);
# 设定定界符//
DELIMITER //
# 创建存储过程handlerdemo
CREATE PROCEDURE handlerdemo()
# 存储过程体起始
BEGIN
# handler 句柄 抓取异常 抓到??异常就敢??是
# 声明 处理异常 的函数 抓取到23000的异常 就为X2赋值1
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @X2=1;
# 为X赋值1
SET @X=1;
# 插入刚刚的用户变量
INSERT INTO table_name VALUES(1);
SET @X=2;
INSERT INTO table_name VALUES(1);
SET @X=3;
# 存储过程体结束
END//
# 恢复定界符;
DELIMITER ;
/* 调用存储过程*/
CALL handlerdemo();
/* 查看调用存储过程结果*/
SELECT @X
# @X 是一个用户变量,执行结果@X等于3,表示程序执行到程序的末尾。
# 如果DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @X2=1;代码不存在
# 第二个insert 因为 primary key 约束而失败之后,mysql可能以及采取EXIT策略
# 并且select @X 可能以及返回2
# 注意:
# @X表示用户变量,使用set为其赋值,用户变量与连接有关
# 一个客户端的变量不能被其他客户端所使用,既有作用域的,当前客户端退出时,变量会字段释放
3、游标
查询语句可能查出多条记录,在存储过程和函数中使用游标来逐条读取查询结果集中的记录。
游标的使用包括声明游标、打开游标、使用游标、关闭游标。
游标必须声明在处理程序之前,并且声明在变量和条件之后。
(1)声明游标
定义语法:
# DECLARE 声明
# cursor_name 自定义游标名称
# CURSOR FOR 游标
# select_statement 表示select语句的内容,返回一个用于创建游标的结果集
DECLARE cursor_name CURSOR FOR select_statement ;
代码示例:
# 从员工表的返回的数据声明一个游标cur_employee
declare cur_employee CURSOR FOR select emp_name,emp_age from employee;
(2)打开游标
定于语法:
# open 打开
# cursor_name 游标名字
OPEN cursor_name ;
代码示例:
# 打开刚刚声明的游标
open cur_employee;
(3)使用游标
定义语法;
# fetch 关键字 逐条遍历结果集
# cursor_name 游标名称
# var_name 表示将游标中的select语句查询出来的信息存入该参数中
# var_name 必须在声明光标之前就定义好
FETCH cursor_name INTO var_name[,var_name…] ;
代码示例:
# 遍历刚刚声明的游标
FETCH cur_employee INTO emp_name, emp_age ;
# 将光标cur_employee中SELECT语句查询出来的信息存入emp_name和emp_age中
# emp_name和emp_age必须在前面已经定义
(4)关闭游标
定义语法:
# cursor_name 游标名称
CLOSE cursor_name ;
代码示例:
# 关闭cur_employee游标
# 关闭后就不能使用fetch来使用游标了
# mysql中光标只能在存储过程和函数中使用
CLOSE cur_employee ;
4、流程控制的使用
存储过程和函数中可以使用流程控制来控制语句的执行。
MySQL中可以使用IF语句、CASE语句、LOOP语句、LEAVE语句、ITERATE语句、REPEAT语句和WHILE语句来进行流程控制。
每个流程中可能包含一个单独语句,或者是使用BEGIN…END构造的复合语句,构造可以被嵌套。
(1)IF语句
IF语句用来进行条件判断。根据是否满足条件,将执行不同的语句。其语法的基本形式如下:
定义语法:
# search_condition 参数表示条件判断语句
# statement_list 参数表示不同条件的执行语句
# mysql中还有一个if()函数 它不同于这里描述的IF语句
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list] ...
[ELSE statement_list]
END IF
代码示例:
# 如果年龄小于20 那么count+1
# 如果年龄等于20 那么count2+1
# 否则count3+1
# 一定要跟上end if 退出if语句
IF age>20 THEN SET @count1=@count1+1;
ELSEIF age=20 THEN SET @count2=@count2+1;
ELSE SET @count3=@count3+1;
END IF;
(2)CASE语句
CASE语句也用来进行条件判断,其可以实现比IF语句更复杂的条件判断。CASE语句的基本形式如下:
定义语法:
# case_value 参数表示条件判断的变量
# when_value 参数表示变量的取值
# statement_list参数表示不同的when_value值的执行语句
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list] ...
[ELSE statement_list]
END CASE
CASE语句还有另一种形式。该形式的语法如下:
# search_condition 参数表示条件判断语句
# statement_list 参数表示不同条件的执行语句
CASE
WHEN search_condition THEN statement_list
[WHEN search_condition THEN statement_list] ...
[ELSE statement_list]
END CASE
代码示例:
# 对比年龄变量
# 如果是20 count1+1
# 否则count2+1
# end case 退出
CASE age
WHEN 20 THEN SET @count1=@count1+1;
ELSE SET @count2=@count2+1;
END CASE ;
# 无对比变量
# 条件判断直接写进表达式里
CASE
WHEN age=20 THEN SET @count1=@count1+1;
ELSE SET @count2=@count2+1;
END CASE ;
# 注意:这里的CASE语句和“控制流程函数”里描述的SQL CASE表达式的CASE语句有轻微不同。
# 这里的CASE语句不能有ELSE NULL子句 并且用END CASE替代END来终止!!
(3)LOOP语句
LOOP语句可以使某些特定的语句重复执行,实现一个简单的循环。
注意:LOOP语句本身没有停止循环的语句,必须是遇到leave语句等才能停止循环。
定义语法:
# begin_label 类似于别名 为loop设置一个标识名称
# statement_list参数标识需要循环执行的代码
[begin_label:] LOOP
statement_list 6
END LOOP [end_label]
代码示例:
# 声明loop死循环add_num
# 每次循环count+1
add_num: LOOP
SET @count=@count+1;
END LOOP add_num ;
(4)LEAVE语句
LEAVE语句主要用于跳出循环控制。其语法形式如下:
定义语法:
# leave 离开
# label 表示循环的标识名称
LEAVE label
代码示例:
# 声明一个LOOP循环add_num
# 每次进入循环count+1
# 设定条件判断count=100时离开循环
add_num: LOOP
SET @count=@count+1;
IF @count=100 THEN
LEAVE add_num ;
END LOOP add_num ;
(5)ITERATE语句
ITERATE语句也是用来跳出循环的语句。
但是,ITERATE语句是跳出本次循环,然后直接进入下一次循环。
ITERATE语句只可以出现在LOOP、REPEAT、WHILE语句内。
ITERATE语句的基本语法形式如下:
定义语法:
# iterate 类似于continue 跳出本次循环 加速开始下一次
# label 表示循环的标识名称
ITERATE label
代码示例:
# 声明一个LOOP循环add_num
# 每循环一次count+1
# 设定条件判断count=100时离开循环
# 或者count的值能够被3整除 就离开循环 不再执行下面的sql语句
add_num: LOOP
SET @count=@count+1;
IF @count=100 THEN
LEAVE add_num ;
ELSE IF MOD(@count,3)=0 THEN
ITERATE add_num;
SELECT * FROM employee ;
END LOOP add_num ;
# LEAVE语句和ITERATE语句都用来跳出循环语句,但两者的功能是不一样的。
# LEAVE语句是跳出整个循环,然后执行循环后面的程序。
# 而ITERATE语句是跳出本次循环,然后进入下一次循环。
# 使用这两个语句时一定要区分清楚。
(6)REPEAT语句
REPEAT语句是有条件控制的循环语句。
当满足特定条件时,就会跳出循环语句。REPEAT语句的基本语法形式如下:
定义语法:
# begin_label 类似于别名 为REPEAT设置一个标识名称
# statement_list 参数表示循环的执行语句
# search_condition 参数表示结束循环的条件,满足该条件时循环结束。
[begin_label:] REPEAT
statement_list
UNTIL search_condition
END REPEAT [end_label]
代码示例:
# 声明关键字 repeat 循环 没有为其设置别名 次循环类似do while
# 循环执行count+1的操作直至count=100循环结束
REPEAT
SET @count=@count+1;
UNTIL @count=100
END REPEAT ;
(7)WHILE语句
WHILE语句也是有条件控制的循环语句。
但WHILE语句和REPEAT语句是不一样的。
WHILE语句是当满足条件时,执行循环内的语句。
WHILE语句的基本语法形式如下:
定义语法:
# begin_label 类似于别名 为WHILE设置一个标识名称
# search_condition 参数表示循环执行的条件,满足该条件时循环执行
# statement_list 参数表示循环的执行语句
[begin_label:] WHILE search_condition DO
statement_list
END WHILE [end_label]
代码示例:
# 声明关键字 while
# 如果count小于100就一直循环
# 如果count不小于(大于或等于)就结束循环
WHILE @count<100 DO
SET @count=@count+1;
END WHILE ;
5、 MySQL通用分页存储过程
# 设置定界符//
DELIMITER //
# 检查是否存在pr_pager存储过程 如果存在就删除
DROP PROCEDURE IF EXISTS pr_pager;
# 创建一个存储过程pr_pager
CREATE PROCEDURE pr_pager(
IN p_table_name VARCHAR(100), -- 表名称
IN p_fields VARCHAR(500), -- 要显示的字段
IN pagecurrent INT, -- 当前页
IN pagesize INT, -- 每页显示的记录数
IN p_where VARCHAR(500) CHARSET utf8, -- 查询条件
IN p_order VARCHAR(100), -- 排序
OUT totalcount INT -- 总记录数
)
# 存储过程体起始
BEGIN
# 开始IF语句 设定条件 如果每页显示记录数小于等于1时 就修改为20
IF pagesize <= 1 THEN
SET pagesize = 20;
# 结束IF语句
END IF;
#开始IF语句 设定条件 为当前页数修改为1
IF pagecurrent THEN
SET pagecurrent = 1;
# 结束IF语句
END IF;
# 为用户变量赋值 startIndex首页 = 当前页减1 乘 每页记录数
SET @startIndex = (pagecurrent-1)*pagesize;
# 为用户变量赋值 endIndex尾页 = 每页记录数
SET @endIndex = pagesize;
# 为用户变量赋值 strsql 拼接select语句 判断每个查询条件是否存在值 有值就拼接 无值就进入下一个代码块
# 使用limit 用户变量首页,用户变量尾页 来分页
SET @strsql = CONCAT('select ',p_fields,' from ',p_table_name,
CASE IFNULL(p_where,'') WHEN '' THEN '' ELSE CONCAT(' where ',p_where) END,
CASE IFNULL(p_order,'') WHEN '' THEN '' ELSE CONCAT(' order by ',p_order) END,
' limit ',@startIndex,',',@endIndex);
-- 预定义一个语句,并将它赋给stmtsql
PREPARE stmtsql FROM @strsql;
EXECUTE stmtsql;
-- 释放一个预定义语句的资源
DEALLOCATE PREPARE stmtsql;
# 为用户变量赋值 拼接一个可以查询总条目数的SQL语句
SET @strsqlcount = CONCAT('select count(*) into @Rows_Total from ',p_table_name,
CASE IFNULL(p_where,'') WHEN '' THEN '' ELSE CONCAT(' where ',p_where) END);
# 定义预处理sql语句
PREPARE stmtsqlcount FROM @strsqlcount;
# 执行预处理SQL语句
EXECUTE stmtsqlcount;
# 删除预处理sql语句
DEALLOCATE PREPARE stmtsqlcount;
# 把查询到的总记录数 赋值给totalcount这个出参 传出存储过程
SET totalcount = @Rows_Total;
-- 计算总数也可以是下面这种方法
-- SELECT COUNT(*) INTO totalcount FROM tb_user;
# 结束存储过程体
END //
# 恢复定界符
DELIMITER ;
6、存储过程调用
1、 不带查询条件和排序
CALL pr_pager('t_user','id,username,birthday,sex,address',1,5,
NULL,NULL,@totalcount);
SELECT @totalcount;
2、 带查询条件和排序
CALL pr_pager('t_user','id,username,birthday,sex,address',1,5,
'username like \'小%\'','id asc',@totalcount);
SELECT @totalcount;
7、入参出参代码示例
语法: IN | OUT | INOUT param_name type
在使用语句的时候,不可避免的要考虑参数的问题,而参数又是用来辅助变量的。
IN:入参
引入值,in只负责传入参数到存储过程中(类似Java形参)
OUT:出参
返回值,来的时候可能没值,走的时候被赋了值
INOUT:出参和入参
引入返回,把in和out合并成一个关键字使用,既可以引入参数也可以返回参数
(1)无参数代码示例
-- 1.将mysql分隔符从;设置为&
DELIMITER &
-- 2.如果存在存储过程proc1则删除
DROP PROCEDURE IF EXISTS `proc1` &
-- 3.定义存储过程(无参)
CREATE PROCEDURE proc1()
-- 4.开始
BEGIN
-- 5.执行指定sql
SELECT COUNT(*) FROM t1 t WHERE t.name LIKE '%1%';
-- 6.结束
END &
-- 7.将mysql分隔符从;设置为;
DELIMITER ;
-- 8.调用存储过程
CALL proc1();
-- 9.如果存在存储过程proc1则删除
DROP PROCEDURE IF EXISTS proc1;
(2)引入参数代码示例
# 设置定界符$$
delimiter $$
# 创建存储过程sp_param02 带in引入参数
create procedure sp_param01(in ename VARCHAR(10))
# 起始
begin
# 执行体
set @emp_name = ename;
# 结束
end $$
# 调用 给出引入参数
call sp_param01('稻盛和夫') $$
# 查询变量
select @emp_name $$
(3)返回参数代码示例
/*------------in引入参数---out返回参数------------*/
# 根据in入参 返回out出参
# 设置定界符$$
delimiter $$
# 创建存储过程sp_param02 带in引入参数和out返回参数
create procedure sp_param02(in dept_loc VARCHAR(13), out dept_name VARCHAR(14))
# 存储过程体起始
begin
# 执行体
select DNAME into dept_name from dept d where d.LOC = dept_loc;
# 存储过程体结束
end $$
# 调用,给出入参,带出返回
call sp_param02('BEIJING',@dept_name) $$
# 查询返回
select @dept_name $$
call sp_param02('SHANHAI',@dept_name) ;
select @dept_name $$
# 强调一下 入参 和 出参
# 入参 只管入, 入之后为其赋值,他也没值
# 出参 只管出, 出参来的时候是没值的 内部不可以被调用
(4)即可引入参数也可以返回参数 代码示例
/*------------in引入参数---inout即可引入参数也可以返回参数------------*/
# 设置定界符$$
delimiter $$
# 创建存储过程sp_param02 带in引入参数和inout即可引入页可以返回参数
create procedure sp_param03_inout(in dept_loc VARCHAR(13), inout dept_name VARCHAR(14))
# 起始
begin
# 出参 加上dept_name带过来的参数
# 声明局部变量
declare d_name varchar(14);
# 为变量赋值
set d_name = dept_name;
select d.DNAME into dept_name from dept d where d.LOC = dept_loc;
# 拼接 部门名称 和传进来的参数 使用concat()函数
select concat(dept_name,'-',d_name);
# 结束
end $$
# 此时修改变量的值 依旧可以生效 说明inout即可以入也可以出
set @dept_name ='HELLO111';
# 调用 给出引入参数 带回返回参数
call sp_param03_inout('BEIJING',@dept_name);
# 查询inout参数
select @dept_name;
8、流程控制代码实例
(1)IF语句
# -------------if---------流程控制
# 入职年<=38新手 >38年<=40 老员工 >40元老
select timestampdiff(year,e.hiredate,now()) from emp e where e.empno ='7499';
# 设置定界符//
delimiter //
create procedure sp_hire_if()
# 起始
begin
# 声明变量 用来存储用户的职称
declare result varchar(32);
# 声明变量 用来存储用户的工作年限
declare years int;
# 查询员工编号为7499的员工入职日期,利用函数返回入职年限
select timestampdiff(year,e.HIREDATE,now()) into years from emp e where e.empno ='7499';
# 判断员工入职年限
if years>40 then
# 赋值用户职称
set result = '元老';
elseif years>38 then
set result = '老员工';
else
set result ='新手';
end if;
select result;
# 结束
end //
# 恢复定界符
delimiter ;
# 调用存储过程
call sp_hire_if();
(2)LOOP死循环
# ################ loop死循环
# leave 离开循环 = break
delimiter //
create procedure sp_flow_loop()
begin
#声明变量用来存储循环次数 起始1
declare c_index int default 1;
# 声明循环
num_loop:loop
select c_index;
if c_index>=10
# 满足条件 离开循环
then leave num_loop;
end if;
set c_index = c_index +1;
end loop num_loop;
end //
delimiter ;
call sp_flow_loop();
(3)拼接字符串
# 循环拼接字符串
delimiter //
create procedure sp_flow_loop002()
begin
#声明变量用来存储循环次数 起始1
declare c_index int default 1;
declare result_str varchar(256) default '1';
# 声明循环
cnt:loop
set c_index = c_index +1;
set result_str = concat(result_str,',',c_index);
if c_index < 10
then iterate cnt;
end if;
# 下面这句代码能否执行?什么时候执行? 能执行,等if条件不成立的时候执行
leave cnt;
end loop cnt;
select result_str;
end //
delimiter ;
call sp_flow_loop002();
(4)REPEAT语句
# 直到……位置 才结束 repeat(do while循环)
# repeat结束循环使用 until 加条件 结尾不需要分号;
delimiter //
create procedure sp_flow_repeat()
begin
declare c_index int default 1;
declare result_str varchar(256) default '1';
count_lab:repeat
set c_index = c_index +1;
set result_str = concat(result_str,'-',c_index);
until c_index >= 10
end repeat count_lab;
select result_str;
end //
delimiter ;
call sp_flow_repeat;
(5)WHILE语句
#############(while)判断条件 执行代码
delimiter //
create procedure sp_flow_while()
begin
declare c_index int default 1;
declare result_str varchar(256) default '1';
while c_index < 10 do
set c_index = c_index +1;
set result_str = concat(result_str,'/',c_index);
end while;
select result_str;
end //
delimiter ;
call sp_flow_while;
9、练习
(1)查询数据
要求:按照部门名称查询,通过select查询员工的编号、姓名、薪资
# 设置定界符//
delimiter //
# 创建high_sal存储过程引入参数(因为要根据指定部门来查询数据)
create procedure search_empInfo(in dept_name varchar(14))
# 过程体起始
begin
# 声明变量 用来存储员工id、name、sal
declare e_no int;
declare e_name varchar(13);
declare e_sal decimal(7,2);
# 声明变量 用来开关循环
declare lp_flag boolean default true;
# 声明游标 从指定部门的员工部门信息结果集建立游标
declare emp_cursor cursor for
select e.empno, e.ename, e.sal
from emp e,dept d
where e.deptno = d.deptno and d.dname = dept_name;
# 定义处理程序
# continue 遇到错误不处理 继续向下执行代码
# handler 句柄
# not found 所有以02开头的sqlstate_value值
# 如果捕捉到符合的异常错误 就关掉循环
declare continue handler for not found set lp_flag = false;
# 打开游标
# 遍历游标所在集合
# fetch emp_cursor into e_no,e_name,e_sal; 只能遍历出一条数据
# 利用循环遍历所有数据
# 使用循环时依旧会出现异常 因为fetch 遍历会因为没数据而抛出异常
# 需要使用handler句柄控制循环
open emp_cursor;
# 声明loop锁循环 别名emp_loop 也是锁循环的起始
emp_loop:loop
# 遍历出的结果 赋值给已经声明的变量
fetch emp_cursor into e_no,e_name,e_sal;
# 如果开关是开 就查询已经遍历到的数据
# 如果开关是关 就离开锁循环
if lp_flag then
select e_no,e_name,e_sal;
else
leave emp_loop;
end if;
# 锁循环结束
end loop emp_loop;
# 设定结束语句
set @end_flag = 'END';
# 关闭游标
close emp_cursor;
# 过程体结束
end //
# 恢复界定符
delimiter ;
# 调用该存储过程 并给出部门名称
call search_empInfo('ACCOUNTING');
# 调用用户变量 判断是否执行循环结束后的代码
select @end_falg;
(2)更新数据
要求:按照部门名称,为其中的员工增加薪水(老板不涨)
# 设置定界符//
delimiter //
# 创建update_emp_sal存储过程引入参数(因为要根据指定部门来更新数据)
create procedure update_emp_sal(in dept_name varchar(14))
# 过程体起始
begin
# 声明变量 用来存储员工id、name、sal
declare e_no int;
declare e_name varchar(13);
declare e_sal decimal(7,2);
# 声明变量 用来开关循环
declare lp_flag boolean default true;
# 声明游标 从指定部门的员工部门信息结果集建立游标
declare emp_cursor cursor for
select e.empno, e.ename, e.sal
from emp e,dept d
where e.deptno = d.deptno and d.dname = dept_name;
# 定义处理程序
# continue 遇到错误不处理 继续向下执行代码
# handler 句柄
# not found 所有以02开头的sqlstate_value值
# 如果捕捉到符合的异常错误 就关掉循环
declare continue handler for not found set lp_flag = false;
# 打开游标
# 遍历游标所在集合
# fetch emp_cursor into e_no,e_name,e_sal; 只能遍历出一条数据
# 利用循环遍历所有数据
# 使用循环时依旧会出现异常 因为fetch 遍历会因为没数据而抛出异常
# 需要使用handler句柄控制循环
open emp_cursor;
# 声明loop锁循环 别名emp_loop 也是锁循环的起始
emp_loop:loop
# 遍历出的结果 赋值给已经声明的变量
fetch emp_cursor into e_no,e_name,e_sal;
# 控制流程
# 如果开关是开
# 判断此次遍历的员工名称,是king就跳过本次循环否则就执行sql语句
# 如果开关是关
# 那么离开锁循环
if lp_flag then
if e_name = 'king' then
iterate emp_loop;
else
update emp e set e.sal = e.sal +500 where e.EMPNO = e_no;
end if;
else
leave emp_loop;
end if;
# 锁循环结束
end loop emp_loop;
# 设定结束语句
set @end_flag = 'END';
# 关闭游标
close emp_cursor;
# 过程体结束
end //
# 恢复界定符
delimiter ;
# 调用该存储过程 并给出部门名称
call update_emp_sal('ACCOUNTING');
# 调用用户变量 判断是否执行循环结束后的代码
select @end_falg;
(3)循环创建表
创建下一个月的每天对应的表
表名:comp_2021_07_01…………comp_2021-07_31
需求描述(模拟):
- 需要某个表记录很多数据,比如某用户的搜索、购买等行为(假设)
- 当每天记录较多时,如果把所有数据存放到一个表中储存体量过于庞大,不好管理和增删改查等操作。
- 所以需要分表,要求是每天的数据存储一个表,一天一表。
- 此时需要考虑预处理,提前生产这些表(每月月底创建下一个月每天的表)
# 设置定界符//
delimiter //
# 创建loop_create_tables存储过程
create procedure loop_create_tables()
# 存储过程体的起始
begin
# 声明变量 用来存储下一个年份
declare next_year int;
# 声明变量 用来存储下一个月份
declare next_month int;
# 声明变量 用来存储下一个天数
declare next_month_day int;
# 声明变量 存储下一个月份的字符串
declare next_month_str char(2);
# 声明变量 存储下一个天数的字符串
declare next_month_day_str char(2);
# 声明变量 存储下一个年份的字符串
# 此时因为一年之中只有一个年份 所以没有特殊声明 用到时候直接拼接即可
# 声明变量 存储每天对应的表名字的字符串
declare table_name_str char(10);
# 声明变量 用来控制循环 每个月从1号开始
declare t_index int default 1;
# 声明变量 用来存储创建表的create sql 语句
# 后来发现用不到 因为prepare预处理只接受用户变量
-- declare create_ table_sql varchar (200);
# 获取下个月的年份
set next_year = year(date_add(now(),INTERVAL 1 month));
# 获取下个月是几月
set next_month = month(date_add(now(),INTERVAL 1 month));
# 下个月最后一天是几号
set next_month_day = dayofmonth(last_day(date_add(now( ),INTERVAL 1 month)));
if (next_month < 10)
then set next_month_str = concat('0',next_month);
else
set next_month_str = concat('',next_month);
end if;
# t_index循环接收记录数
# next_month_day 每月的最后一天数
# 如果记录数小于每月最后天数那么就进入循环
while t_index <= next_month_day do
# 每次进入循环判断记录数是否小于10
# 小于10
# 因为10天之前都是单号1,2,3…………
# 所以需要为之做处理,如果小于10那么在前面拼接0
# 等于或大于10
# 就不拼接
if (t_index < 10)
then set next_month_day_str = concat('0',t_index);
else
set next_month_day_str = concat('',t_index);
end if;
# 将每次循环到的年份、月份、天数拼接成字符串
# 比如table_name_str = 2021_07_01
set table_name_str = concat(next_year,'_',next_month_str,'_',next_month_day_str);
# 声明用户变量,存储create sql语句 拼接每次得到的对应每年每月每日的表名
set @create_table_sql = concat(
'create table comp_',table_name_str,
'(grade int null,
losal int null,
hisal int null);'
);
# prepare 预编译
# 注意 预编译 from 后面 不可以使用局部变量
prepare create_table_stmt from @create_table_sql;
# execute 执行预编译
execute create_table_stmt;
# deallocate prepare 删除预编译 为下一个月做准备
deallocate prepare create_table_stmt;
# 记录数+1 控制循环
set t_index = t_index+1;
end while;
# 存储过程体的结束
end //
# 调用存储过程
call loop_create_tables();
十五、事务
1、 事务的概念
可以把事务看做数据处理的一个最小独立单元;
- 事务主要用于处理操作量大,复杂度高的数据
比如说:在人员管理系统中,删除一个人员,既需要删除人员的基本资料;
也要删除和该人员相关的信息,如信箱,文章等等这样;
这些数据库操作语句就构成一个事务
2、 MySQL中的事务
- MySQL 中使用了 Innodb 数据库引擎的数据库或表才支持事务
- 事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行;
- 事务用来管理
insert、update、delete
语句- 在
MySQL
命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作;要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION
3、 事务满足的条件
原子性:
- 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
- 事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性:
- 在事务开始之前和事务结束以后,数据库的完整性没有被破坏
- 这表示写入的资料必须完全符合所有的预设规则
- 这包含资料的精确度、串联性以及后续数据库可以自发完成预定的工作
隔离性:
- 数据库允许多个并发事务同时对其数据进行读写和修改的能力
- 隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致
- 事务隔离分为不同级别:
- 读未提交(Read uncommitted)
- 读提交(read committed)
- 可重复读(repeatable read)
- 串行化(Serializable)
持久性:
- 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
隔离级别 | 脏读 | 幻读 | 不可重复读 | 第一类丢失更新 | 第二类丢失更新 |
---|---|---|---|---|---|
READ UNCOMMITED(读未提交) | 允许 | 允许 | 允许 | 不允许 | 允许 |
READ COMMITTED(读已提交) | 不允许 | 允许 | 允许 | 不允许 | 允许 |
REPEATABLE READ(可重复读) | 不允许 | 允许 | 不允许 | 不允许 | 不允许 |
SERIALIZABLE (串行化) | 不允许 | 不允许 | 不允许 | 不允许 | 不允许 |
读取未提交数据(脏读)
不可重复读:(多次读取,内容不一致)
幻读:(前后多次读取,数据总量不一致)
4、 事务控制语句
BEGIN 或 START TRANSACTION
-- 显式地开启一个事务
COMMIT
-- 提交事务,并使已对数据库进行的修改成为永久性
ROLLBACK
-- 回滚会结束用户的事务,并撤销正在进行的所有未提交的修改
5、 事务测试
-- 成功提交的事务
begin;
insert into publisher values(3,'邮电出版社','上海');
commit;
select * from publisher;
-- 回滚的事务
begin;
insert into publisher values(3,'邮电出版社','上海');
rollback;
select * from publisher;
-- 创建银行卡表
create table card(
id int primary key,
name varchar(50) not null,
balance decimal(10,2)
);
-- 插入测试数据
insert into card values
(1,'derek', 1000),
(2,'linda', 2000);
-- 正常提交的事务
start transaction;
update card set balance=balance-500 where name='derek';
update card set balance=balance+500 where name='linda';
commit;
select * from card;
-- 回滚的事务
start transaction;
update card set balance=balance-500 where name='derek';
update card set balance=balance+500 where name='linda';
rollback;
select * from card;