MySQL之视图、触发器、事务、存储过程、函数
MySQL这个软件想将数据处理的全部事情,可以在mysql这个层面上所有都作了,也就是说它想要完成一件事,之后想开发的人,例如想写python程序的人,你就专门的写你本身的python程序,之后凡是关于数据的增删改查,所有都在MySQL里面完成,也就是说它想实现一个数据处理与应用程序的一个彻底的解耦和状态,好比说,若是我是个应用程序员,我想要查询数据,我不须要本身写sql语句,只须要调用mysql封装好的一些功能,直接调用这个功能就能够了,以前咱们使用sql来进行数据的增删改查,其实sql也能够算做一个开发语言,有专门招数据库开发的岗位,也就是说mysql想作这么一个事儿,之后啊,专门有人写应用程序的开发,专门有人来写sql,来开发sql部分,在数据库层面根据应用层的程序员的要求,把sql语句所有写好,各类复杂的需求所有帮你封装好,封装成一个一个的功能,应用程序开发程序员在根据本身的需求来使用这些功能,直接调用就能够了,这是mysql想要完成的事情,可是我们之后作开发,通常不会这么搞,通常招聘需求里面都会有一项是要会sql,浅显的说是由于花最少的钱,作最多的事儿,可是往深了说是由于公司里面通常不会用这些内置的功能去sql的工做,至于为何,我们学完mysql以后再说吧~~~java
一 视图
视图是一个虚拟表(非真实存在),是跑到内存中的表,真实表是硬盘上的表,怎么就获得了虚拟表,就是你查询的结果,只不过以前咱们查询出来的虚拟表,从内存中取出来显示在屏幕上,内存中就没有了这些表的数据,可是下次我要是想用这个虚拟表呢,没办法,只能从新查一次,每次都要从新查。其本质是【根据SQL语句获取动态的数据集,并为其命名】,用户使用时只需使用【名称】便可获取结果集,能够将该结果集当作表来使用。若是咱们想查询一些有关联的表,好比咱们前面的老师学生班级什么的表,我可能须要几个表联合查询的结果,可是这几张表在硬盘上是单独存的,因此咱们须要经过查询的手段,将这些表在内存中拼成一个虚拟表,而后是否是咱们再基于虚拟表在进行进一步的查询,而后咱们若是之后想从新再查一下这些关系数据,还须要对硬盘上这些表进行再次的从新加载到内容,联合成虚拟表,而后再筛选等等的操做,意味着我们每次都在写重复的sql语句,那有没有好的方法啊,其实很简单,咱们把重复用的这些sql逻辑封装起来,而后下次使用的时候直接调用这个封装好的操做就能够了,这个封装起来的操做就相似咱们下面要说的视图python
为何要用视图:使用视图咱们能够把查询过程当中的临时表摘出来,保存下来,用视图去实现,这样之后再想操做该临时表的数据时就无需重写复杂的sql了,直接去视图中查找便可,但视图有明显地效率问题,而且视图是存放在数据库中的,若是咱们程序中使用的sql过度依赖数据库中的视图,即强耦合,那就意味着扩展sql极为不便,所以并不推荐使用mysql
临时表应用举例:linux
#两张有关系的表 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语句 create view teacher_view as select tid from teacher where tname='李平老师';
mysql> show tables;
+---------------+
| Tables_in_crm |
+---------------+
| class |
| course |
| score |
| student |
| teacher |
| teacher_view | #看这里
+---------------+web
mysql> desc teacher_view; #有表结构
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| tid | int(11) | NO | | 0 | |
+-------+---------+------+-----+---------+-------+
1 row in set (0.04 sec)算法
mysql> select * from teacher_view; #有表数据
+-----+
| tid |
+-----+
| 2 |
+-----+
1 row in set (0.00 sec)sql
注意:可是在硬盘上你找到本身的mysql安装目录里面的data文件夹里面的对应的那个库的文件夹,这个文件夹里面存着我们的表信息,打开以后你会发现,这个视图表,只有表结构的teacher_view.frm文件,没有那个.idb存放数据的文件
其实他并无真实的数据,也没有必要再存一份数据,由于它的数据来源于其余两个表,因此他本质在后台对应的就是一个sql语句而已,因此记住了,视图只有表结构,没有表数据
视图的好处是之后咱们若是再须要查询或者使用上面的虚拟表,就能够直接使用这个视图了,sql的代码量也会省不少。可是弊端也很致命,看下面注意的内容。
#因而查询李平老师教授的课程名的sql能够改写为 mysql> select cname from course where teacher_id = (select tid from teacher_view); +--------+ | cname | +--------+ | 物理 | | 美术 | +--------+ 2 rows in set (0.00 sec) #!!!注意注意注意: #1. 使用视图之后就无需每次都重写子查询的sql,开发的时候是方便了不少,可是这么效率并不高,还不如咱们写子查询的效率高 #2. 并且有一个致命的问题:视图是存放到数据库里的,若是咱们程序中的sql过度依赖于数据库中存放的视图,那么意味着,一旦sql须要修改且涉及到视图的部分,则必须去数据库中进行修改,而后再到本身的应用程序里面将那个sql语句改一改,须要不少的修改工做,并而对视图的更改一般在通常中型及以上公司中数据库有专门的DBA负责,你要想完成修改,必须付出大量的沟通成本DBA可能才会帮你完成修改,极其地不方便
这么多的弊端,为何mysql还要提供这个东西呢,有一点是由于mysql想把全部数据处理的工做所有接手过来,但其实还有其余的缘由,等咱们讲完存储过程在和你们说吧。
#3 而且注意:视图通常都是用于查询,尽可能不要修改(插入、删除等)视图中的数据,虽然有时候能够修改为功,可是尽可能不要这样作,由于这个视图多是多个表联合起来生成的一个结果,若是你修改它,可能会形成不少表里面的数据都跟着被修改了
二 使用视图数据库
#修改视图,原始表也跟着改 mysql> select * from course; +-----+--------+------------+ | cid | cname | teacher_id | +-----+--------+------------+ | 1 | 生物 | 1 | | 2 | 物理 | 2 | | 3 | 体育 | 3 | | 4 | 美术 | 2 | +-----+--------+------------+ 4 rows in set (0.00 sec) mysql> create view course_view as select * from course; #建立表course的视图 Query OK, 0 rows affected (0.52 sec) mysql> select * from course_view; +-----+--------+------------+ | cid | cname | teacher_id | +-----+--------+------------+ | 1 | 生物 | 1 | | 2 | 物理 | 2 | | 3 | 体育 | 3 | | 4 | 美术 | 2 | +-----+--------+------------+ 4 rows in set (0.00 sec) mysql> update course_view set cname='xxx'; #更新视图中的数据 Query OK, 4 rows affected (0.04 sec) Rows matched: 4 Changed: 4 Warnings: 0 mysql> insert into course_view values(5,'yyy',2); #往视图中插入数据 Query OK, 1 row affected (0.03 sec) mysql> select * from course; #发现原始表的记录也跟着修改了 +-----+-------+------------+ | cid | cname | teacher_id | +-----+-------+------------+ | 1 | xxx | 1 | | 2 | xxx | 2 | | 3 | xxx | 3 | | 4 | xxx | 2 | | 5 | yyy | 2 | +-----+-------+------------+ 5 rows in set (0.00 sec)
咱们不该该修改视图中的记录,并且在涉及多个表的状况下是根本没法修改视图中的记录的,以下图
三 修改视图
语法:ALTER VIEW 视图名称 AS SQL语句,这基本就和删掉视图从新建立一个视图的过程是同样的,修改视图没什么好讲的,这里就简单提一下,就不讲啦~~,还不如咱们直接删掉,再从新建立呢 mysql> alter view teacher_view as select * from course where cid>3; Query OK, 0 rows affected (0.04 sec) mysql> select * from teacher_view; +-----+-------+------------+ | cid | cname | teacher_id | +-----+-------+------------+ | 4 | xxx | 2 | | 5 | yyy | 2 | +-----+-------+------------+ 2 rows in set (0.00 sec)
四 删除视图
语法:DROP VIEW 视图名称 DROP VIEW teacher_view
二 触发器
使用触发器能够定制用户对某一张表的数据进行【增、删、改】操做时先后的行为,注意:没有查询,在进行增删改操做的时候,触发的某个操做,称为触发器,也就是增删改的行为触发另外的一种行为,触发的行为无非就是sql语句的事情,及自动运行另一段sql语句。来看一下触发器怎么来建立:
一 建立触发器
# 插入前 CREATE TRIGGER tri_before_insert_tb1 BEFORE INSERT ON tb1 FOR EACH ROW BEGIN #begin和end里面写触发器要作的sql事情,注意里面的代码缩进,而且给触发器起名字的时候,名字的格式最好这样写,有表示意义,一看名字就知道要作什么,是给哪一个表设置的触发器 ... 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
插入后触发触发器:
#准备表 CREATE TABLE cmd ( #这是一张指令信息表,你在系统里面执行的任何的系统命令都在表里面写一条记录 id INT PRIMARY KEY auto_increment, #id USER CHAR (32), #用户 priv CHAR (10), #权限 cmd CHAR (64), #指令 sub_time datetime, #提交时间 success enum ('yes', 'no') #是否执行成功,0表明执行失败 ); CREATE TABLE errlog ( #指令执行错误的信息统计表,专门提取上面cmd表的错误记录 id INT PRIMARY KEY auto_increment, #id err_cmd CHAR (64), #错误指令 err_time datetime #错误命令的提交时间 ); #如今的需求是:无论正确或者错误的cmd,都须要往cmd表里面插入,而后,若是是错误的记录,还须要往errlog表里面插入一条记录
#若果没有触发器,咱们会怎么实现,咱们彻底能够经过我们的应用程序来作,根据cmd表里面的success这个字段是哪一个值(yes成功,no表示失败),在给cmd插入记录的时候,判断一下这个值是yes或者no,来判断一下成功或者失败,若是失败了,直接给errlog来插入一条记录
#可是mysql说,你的应用程序能够省事儿了,你只须要往cmd表里面插入数据就好了,不必你本身来判断了,可使用触发器来实现,能够判断你插入的这条记录的success这个字段对应的值,而后自动来触发触发器,进行errlog表的数据插入
#建立触发器 delimiter // (或者写$$,其余符号也行,可是不要写mysql不能认识的,知道一下就好了),delimiter 是告诉mysql,遇到这句话的时候,就将sql语句的结束符分号改为delimiter后面的// CREATE TRIGGER tri_after_insert_cmd AFTER INSERT ON cmd FOR EACH ROW #在你cmd表插入一条记录以后触发的。 BEGIN #每次给cmd插入一条记录的时候,都会被mysql封装成一个对象,叫作NEW,里面的字段都是这个NEW的属性 IF NEW.success = 'no' THEN #mysql里面是能够写这种判断的,等值判断只有一个等号,而后写then INSERT INTO errlog(err_cmd, err_time) VALUES(NEW.cmd, NEW.sub_time) ; #必须加分号,而且注意,咱们必须用delimiter来包裹,否则,mysql一看到分号,就认为你的sql结束了,因此会报错 END IF ; #而后写end if,必须加分号 END// #只有遇到//这个完成的sql才算结束 delimiter ; #而后将mysql的结束符改回为分号 #往表cmd中插入记录,触发触发器,根据IF的条件决定是否插入错误日志 INSERT INTO cmd ( USER, priv, cmd, sub_time, success ) VALUES ('chao','0755','ls -l /etc',NOW(),'yes'), ('chao','0755','cat /etc/passwd',NOW(),'no'), ('chao','0755','useradd xxx',NOW(),'no'), ('chao','0755','ps aux',NOW(),'yes'); #查询错误日志,发现有两条 mysql> select * from errlog; +----+-----------------+---------------------+ | id | err_cmd | err_time | +----+-----------------+---------------------+ | 1 | cat /etc/passwd | 2017-09-14 22:18:48 | | 2 | useradd xxx | 2017-09-14 22:18:48 | +----+-----------------+---------------------+ 2 rows in set (0.00 sec)
特别的:NEW表示即将插入的数据行,OLD表示即将删除的数据行。
二 使用触发器
触发器没法由用户直接调用,而由对表的【增/删/改】操做被动引起的。
三 删除触发器
drop trigger tri_after_insert_cmd;
三 事务
事务用于将某些操做的多个SQL做为原子性操做,也就是这些sql语句要么同时成功,要么都不成功,事务的其余特性在我第一篇博客关于事务的介绍里面有,这里就很少作介绍啦,一旦有某一个出现错误,便可回滚到原来的状态,从而保证数据库数据完整性。
简单来讲:我给一个姑娘转帐,姑娘那儿收到了200,你的帐户上扣了200,这两个操做是否是两个sql语句,这两个sql语句是你的应用程序发给mysql服务端的,而且这两个sql语句都要一块儿执行,否则数据就错了,你想一想是否是。而且若是你经过应用程序发送这两条sql的时候,因为网络问题,你只发送了一个sql过来,那只有一个帐户改了数据,另一个没改,那数据是否是就出错了啊。这就是事务要完成的事情。
create table user( id int primary key auto_increment, name char(32), balance int ); insert into user(name,balance) values ('wsb',1000), ('chao',1000), ('ysb',1000); #原子操做 start transaction; update user set balance=900 where name='wsb'; #买支付100元 update user set balance=1010 where name='chao'; #中介拿走10元 update user set balance=1090 where name='ysb'; #卖家拿到90元 commit; #只要不进行commit操做,就没有保存下来,没有刷到硬盘上 #出现异常,回滚到初始状态 start transaction; update user set balance=900 where name='wsb'; #买支付100元 update user set balance=1010 where name='chao'; #中介拿走10元 uppdate user set balance=1090 where name='ysb'; #卖家拿到90元,出现异常没有拿到 rollback; #若是上面三个sql语句出现了异常,就直接rollback,数据就直接回到原来的状态了。可是执行了commit以后,rollback这个操做就无法回滚了
#咱们要作的是检测这几个sql语句是否异常,没有异常直接commit,有异常就rollback,可是如今单纯的只是开启了事务,可是尚未说如何检测异常,咱们先来一个存储过程来捕获异常,等咱们学了存储过程,再细说存储过程。 commit;
#经过存储过程来捕获异常:(shit!,写存储过程的是,注意每一行都不要缩进!!!按照下面的缩进来写,竟然让我翻车了!!!我记住你了~~~),个人代码直接黏贴就能用。
delimiter //
create PROCEDURE p5()
BEGIN
DECLARE exit handler for sqlexception
BEGIN
rollback;
END;
START TRANSACTION;
update user set balance=900 where name='wsb'; #买支付100元
update user set balance=1010 where name='chao'; #中介拿走10元
#update user2 set balance=1090 where name='ysb'; #卖家拿到90元
update user set balance=1090 where name='ysb'; #卖家拿到90元
COMMIT;
END //
delimiter ;
mysql> select * from user; +----+------+---------+ | id | name | balance | +----+------+---------+ | 1 | wsb | 1000 | | 2 | chao | 1000 | | 3 | ysb | 1000 | +----+------+---------+ 3 rows in set (0.00 sec)
四 存储过程
一 介绍
存储过程包含了一系列可执行的sql语句,存储过程存放于MySQL中,经过调用它的名字能够执行其内部的一堆sql。到目前为止,咱们上面学的视图、触发器、事务等为咱们简化了应用程序级别写sql语句的复杂程度,让咱们在应用程序里面写sql更简单方便了,可是咱们在应用程序上仍是须要本身写sql的,而咱们下面要学的存储过程,它是想让咱们的应用程序不须要再写sql语句了,全部的sql语句,所有放到mysql里面,被mysql封装成存储过程,说白了它就是一个功能,这个功能对应着一大堆的sql语句,这些语句里面能够包括咱们前面学的视图啊、触发器啊、事务啊、等等的内容,也就是说存储过程实际上是什么?是一堆sql的集合体,能够直接用mysql里面提供的一堆功能,有了存储过程之后,它的好处是我项目逻辑中须要的各类查询均可以让DBA或者你本身封装到存储过程里面,之后使用的时候直接调用存储过程名就能够了,在开发应用的时候就简单了,就不要应用程序员进行sql语句的开发了,可是你想若是你真的这么作了,确实颇有好处,简单不少,应用程序的开发和数据库sql语句的开发,彻底的解耦了,这样,专门的人作专门的事情,专门招一个应用开发的人开发应用程序,招一个开发型DBA,会sql的开发,他把sql写完以后,封装成一个个的存储过程,给应用程序员用就好了,对不对,这个DBA就不单纯的是管理数据库系统了,还须要会写sql语句,那这样你的应用程序开发的效率就高了,运行效率也提升了,你开发应用程序的时候若是写了一堆的sql语句,这些语句是否是要经过网络传输,传输到mysql服务端来执行,而后将结果返回给你的应用程序,那么在传输的时候,你说好多的sql语句和简单的一个存储过程的名字,哪一个传输的速度快,哪一个发送给服务端的速度快,固然是单纯的一个存储过程的名字更快。
因此摆在你面前有两种开发模式:
第一种是招一个会开发应用程序的而且这我的还要会sql开发,这样的人既写应用程序,还写sql语句,这种状况你能够招两我的,一个是前面说的,还有一个是数据库管理人员,单纯只会管理数据库的而不会sql开发的人,这样好招人,工资也不高。(应用程序员-->只须要开发应用程序的逻辑 。 sql开发人员-->编写存储过程)
第二种:招一个应用程序开发的,只须要会应用程序级别的开发,再招一个会sql开发的DBA。(应用程序员-->开发应用+写原生sql 。 数据库人员负责维护数据库的正常运行)
咱们比较一下这两种的开发模式:第二种:解耦和,开发效率高,运行效率也高,因此之后最好采用第一种开发模式,哈哈,是否是神反转,缘由是什么呢,钱只是一个方面,主要仍是由于之后若是你想扩展,那就很不方便了,为何呢,由于一般sql开发人员,不如你的应用程序员更懂你的业务逻辑,一旦你要扩展一个功能,还须要跨部门沟通,致使这种工做方式受限的不仅是技术层面了,这种方式在技术层面确定是效率高的,可是要考虑人为因素,还有成本方面的考虑,因此一般我们之后作开发,不要想着会有人给你写sql,须要你本身写的很熟练。这样,你一个部门就能搞定这两件事情。
第二种方式其实也比较麻烦,你开发程序员本身须要写sql,而且写出来的sql还存在效率问题,那么有没有一种方式能够不让开发程序员本身再写sql了,搞一个封装程度更高的东西让你来调用,有没有这种方式呢?有,就是第三种方式
第三种:应用程序除了开发应用程序的逻辑,不须要编写原生sql,只须要使用别人写好的框架,基于框架来处理数据,框架提供的功能是ORM:对象关系映射,和对象有关系,就是在应用程序里面,只须要定义一堆的类,每一个类对应数据库里面的一张表,这个类一实例化,也就是一个类对象对应表里面的一条记录,获得对象之后,这个对象除了有数据以外,还有处理该条信息的方法,增删改查都有了,全都封装成了对象的一个一个的方法了,意味着你之后再想进行查询,就不必写原生sql了,直接基于面向对象的思想来处理类与对象就好了,可是这种方式本质上仍是使用了原生sql,只不过对于应用程序员来讲,你不用直接写sql了,别人写好的ORM框架就帮你处理这件事儿了,帮你把你调用的那些接口方法和你传入的参数等等帮你转换为了原生sql,而后再往mysql里面提交。因此这种方式和第二种方式有些相似,可是比第二种方式要好(前提是第二种方式应用程序员的sql水平比较low的状况下,通常会比较low):
这种方式的优势:应用程序员不须要再写原生的sql了,这意味着开发效率比第二种要高,同时还兼顾了第二种方式扩展性高的好处,由于本质上仍是原生sql
缺点是:执行效率还不如第二种方式高,由于你如今再想运行须要作什么事 情,首先你想,你程序里面用的是别人写好的ORM框架或者模块,你的sql要想执行,你须要作什么事儿,你的ORM框架须要把类或者类对象先翻译成原生的sql,再沿着网络发到mysql服务端,中间对了一个转换的过程,因此执行效率其实连方式二都比不上。
总结:其实单单从技术层面上看,第一种方式确定是最好的,开发和执行效率是最高的,扩展性单纯技术层面来看也比较高,因此单单从技术层面来考虑,这种方式确定是优选的。可是就目前的现状而言,多数仍是须要你应用程序员既作应用逻辑的开发,还要会原生sql的开发,因此应该尽量的不让mysql来作了,全部关于数据的增删改查都交给应用程序级别来作,在应用程序中写原生的sql,这也是第二种方式和第三种方式的一个共性,全部事情基本都交给应用程序级别来作,mysql级别基本不作sql的开发,这样扩展性也好一些,由于全部的事情都交给你应用程序的开发部门来作了,本身部门内部进行扩展仍是扩展性不错的,因此我们通常从后面两种方式来选择,那么后面两种选哪种呢?第二种执行效率比第三种高,由于比第三种少了一步类对象转换为sql的过程。第三种开发效率高,不要应用程序员再写原生的sql了。因此具体选哪种,看大家本身公司的状况,须要快速开发,就找第三种,若是本身程序员的sql写的很溜(又快又优),那么找第二种,通常大公司采用的第一种多一些,部门分工很是的明确,等大家你们学到Django框架,大家就会接触ORM啦~~~学完MySQL以后,咱们在学框架以前,带你们我们本身开发一个简单的ORM框架,可以在你本身的应用程序中使用的ORM,写这个ORM框架很重要,对你之后的框架和项目的学习颇有指导意义,说哆啦,咱们继续说存储过程:
使用存储过程的优势:
#1. 用于替代程序写的SQL语句,实现程序与sql解耦 #2. 基于网络传输,传别名的数据量小,而直接传sql数据量大
使用存储过程的缺点:
#1. 程序员扩展功能不方便
上面一大堆话的总结:程序与数据库结合使用的三种方式
#方式一: MySQL:存储过程 程序:调用存储过程 #方式二: MySQL: 程序:纯SQL语句 #方式三: MySQL: 程序:类和对象,即ORM(本质仍是纯SQL语句)
二 建立简单存储过程(无参)
delimiter // create procedure p1() BEGIN select * from blog; INSERT into blog(name,sub_time) values("xxx",now()); END // delimiter ; #在mysql中调用 call p1(); #相似于MySQL的函数,但不是函数昂,别搞混了,MySQL的函数(count()\max()\min()等等)都是放在sql语句里面用的,不能单独的使用,存储过程是能够直接调用的 call 名字+括号; #MySQL的视图啊触发器啊if判断啊等等都能在存储过程里面写,这是一大堆的sql的集合体,均可以综合到这里面 #在python中基于pymysql调用 cursor.callproc('p1') print(cursor.fetchall())
另外:存储过程是能够传参数的,看下面的内容
三 建立存储过程(有参)
对于存储过程,能够接收参数,其参数有三类: #in 仅用于传入参数用 #out 仅用于返回值用 #inout 既能够传入又能够看成返回值
in:传入参数:
delimiter // create procedure p2( in n1 int, #n1参数是须要传入的,也就是接收外部数据的,而且这个数据必须是int类型 in n2 int ) BEGIN select * from blog where id > n1; #直接应用变量 END // delimiter ;
#调用存储过程的两种方式:或者说是两个地方吧 #在mysql中调用 call p2(3,2) #在python中基于pymysql调用 cursor.callproc('p2',(3,2)) print(cursor.fetchall())
#经过存储过程的传参来看,也能体现出咱们学习的Python的灵活性,传参不须要指定类型,也不须要声明这个参数是传入的仍是返回出来的,参数既能够传入,这个参数也能够直接经过return返回。
out:返回值:
#查看存储过程的一些信息:show create procedure p3; #查看视图啊、触发器啊都这么看,还能够用\G,show create procedure p3\G;\G的意思是你直接查看表结构可能横向上显示不完,\G是让表给你竖向显示,一row是一行的字段
delimiter // create procedure p3( in n1 int, out res int ) BEGIN select * from blog where id > n1; set res = 1; #我在这里设置一个res=1,若是上面的全部sql语句所有正常执行了,那么这一句确定也就执行了,那么此时res=1,若是我最开始传入的时候,给res的值设置的是0,
#那么你想,最后我接收到的返回值若是是0,那么说明你中间确定有一些sql语句执行失败了
#注意写法:out的那个参数,能够用set来设置,set设置以后表示这个res能够做为返回值,而且不须要像python同样写一个return,你直接set以后的值,就是这个存储过程的返回值 END // delimiter ; #在mysql中调用 set @res=0; #这是MySQL中定义变量名的固定写法(set @变量名=值),能够本身规定好,0表明假(执行失败),1表明真(执行成功),若是这个被改成1了,说明存储过程当中的sql语句执行成功了 call p3(3,@res);#注意:不要这样写:call p3(3,1),这样out的参数值你写死了,无法肯定后面这个1是否是成功了,也就是说随后这个out的值可能改为0了,也就是失败了,可是这样你就判断不了了,你后面查看的这个res就成1了,因此这个参数应该是一个变量名昂,定义变量名就是上一句,若是你直接传一个常量数字,会报错的,写法不对。 select @res; #看一下这个结果,就知道这些sql语句是否是执行成功了,你们明白了吗~~~ #在python中基于pymysql调用,在python中只须要知道存储过程的名字就好了 cursor.callproc('p3',(3,0)) #0至关于set @res=0,为何这里这个out参数能够写常数0啊,由于你用的pymysql,人家会帮你搞定,pymysql其实会帮你写成这样:第一个参数变量名:@_p3_0=3,第二个:@_p3_1=0,也就是pymysql会自动帮你对应上一个变量名,pymysql只是想让你写的时候更方便
#沿着网络将存储过程名和参数发给了mysql服务端,比我们发一堆的sql语句确定要快对了,mysql帮你调用存储过程 print(cursor.fetchall()) #查询select的查询结果 cursor.execute('select @_p3_0,@_p3_1;') #@_p3_0表明第一个参数,@_p3_1表明第二个参数,即返回值 print(cursor.fetchall())
#别忘了关掉:
cursor.close()
conn.close()
#注意昂:存储过程在哪一个库里面建的,就只能在哪一个库里面用
inout:既可传入又能够返回值:
delimiter // create procedure p4( inout n1 int ) BEGIN select * from blog where id > n1; set n1 = 1; END // delimiter ;
#在mysql中调用 set @x=3; call p4(@x); select @x; #在python中基于pymysql调用 cursor.callproc('p4',(3,)) print(cursor.fetchall()) #查询select的查询结果 cursor.execute('select @_p4_0;') print(cursor.fetchall())
存储过程结合事务来写:
delimiter // create procedure p4( out status int ) BEGIN 1. 声明若是出现异常则执行{ set status = 1; rollback; } 开始事务 -- 由秦兵帐户减去100 -- 方少伟帐户加90 -- 张根帐户加10 commit; 结束 set status = 2; END // delimiter ; #实现 delimiter // create PROCEDURE p5( OUT p_return_code tinyint ) BEGIN DECLARE exit handler for sqlexception #声明若是一旦出现异常则执行下面的这个begin和end里面的操做 BEGIN -- ERROR #--是什么啊,忘了吧,是注释的意思,就告诉你后面是对错误的处理 set p_return_code = 1; #将out返回值改成1了,这是你本身规定的,1表示出错了 rollback; #回滚事务 END; DECLARE exit handler for sqlwarning #声明了出现警告信息以后你的操做行为 BEGIN -- WARNING set p_return_code = 2; rollback; END; START TRANSACTION; #其实我们这个存储过程里面就是执行这个事务,而且一直检测着这个事务,一旦出错或者出现警告,就rollback DELETE from tb1; #事务里面的任何一条sql执行失败或者执行出现警告,都会执行上面咱们声明的那些对应的操做,若是没有任何的异常,就会自动执行下面的commit,并执行后面成功的sql insert into blog(name,sub_time) values('yyy',now()); #拿个人代码进行测试的时候,别忘了改为你本身库里的表,还有表里面对应的字段名要有的,本身测试的时候,能够本身写一个错误的sql来试试看 COMMIT; -- SUCCESS set p_return_code = 0; #0表明执行成功 END // delimiter ; #在mysql中调用存储过程 set @res=123; call p5(@res); select @res; #在python中基于pymysql调用存储过程 cursor.callproc('p5',(123,)) #注意后面这个参数是个元祖,别忘了逗号,按照咱们上面规定的,上面有三个值0,1,2:0成功、1失败、2警告也是失败。因此咱们传给这个out参数的值只要不是这三个值就好了,这里给的是100 print(cursor.fetchall()) #查询select的查询结果 cursor.execute('select @_p5_0;') print(cursor.fetchall())
#执行成功之后,查看一下结果就能看到执行后的值了
四 执行存储过程
在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) 执行存储过程
在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 proc_name;
五 函数
MySQL中提供了许多内置函数,可是注意,这些函数只能在sql语句中使用,不能单独调用昂,例如:其实下面的有些函数咱们都已经用过了,其余的若是大家用到了,我们再过来查吧,好不?
1、数学函数 ROUND(x,y) 返回参数x的四舍五入的有y位小数的值 RAND() 返回0到1内的随机值,能够经过提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。 2、聚合函数(经常使用于GROUP BY从句的SELECT查询中) AVG(col)返回指定列的平均值 COUNT(col)返回指定列中非NULL值的个数 MIN(col)返回指定列的最小值 MAX(col)返回指定列的最大值 SUM(col)返回指定列的全部值之和 GROUP_CONCAT(col) 返回由属于一组的列值链接组合而成的结果 3、字符串函数 CHAR_LENGTH(str) 返回值为字符串str 的长度,长度的单位为字符。一个多字节字符算做一个单字符。 CONCAT(str1,str2,...) 字符串拼接 若有任何一个参数为NULL ,则返回值为 NULL。 CONCAT_WS(separator,str1,str2,...) 字符串拼接(自定义链接符) CONCAT_WS()不会忽略任何空字符串。 (然而会忽略全部的 NULL)。 CONV(N,from_base,to_base) 进制转换 例如: SELECT CONV('a',16,2); 表示将 a 由16进制转换为2进制字符串表示 FORMAT(X,D) 将数字X 的格式写为'#,###,###.##',以四舍五入的方式保留小数点后 D 位, 并将结果以字符串的形式返回。若 D 为 0, 则返回结果不带有小数点,或不含小数部分。 例如: SELECT FORMAT(12332.1,4); 结果为: '12,332.1000' INSERT(str,pos,len,newstr) 在str的指定位置插入字符串 pos:要替换位置其实位置 len:替换的长度 newstr:新字符串 特别的: 若是pos超过原字符串长度,则返回原字符串 若是len超过原字符串长度,则由新字符串彻底替换 INSTR(str,substr) 返回字符串 str 中子字符串的第一个出现位置。 LEFT(str,len) 返回字符串str 从开始的len位置的子序列字符。 LOWER(str) 变小写 UPPER(str) 变大写 REVERSE(str) 返回字符串 str ,顺序和字符顺序相反。 SUBSTRING(str,pos) , SUBSTRING(str FROM pos) SUBSTRING(str,pos,len) , SUBSTRING(str FROM pos FOR len) 不带有len 参数的格式从字符串str返回一个子字符串,起始于位置 pos。带有len参数的格式从字符串str返回一个长度同len字符相同的子字符串,起始于位置 pos。 使用 FROM的格式为标准 SQL 语法。也可能对pos使用一个负值。倘若这样,则子字符串的位置起始于字符串结尾的pos 字符,而不是字符串的开头位置。在如下格式的函数中能够对pos 使用一个负值。 mysql> SELECT SUBSTRING('Quadratically',5); -> 'ratically' mysql> SELECT SUBSTRING('foobarbar' FROM 4); -> 'barbar' mysql> SELECT SUBSTRING('Quadratically',5,6); -> 'ratica' mysql> SELECT SUBSTRING('Sakila', -3); -> 'ila' mysql> SELECT SUBSTRING('Sakila', -5, 3); -> 'aki' mysql> SELECT SUBSTRING('Sakila' FROM -4 FOR 2); -> 'ki' 4、日期和时间函数 CURDATE()或CURRENT_DATE() 返回当前的日期 CURTIME()或CURRENT_TIME() 返回当前的时间 DAYOFWEEK(date) 返回date所表明的一星期中的第几天(1~7) DAYOFMONTH(date) 返回date是一个月的第几天(1~31) DAYOFYEAR(date) 返回date是一年的第几天(1~366) DAYNAME(date) 返回date的星期名,如:SELECT DAYNAME(CURRENT_DATE); FROM_UNIXTIME(ts,fmt) 根据指定的fmt格式,格式化UNIX时间戳ts HOUR(time) 返回time的小时值(0~23) MINUTE(time) 返回time的分钟值(0~59) MONTH(date) 返回date的月份值(1~12) MONTHNAME(date) 返回date的月份名,如:SELECT MONTHNAME(CURRENT_DATE); NOW() 返回当前的日期和时间 QUARTER(date) 返回date在一年中的季度(1~4),如SELECT QUARTER(CURRENT_DATE); WEEK(date) 返回日期date为一年中第几周(0~53) YEAR(date) 返回日期date的年份(1000~9999) 重点: DATE_FORMAT(date,format) 根据format字符串格式化date值 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' 5、加密函数 MD5() 计算字符串str的MD5校验和 PASSWORD(str) 返回字符串str的加密版本,这个加密过程是不可逆转的,和UNIX密码加密过程使用不一样的算法。 6、控制流函数 CASE WHEN[test1] THEN [result1]...ELSE [default] END 若是testN是真,则返回resultN,不然返回default CASE [test] WHEN[val1] THEN [result]...ELSE [default]END 若是test和valN相等,则返回resultN,不然返回default IF(test,t,f) 若是test是真,返回t;不然返回f IFNULL(arg1,arg2) 若是arg1不是空,返回arg1,不然返回arg2 NULLIF(arg1,arg2) 若是arg1=arg2返回NULL;不然返回arg1 7、控制流函数小练习 #7.一、准备表,将下面这些内容保存为一个.txt文件或者.sql,而后经过navicat的运行sql文件的功能导入到数据库中,还记得吗? /* Navicat MySQL Data Transfer Source Server : localhost_3306 Source Server Version : 50720 Source Host : localhost:3306 Source Database : student Target Server Type : MYSQL Target Server Version : 50720 File Encoding : 65001 Date: 2018-01-02 12:05:30 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for course -- ---------------------------- DROP TABLE IF EXISTS `course`; CREATE TABLE `course` ( `c_id` int(11) NOT NULL, `c_name` varchar(255) DEFAULT NULL, `t_id` int(11) DEFAULT NULL, PRIMARY KEY (`c_id`), KEY `t_id` (`t_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of course -- ---------------------------- INSERT INTO `course` VALUES ('1', 'python', '1'); INSERT INTO `course` VALUES ('2', 'java', '2'); INSERT INTO `course` VALUES ('3', 'linux', '3'); INSERT INTO `course` VALUES ('4', 'web', '2'); -- ---------------------------- -- Table structure for score -- ---------------------------- DROP TABLE IF EXISTS `score`; CREATE TABLE `score` ( `id` int(11) NOT NULL AUTO_INCREMENT, `s_id` int(10) DEFAULT NULL, `c_id` int(11) DEFAULT NULL, `num` double DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of score -- ---------------------------- INSERT INTO `score` VALUES ('1', '1', '1', '79'); INSERT INTO `score` VALUES ('2', '1', '2', '78'); INSERT INTO `score` VALUES ('3', '1', '3', '35'); INSERT INTO `score` VALUES ('4', '2', '2', '32'); INSERT INTO `score` VALUES ('5', '3', '1', '66'); INSERT INTO `score` VALUES ('6', '4', '2', '77'); INSERT INTO `score` VALUES ('7', '4', '1', '68'); INSERT INTO `score` VALUES ('8', '5', '1', '66'); INSERT INTO `score` VALUES ('9', '2', '1', '69'); INSERT INTO `score` VALUES ('10', '4', '4', '75'); INSERT INTO `score` VALUES ('11', '5', '4', '66.7'); -- ---------------------------- -- Table structure for student -- ---------------------------- DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `s_id` varchar(20) NOT NULL, `s_name` varchar(255) DEFAULT NULL, `s_age` int(10) DEFAULT NULL, `s_sex` char(1) DEFAULT NULL, PRIMARY KEY (`s_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of student -- ---------------------------- INSERT INTO `student` VALUES ('1', '鲁班', '12', '男'); INSERT INTO `student` VALUES ('2', '貂蝉', '20', '女'); INSERT INTO `student` VALUES ('3', '刘备', '35', '男'); INSERT INTO `student` VALUES ('4', '关羽', '34', '男'); INSERT INTO `student` VALUES ('5', '张飞', '33', '女'); -- ---------------------------- -- Table structure for teacher -- ---------------------------- DROP TABLE IF EXISTS `teacher`; CREATE TABLE `teacher` ( `t_id` int(10) NOT NULL, `t_name` varchar(50) DEFAULT NULL, PRIMARY KEY (`t_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of teacher -- ---------------------------- INSERT INTO `teacher` VALUES ('1', '大王'); INSERT INTO `teacher` VALUES ('2', 'alex'); INSERT INTO `teacher` VALUES ('3', 'chao'); INSERT INTO `teacher` VALUES ('4', 'peiqi'); #7.二、统计各科各分数段人数.显示格式:课程ID,课程名称,[100-85],[85-70],[70-60],[ <60] select score.c_id, course.c_name, sum(CASE WHEN num BETWEEN 85 and 100 THEN 1 ELSE 0 END) as '[100-85]', sum(CASE WHEN num BETWEEN 70 and 85 THEN 1 ELSE 0 END) as '[85-70]', sum(CASE WHEN num BETWEEN 60 and 70 THEN 1 ELSE 0 END) as '[70-60]', sum(CASE WHEN num < 60 THEN 1 ELSE 0 END) as '[ <60]' from score,course where score.c_id=course.c_id GROUP BY score.c_id;
须要掌握的函数: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 准备表和记录 CREATE TABLE blog ( id INT PRIMARY KEY auto_increment, NAME CHAR (32), sub_time datetime ); 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'); #3. 提取sub_time字段的值,按照格式后的结果即"年月"来分组,统计一下每一年每个月的博客数量,怎么写呢,按照sub_time分组,可是咱们的sub_time是年月日加时间,我想看每一年每个月,直接按照sub_time来分组是不行的,每篇博客的发表时间基本都是不一样的,因此咱们须要经过这个date_format来搞了 SELECT DATE_FORMAT(sub_time,'%Y-%m'),COUNT(1) FROM blog GROUP BY DATE_FORMAT(sub_time,'%Y-%m'); #结果 +-------------------------------+----------+ | DATE_FORMAT(sub_time,'%Y-%m') | COUNT(1) | +-------------------------------+----------+ | 2015-03 | 2 | | 2016-07 | 4 | | 2017-03 | 3 | +-------------------------------+----------+ 3 rows in set (0.00 sec)
一 自定义函数(本身简单看看吧)
#!!!注意!!! #函数中不要写sql语句(不然会报错),函数仅仅只是一个功能,是一个在sql中被应用的功能 #若要想在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('chao') into @res; SELECT @res; # 在查询中使用 select f1(11,nid) ,name from tb2;
关于查看存储过程,函数,视图,触发器的语法:
查询数据库中的存储过程和函数 select name from mysql.proc where db = 'xx' and type = 'PROCEDURE' //查看xx库里面的存储过程 select name from mysql.proc where db = 'xx' and type = 'FUNCTION' //函数 show procedure status; //存储过程 show function status; //函数 查看存储过程或函数的建立代码 show create procedure proc_name; show create function func_name; 查看视图 SELECT * from information_schema.VIEWS //视图 SELECT * from information_schema.TABLES //表 查看触发器 SHOW TRIGGERS [FROM db_name] [LIKE expr] SELECT * FROM triggers T WHERE trigger_name=”mytrigger” \G;其中triggers T就是triggers as T的意思,起别名
六 流程控制
一 条件语句
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 ;
二 循环语句
while循环:#后面讲索引的时候,我们会用到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 ;
到这里你会发现,其实sql也是一个开发语言,基本数据类型啊函数啊流程控制啊(if、while等)它都有。下面这两个咱们简单看一下用法就行啦~~~
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 ;
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