视图、触发器、事务、存储过程、函数
一 视图
1 什么是视图
视图是一个虚拟表(非真实存在) ,其本质是【根据SQL语句获取动态的数据集,并为其命名】,用户使用时只需要使用【名称】即可获得结果集,可以将该结果当作表来使用。
使用视图我们可以把查询结果过程中的临时表摘出来,用视图去实现,这样可以以后再想操作该临时表的数据时就无需重写复杂的sql了,直接去视图中查找即可,但视图有明显的效率问题,并且视图是存放在数据库中的,如果我们程序中使用的sql过分依赖数据库中的视图,即强耦合,那就意味着扩展sql极为不便,因此并不推荐使用。
# 示例:临时表应用
# 两张有关系的表
mysql> select * from course;
+-----+--------+------------+
| cid | cname | teacher_id |
+-----+--------+------------+
| 1 | 生物 | 1 |
| 2 | 物理 | 2 |
| 3 | 体育 | 3 |
| 4 | 美术 | 2 |
+-----+--------+------------+
4 rows in set (0.00 sec)
mysql> select * from teacher;
+-----+-----------------+
| tid | tname |
+-----+-----------------+
| 1 | 张磊老师 |
| 2 | 李平老师 |
| 3 | 刘海燕老师 |
| 4 | 朱云海老师 |
| 5 | 李杰老师 |
+-----+-----------------+
5 rows in set (0.00 sec)
# 查询李平老师教授的课程名
mysql> select cname from course where teacher_id = (select tid from teacher where tname='李平老师');
+--------+
| cname |
+--------+
| 物理 |
| 美术 |
+--------+
2 rows in set (0.00 sec)
# 子查询出临时表,作为teacher_id等判断依据
select tid from teacher where tname='李平老师'
2 创建视图
"语法": CREATE VIEW 视图名称 AS SQL语句;
# 于是查询李平老师教授的课程名的sql可以改写为
mysql> select cname from course where teacher_id = (select tid from teacher_view);
+--------+
| cname |
+--------+
| 物理 |
| 美术 |
+--------+
# 注意:"
1).使用视图后就无需每次都重写子查询的sql,但是这么效率不高,还不如我们写子查询效率高;
2).而且有一个致命的问题:视图是存放到数据库里的,如果我们程序中的SQL过分依赖于数据库中存放的视图,那么意味着,一旦SQL需要修改且涉及到视图的部分,则必须去数据库中进行修改,而通常在公司中数据库有专门的DBA负责,修改需付出巨大的沟通成本,极不方便。
3 使用视图
# 修改原始表,原始视图也跟着改
mysql> select * from course;
+-----+--------+------------+
| cid | cname | teacher_id |
+-----+--------+------------+
| 1 | 生物 | 1 |
| 2 | 物理 | 2 |
| 3 | 体育 | 3 |
| 4 | 美术 | 2 |
+-----+--------+------------+
# 创建表course的视图
mysql> create view course_view as select * from course;
mysql> select * from course_view;
+-----+--------+------------+
| cid | cname | teacher_id |
+-----+--------+------------+
| 1 | 生物 | 1 |
| 2 | 物理 | 2 |
| 3 | 体育 | 3 |
| 4 | 美术 | 2 |
+-----+--------+------------+
# 更新视图中的数据
mysql> update course_view set cname='xxx';
# 往视图中插入数据
mysql> insert into course_view values(5,'yyy',2);
# 发现原始表的记录也跟着修改了
mysql> select * from course;
+-----+-------+------------+
| cid | cname | teacher_id |
+-----+-------+------------+
| 1 | xxx | 1 |
| 2 | xxx | 2 |
| 3 | xxx | 3 |
| 4 | xxx | 2 |
| 5 | yyy | 2 |
+-----+-------+------------+
我们不应该修改视图中的记录,而且在涉及多个表的情况下是根本无法修改视图中的记录的,如下图
4 修改视图
"语法": ALTER VIEW 视图名称 AS SQL 语句;
mysql> alter view teacher_view as select * from course where cid>3;
mysql> select * from teacher_view;
+-----+-------+------------+
| cid | cname | teacher_id |
+-----+-------+------------+
| 4 | xxx | 2 |
| 5 | yyy | 2 |
+-----+-------+------------+
5 删除视图
"语法" : DROP VIEW 视图名称;
DROP VIEW teacher_view;
二 触发器
1 为何要使用触发器
使用触发器可以定制用户对表进行【增、删、改】操作前后的行为。!!注意:没有修改。
2 创建触发器
# 创建触发器的语法:
# 插入前
CREATE TRIGGER tri_before_insert_tb1 BEFORE INSERT ON tb1 FOR EACH ROW
BEGIN
...
END
# 插入后
CREATE TRIGGER tri_after_insert_tb1 AFTER INSERT ON tb1 FOR EACH ROW
BEGIN
...
END
# 删除前
CREATE TRIGGER tri_before_delete_tb1 BEFORE DELETE ON tb1 FOR EACH ROW
BEGIN
...
END
# 删除后
CREATE TRIGGER tri_after_delete_tb1 AFTER DELETE ON tb1 FOR EACH ROW
BEGIN
...
END
# 更新前
CREATE TRIGGER tri_before_update_tb1 BEFORE UPDATE ON tb1 FOR EACH ROW
BEGIN
...
END
# 更新后
CREATE TRIGGER tri_after_update_tb1 AFTER UPDATE ON tb1 FOR EACH ROW
BEGIN
...
END
# 示例:插入后触发触发器
"特别的语法:NEW表示即将插入的数据行,OLD表示即将删除的数据行。"
# 准备表
CREATE TABLE cmd (
id INT PRIMARY KEY auto_increment,
USER CHAR (32),
priv CHAR (10),
cmd CHAR (64),
sub_time datetime, # 提交时间
success enum ('yes', 'no') # 0代表执行失败
);
CREATE TABLE errlog (
id INT PRIMARY KEY auto_increment,
err_cmd CHAR (64),
err_time datetime
);
# 创建触发器
mysql> delimiter // # 将结束符号修改为//,让MySQL允许我们输入多个含分号";"的语句,最后修改回
来,让前面的争端语句生效。
mysql> CREATE TRIGGER tri_after_insert_cmd AFTER INSERT ON cmd FOR EACH ROW
-> BEGIN
-> IF NEW.success = 'no' THEN # 等值判断只有一个等号
-> INSERT INTO errlog(err_cmd, err_time) VALUES(NEW.cmd, NEW.sub_time) ; # 必须加分号
-> END IF ; # 必须加分号
-> END//
Query OK, 0 rows affected (0.10 sec)
mysql> delimiter ;
mysql>
# 往表cmd中插入记录,触发触发器,根据IF的条件决定是否插入错误日志
INSERT INTO cmd (
USER,
priv,
cmd,
sub_time,
success
)
VALUES
('egon','0755','ls -l /etc',NOW(),'yes'),
('egon','0755','cat /etc/passwd',NOW(),'no'),
('egon','0755','useradd xxx',NOW(),'no'),
('egon','0755','ps aux',NOW(),'yes');
# 查询错误日志,发现有两条
mysql> select * from errlog;
+----+-----------------+---------------------+
| id | err_cmd | err_time |
+----+-----------------+---------------------+
| 1 | cat /etc/passwd | 2020-09-09 16:50:55 |
| 2 | useradd xxx | 2020-09-09 16:50:55 |
+----+-----------------+---------------------+
2 rows in set (0.00 sec)
3 使用触发器
触发器无法由用户直接调用,只是由于对表的【增/删/改】操作被动引发。
4 删除触发器
DROP TRIGGER tri_after_insert_cmd;
三 事务
1 什么是事务
数据库事务是指作为单个逻辑单元执行的一系列操作(SQL语句) 。这些操作要么全部执行,要么全部不执行。
2 为什么需要事务
"经典的银行转账行为,A账户转给B账户10元,数据库操作需要两步,第一步A账户减10元,第二步B账户加10元,如果没有事务并且在两步中间发生异常,就会导致A的账户少了10元,但B的账户没有变化,如果不能保证这两步操作统一,银行的转账业务也没法进行展开了。"
事务管理是每个数据库(Oracle,MySQL,db等) 都必须实现的。
3 事务特性(4种) :
1).原子性(atomicty) :强调事务的不可分割;
2).一致性(consistency) :事务的执行的前后数据的完整性保持一致;
3).隔离性(isolation) :一个事务执行的过程中,不应该受到其他事务的干扰;
4).持久性(durability) :事务一旦结束,数据就持久到数据库。
4 事务运行模式(3种) :
1).自动提交事务:默认的事务管理模式。如果一个语句成功地完成,则提交该语句;如果遇到错误,则回滚该语句。
2).显式事务:以 BEGIN TRANSACTION 显式开始,以 COMMIT 或 ROLLBACK 显式结束。
3).隐式事务:当连接以此模式进行操作时,sql将在提交或回滚当前事务后自动启动新事务。无须描述事务的开始,只需提交或回滚每个事务。它生成连续的事务链。
5 使用案例
5.1 在MySQL中的使用
# 示例:
create table user(
id int primary key auto_increment,
name char(32),
balance int
);
insert into user(name,balance) values
('wsb',1000),
('egg',1000),
('ysb',1000);
# 原子操作
start transaction;
update user set balance=900 where name='wsb'; # 买支付100元
update user set balance=1010 where name='egg'; # 中介拿走10元
update user set balance=1090 where name='ysb'; # 卖家拿到90元
commit; # 真正提交到数据库中
# 出现异常,回滚到初始状态
start transaction;
update user set balance=900 where name='wsb'; # 买支付100元
update user set balance=1010 where name='egg'; # 中介拿走10元
update user set balance=1090 where name='ysb'; # 卖家拿到90元,出现异常,没有拿到
rollback;
commit;
mysql> select * from user;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | wsb | 1000 |
| 2 | egg | 1000 |
| 3 | ysb | 1000 |
+----+------+---------+
3 rows in set (0.00 sec)
5.2 在pymysql中实现事务处理
"pymysql执行的所有sql语句,都会默认放入一个事务中去。针对查询语句,事务并没有什么影响,更多的是针对修改语句。"
import pymysql # pip3 install pymysql
connect = pymysql.connect(host="127.0.0.1", port=3306, user="root", password="123", db="db13", charset="utf8mb4")
cursor = connect.cursor(cursor=pymysql.cursors.DictCursor)
try:
cursor.execute(sql_1)
cursor.execute(sql_2)
cursor.execute(sql_3)
except Exception as e:
connect.rollback() # 事务回滚
print('事务处理失败', e)
else:
connect.commit() # 事务提交
print('事务处理成功', cursor.rowcount) # 关闭连接
connect.close()
6 总结
事务用于将某些操作的多个SQL语句作为原子性操作,一旦有某一个错误,即可回滚到原来的状态,从而保证数据的完整性。
四 存储过程
1 什么是存储过程
存储过程包含了一系列可执行的sql语句,存储过程存放于MySQL中,通过调用它的名字可以执行其内部的一堆sql语句。相当于一个封装好的接口,里面有各种业务逻辑。
1.1 补充:程序于数据库结合使用的三种方式
# 方式一:
MySQL:存储过程
程序:调用存储过程
# 方式二:
MySQL:建库、表及关系
程序:纯SQL语句(开发人员写)
# 方式三:
MySQL:建库、表及关系
程序:类和对象,即ORM(本质还是纯SQL语句) (对象关系映射;类映射成表,对象映射成表的记录)
2 存储过程的优缺点
# 使用存储过程的优点:
1) 基于替代程序写的SQL语句,实现程序与SQL解耦;
2) 基于网络传输,传别名的数据量小,而直接传SQL数据量大。
# 使用存储过程的缺点:
1) 程序扩展功能不方便
3 创建简单存储过程(无参)
# 创建无参存储过程
mysql> delimiter $$
mysql> create procedure p1()
-> begin
-> select * from emp;
-> end $$
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> delimiter ;
mysql>
# 在MySQL中调用:
mysql> call p1();
+----+------------+--------+------+--------+
| id | name | sex | age | dep_id |
+----+------------+--------+------+--------+
| 1 | egon | male | 18 | 200 |
| 2 | alex | female | 48 | 201 |
| 3 | wupeiqi | male | 38 | 201 |
| 4 | yuanhao | female | 28 | 202 |
| 5 | liwenzhou | male | 18 | 200 |
| 6 | jingliyang | female | 18 | 204 |
| 7 | xxx | male | 66 | NULL |
+----+------------+--------+------+--------+
# 在python中基于pymysql调用:
cursor.callproc('p1')
print(cursor.fetchall())
4 创建存储过程(有参)
对于存储过程,可以接收参数,其参数有三类:
(1) in ————>仅用于接收传入参数用;
(2) out ————>仅用于返回值用;
(3) inout ————>既可以传入又可以当作返回值。
# 创建有参存储过程
delimiter $$
create procedure p2(
in n int, # 指定多个字段,必须加逗号隔开,最后一个不加逗号
out res int
)
begin
select * from emp where id > n; # 查找id>n 的记录
set res=1; # 自己设定,控制返回值,代表本条成功与否
end $$
delimiter ;
# 在MySQL中调用
mysql> set @x=1111; # 必须事先定义一个变量,后面要查看变量的结果
Query OK, 0 rows affected (0.00 sec)
mysql> call p2(3,x);
ERROR 1414 (42000): OUT or INOUT argument 2 for routine d47.p2 is not a variable or NEW pseudo-variable in BEFORE trigger
mysql> call p2(3,@x);
+----+------------+--------+------+--------+
| id | name | sex | age | dep_id |
+----+------------+--------+------+--------+
| 4 | yuanhao | female | 28 | 202 |
| 5 | liwenzhou | male | 18 | 200 |
| 6 | jingliyang | female | 18 | 204 |
| 7 | xxx | male | 66 | NULL |
+----+------------+--------+------+--------+
4 rows in set (0.00 sec)
mysql> select @3;
+------+
| @3 |
+------+
| NULL |
+------+
1 row in set (0.00 sec)
mysql> select @x;
+------+
| @x |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
# 在python中基于pyMySQL调用
import pymysql # pip3 install pymysql
connect = pymysql.connect(host="127.0.0.1", port=3306, user="root", password="123", db="day47", charset="utf8mb4")
cursor = connect.cursor(cursor=pymysql.cursors.DictCursor)
cursor.callproc('p2', (3, 0)) # @_p2_0=3,@_p2_1=0 # 0相当于set @res=0
'''
set @_p2_0=3 【底层组织成的一个变量的格式】
set @_p2_1=0
call p2(@_p2_0,@_p2_1); #@_p2_0代表第一个参数,@_p2_1代表第二个参数,即返回值
'''
print(cursor.fetchall()) # 查询select的查询结果
# [{'id': 4, 'name': 'yuanhao', 'sex': 'female', 'age': 28, 'dep_id': 202}, {'id': 5, 'name': 'liwenzhou', 'sex': 'male', 'age': 18, 'dep_id': 200}, {'id': 6, 'name': 'jingliyang', 'sex': 'female', 'age': 18, 'dep_id': 204}, {'id': 7, 'name': 'xxx', 'sex': 'male', 'age': 66, 'dep_id': None}]
cursor.execute('select @_p2_0,') # 执行一个sql语句查看返回值
print(cursor.fetchall()) # [{'@_p2_0': 3}] 【3这个值不会变,一直是3】
cursor.execute('select @_p2_1')
print(cursor.fetchall()) # [{'@_p2_1': 1}] 【会变,查询成功返回值是1,这个是我们自己在存储过程中设定的那个值】
cursor.close()
connect.close()
5 执行存储过程
5.1 在MySQL中执行存储过程
-- 无参数
call proc_name()
-- 有参数,全in
call proc_name(1,2)
-- 有参数,有in,out, inout
set @t1=0;
set @t2=3;
call proc_name(1,2,@t1,@t2)
5.2 在python中基于pymysql执行存储过程
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import pymysql
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='t1')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
# 执行存储过程
cursor.callproc('p1', args=(1, 22, 3, 4))
# 获取执行完存储的参数
cursor.execute("select @_p1_0,@_p1_1,@_p1_2,@_p1_3")
result = cursor.fetchall()
conn.commit()
cursor.close()
conn.close()
print(result)
6 删除存储过程
DROP procedure prco_name;
五 函数
1 内置函数
1.1 需要掌握函数:date_format
# 1) 基本使用
mysql> SELECT DATE_FORMAT('2009-10-04 22:23:00', '%W %M %Y');
-> 'Sunday October 2009'
mysql> SELECT DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s');
-> '22:23:00'
mysql> SELECT DATE_FORMAT('1900-10-04 22:23:00',
-> '%D %y %a %d %m %b %j');
-> '4th 00 Thu 04 10 Oct 277'
mysql> SELECT DATE_FORMAT('1997-10-04 22:23:00',
-> '%H %k %I %r %T %S %w');
-> '22 22 10 10:23:00 PM 22:23:00 00 6'
mysql> SELECT DATE_FORMAT('1999-01-01', '%X %V');
-> '1998 52'
mysql> SELECT DATE_FORMAT('2006-06-00', '%d');
-> '00'
# 2) 代码示例 :用于按月份进行分组
mysql> CREATE TABLE blog (
-> id INT PRIMARY KEY auto_increment,
-> NAME CHAR (32),
-> sub_time datetime
-> );
mysql> INSERT INTO blog (NAME, sub_time)
-> VALUES
-> ('第1篇','2015-03-01 11:31:21'),
-> ('第2篇','2015-03-11 16:31:21'),
-> ('第3篇','2016-07-01 10:21:31'),
-> ('第4篇','2016-07-22 09:23:21'),
-> ('第5篇','2016-07-23 10:11:11'),
-> ('第6篇','2016-07-25 11:21:31'),
-> ('第7篇','2017-03-01 15:33:21'),
-> ('第8篇','2017-03-01 17:32:21'),
-> ('第9篇','2017-03-01 18:31:21');
mysql> select date_format(sub_time,"%Y-%m") as t,count(id) from blog group by t;
+---------+-----------+
| t | count(id) |
+---------+-----------+
| 2015-03 | 2 |
| 2016-07 | 4 |
| 2017-03 | 3 |
+---------+-----------+
3 rows in set (0.00 sec)
1.2 其他内置函数
http://doc.mysql.cn/mysql5/refman-5.1-zh.html-chapter/functions.html#encryption-functions
2 自定义函数
# 1.注意事项:
(1) 函数中不要写SQL语句(否则会报错) ,函数仅仅是一个功能,是一个在SQL中被应用的功能;
(2) 若想在begin···end···中写SQL,需要使用存储过程。
delimiter //
create function f1(
i1 int,
i2 int)
returns int
BEGIN
declare num int;
set num = i1 + i2;
return(num);
END //
delimiter ;
delimiter //
create function f5(
i int
)
returns int
begin
declare res int default 0;
if i = 10 then
set res=100;
elseif i = 20 then
set res=200;
elseif i = 30 then
set res=300;
else
set res=400;
end if;
return res;
end //
delimiter ;
3 删除函数
DROP function func_name;
4 执行函数
# 获取返回值
select UPPER('egon') into @res;
SELECT @res;
# 在查询中使用
select f1(11,nid),name from tb2;
六 流程控制
1 if条件语句
delimiter //
CREATE PROCEDURE proc_if ()
BEGIN
declare i int default 0;
if i = 1 THEN
SELECT 1;
ELSEIF i = 2 THEN
SELECT 2;
ELSE
SELECT 7;
END IF;
END //
delimiter ;
2 循环语句
2.1 while循环
delimiter //
CREATE PROCEDURE proc_while ()
BEGIN
DECLARE num INT ;
SET num = 0 ;
WHILE num < 10 DO
SELECT
num ;
SET num = num + 1 ;
END WHILE ;
END //
delimiter ;
2.2 repeat循环"
delimiter //
CREATE PROCEDURE proc_repeat ()
BEGIN
DECLARE i INT ;
SET i = 0 ;
repeat
select i;
set i = i + 1;
until i >= 5
end repeat;
END //
delimiter ;
2.3.loop
BEGIN
declare i int default 0;
loop_label: loop
set i=i+1;
if i<8 then
iterate loop_label;
end if;
if i>=10 then
leave loop_label;
end if;
select i;
end loop loop_label;
END