一、视图
(一)什么是视图
视图是一个虚拟表(非真实存在),其本质是【根据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='李平老师'
(二)创建视图
**语法: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负责,修改需付出巨大的沟通成本,极不方便。
(三)使用视图
# 修改原始表,原始视图也跟着改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 |+-----+-------+------------+
我们不应该修改视图中的记录,而且在涉及多个表的情况下是根本无法修改视图中的记录的,如下图

(四)修改视图
**语法: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 |+-----+-------+------------+
(五)删除视图
**语法:DROP VIEW 视图名称;**
DROP VIEW teacher_view;
二、触发器
(一)为何要使用触发器
使用触发器可以定制用户对表进行【增、删、改】操作前后的行为。!!注意:没有修改。
(二)创建触发器
# 创建触发器的语法:# 插入前CREATE TRIGGER tri_before_insert_tb1 BEFORE INSERT ON tb1 FOR EACH ROWBEGIN...END# 插入后CREATE TRIGGER tri_after_insert_tb1 AFTER INSERT ON tb1 FOR EACH ROWBEGIN...END# 删除前CREATE TRIGGER tri_before_delete_tb1 BEFORE DELETE ON tb1 FOR EACH ROWBEGIN...END# 删除后CREATE TRIGGER tri_after_delete_tb1 AFTER DELETE ON tb1 FOR EACH ROWBEGIN...END# 更新前CREATE TRIGGER tri_before_update_tb1 BEFORE UPDATE ON tb1 FOR EACH ROWBEGIN...END# 更新后CREATE TRIGGER tri_after_update_tb1 AFTER UPDATE ON tb1 FOR EACH ROWBEGIN...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)
(三)使用触发器
触发器无法由用户直接调用,只是由于对表的【增/删/改】操作被动引发。
(四)删除触发器
DROP TRIGGER tri_after_insert_cmd;
三、事务
(一)什么是事务
数据库事务是指作为单个逻辑单元执行的一系列操作(SQL语句)。这些操作要么全部执行,要么全部不执行。
(二)为什么需要事务
_经典的银行转账行为,A账户转给B账户10元,数据库操作需要两步,第一步A账户减10元,第二步B账户加10元,如果没有事务并且在两步中间发生异常,就会导致A的账户少了10元,但B的账户没有变化,如果不能保证这两步操作统一,银行的转账业务也没法进行展开了。_事务管理是每个数据库(Oracle,MySQL,db等)都必须实现的。
(三)事务特性(4种):
1.原子性(atomicty):强调事务的不可分割;2.一致性(consistency):事务的执行的前后数据的完整性保持一致;3.隔离性(isolation):一个事务执行的过程中,不应该受到其他事务的干扰;4.持久性(durability):事务一旦结束,数据就持久到数据库。
(四)事务运行模式(3种):
1.自动提交事务:默认的事务管理模式。如果一个语句成功地完成,则提交该语句;如果遇到错误,则回滚该语句。2.显式事务:以 BEGIN TRANSACTION 显式开始,以 COMMIT 或 ROLLBACK 显式结束。3.隐式事务:当连接以此模式进行操作时,sql将在提交或回滚当前事务后自动启动新事务。无须描述事务的开始,只需提交或回滚每个事务。它生成连续的事务链。
(五)使用案例
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)
2.在pymysql中实现事务处理
**pymysql执行的所有sql语句,都会默认放入一个事务中去。针对查询语句,事务并没有什么影响,更多的是针对修改语句。**
import pymysql # pip3 install pymysqlconnect = 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()
(六)总结
事务用于将某些操作的多个SQL语句作为原子性操作,一旦有某一个错误,即可回滚到原来的状态,从而保证数据的完整性。
四、存储过程
(一)什么是存储过程
存储过程包含了一系列可执行的sql语句,存储过程存放于MySQL中,通过调用它的名字可以执行其内部的一堆sql语句。相当于一个封装好的接口,里面有各种业务逻辑。
1.补充:程序于数据库结合使用的三种方式
# 方式一:MySQL:存储过程程序:调用存储过程# 方式二:MySQL:建库、表及关系程序:纯SQL语句(开发人员写)# 方式三:MySQL:建库、表及关系程序:类和对象,即ORM(本质还是纯SQL语句)(对象关系映射;类映射成表,对象映射成表的记录)
(二)存储过程的优缺点
使用存储过程的优点:
# 1) 基于替代程序写的SQL语句,实现程序于SQL解耦;# 2) 基于网络传输,传别名的数据量小,而直接传SQL数据量大。
使用存储过程的缺点:
# 1) 程序扩展功能不方便
(三)创建简单存储过程(无参)
# 创建无参存储过程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())
(四)创建存储过程(有参)
对于存储过程,可以接收参数,其参数有三类:(1) in ————>仅用于接收传入参数用;(2) out ————>仅用于返回值用;(3) inout ————>既可以传入又可以当作返回值。
# 创建有参存储过程delimiter $$create procedure p2(in n int, # 指定多个字段,必须加逗号隔开,最后一个不加逗号out res int)beginselect * 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 triggermysql> 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 pymysqlconnect = 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=0call 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()
(五)执行存储过程
1.在MySQL中执行存储过程
-- 无参数call proc_name()-- 有参数,全incall proc_name(1,2)-- 有参数,有in,out, inoutset @t1=0;set @t2=3;call proc_name(1,2,@t1,@t2)
2.在python中基于pymysql执行存储过程
#!/usr/bin/env python# -*- coding:utf-8 -*-import pymysqlconn = 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)
(六)删除存储过程
DROP procedure prco_name;
五、函数
(一)内置函数
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)
2.其他内置函数
http://doc.mysql.cn/mysql5/refman-5.1-zh.html-chapter/functions.html#encryption-functions
(二)自定义函数
1.注意事项:
(1)函数中不要写SQL语句(否则会报错),函数仅仅是一个功能,是一个在SQL中被应用的功能;
(2)若想在begin···end···中写SQL,需要使用存储过程。
delimiter //create function f1(i1 int,i2 int)returns intBEGINdeclare num int;set num = i1 + i2;return(num);END //delimiter ;
delimiter //create function f5(i int)returns intbegindeclare res int default 0;if i = 10 thenset res=100;elseif i = 20 thenset res=200;elseif i = 30 thenset res=300;elseset res=400;end if;return res;end //delimiter ;
(三)删除函数
DROP function func_name;
(四)执行函数
# 获取返回值select UPPER('egon') into @res;SELECT @res;# 在查询中使用select f1(11,nid),name from tb2;
六、流程控制
(一)if条件语句
delimiter //CREATE PROCEDURE proc_if ()BEGINdeclare i int default 0;if i = 1 THENSELECT 1;ELSEIF i = 2 THENSELECT 2;ELSESELECT 7;END IF;END //delimiter ;
(二)循环语句
1.while循环
delimiter //CREATE PROCEDURE proc_while ()BEGINDECLARE num INT ;SET num = 0 ;WHILE num < 10 DOSELECTnum ;SET num = num + 1 ;END WHILE ;END //delimiter ;
2.repeat循环
delimiter //CREATE PROCEDURE proc_repeat ()BEGINDECLARE i INT ;SET i = 0 ;repeatselect i;set i = i + 1;until i >= 5end repeat;END //delimiter ;
3.loop
BEGINdeclare i int default 0;loop_label: loopset i=i+1;if i<8 theniterate loop_label;end if;if i>=10 thenleave loop_label;end if;select i;end loop loop_label;END
