一、视图
(一)什么是视图
视图是一个虚拟表(非真实存在),其本质是【根据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 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)
(三)使用触发器
触发器无法由用户直接调用,只是由于对表的【增/删/改】操作被动引发。
(四)删除触发器
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 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()
(六)总结
事务用于将某些操作的多个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
)
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()
(五)执行存储过程
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)
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)
(六)删除存储过程
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 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 ;
(三)删除函数
DROP function func_name;
(四)执行函数
# 获取返回值
select UPPER('egon') into @res;
SELECT @res;
# 在查询中使用
select f1(11,nid),name from tb2;
六、流程控制
(一)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 ;
(二)循环语句
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.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 ;
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