视图
视图的概念在之前已经接触过,其实就是查询中所使用的临时表,如果一个临时表经常被使用,那可以使用生成这个临时表的语句来定义一个视图,以后只需要将这个视图当做一个表来用就可以了.
视图实际上就是一个SQL查询,其数据基于原始表格,如果原始表格的数据有所变动,查询结果也用到变动后数据的话,则视图的数据也会变动,但要知道,视图并不存储任何数据,最好也不要向视图内增加删除数据.视图还一个作用就是保护原始数据,比如可以只给用户视图的查询权限,而不是原始数据的权限,这样既让用户可以有效的查询数据,又保护了原始数据的安全.
视图的使用 | |
CREATE VIEW viewname AS (SQL query) | 创建视图名称,这个视图的内容,就是AS后边SQL query查询的结果.在创建以后,viewname可以当成一个表名来使用.创建视图之后,在show tables之中可以看到视图名 |
SHOW CREATE VIEW viewname | 查看一个视图是如何建立的,会返回一个表,其中的Create View列显示了创建视图的语句 |
DROP VIEW viewname | 删除视图 |
CREATE OR REPLACE VIEW viewname AS | 更新视图,当然也可以删除视图以后再建立 |
视图可以重复使用SQL语句,视图还有几个特点:名字不能与其他表和视图重复,视图可以嵌套,即里边的SELECT可以来自其他视图,视图的ORDER BY会被其中SELECT语句的ORDER BY覆盖,视图不能进行索引,也没有触发器和默认值.一般视图仅用来查询,而不去做查询之外的事情.除此之外,可以将视图当成普通的表使用
触发器
在对某个表进行增删改的时候,在这个动作之前或者之后,可以设置触发器,让触发器执行指定的动作.触发器响应的语句只有INSERT UPDATE DELETE三种,外加在之前还是之后,所以一共有6种触发器语句,查询语句没有触发器.
触发器的基本语法是
delimiter // CREATE TRIGGER trigger_name BEFORE INSERT ON table_name FOR EACH ROW BEGIN ... END// delimiter ;这里有个小技巧,就是BEGIN 和END 中间有多个语句的时候,如果加上分号,则执行会结束,可以先把结束符通过delimiter 指令修改一下,然后再修改回来,即可添加一个触发器.
触发器如果针对INSERT和UPDATE语句,在BEGIN和END之间可以引用new,是表示插入或者修改的内容,在DELETE语句中可以引用old,表示是删除的数据.这样可以实现在数据获取之间就修改里边的内容,控制插入数据库的内容.删除的时候也能够获得数据,进行操作
函数
之前已经接触了内置函数,用来对列数据进行处理,这次主要讲自定义函数
# 创建自定义函数 delimiter \\ CREATE FUNCTION f1( i1 INT, i2 INT) RETURNS INT BEGIN DECLARE num INT; SET num = i1 + i2; RETURN(num); END \\ delimiter ;
可见MySQL里的函数类似强类型语言的定义,需要定义函数名,传输参数的数据类型,和返回的数据类型,在函数体内,使用变量需要先声明,然后用SET 进行赋值.
函数体内不能够写SQL语句,只能够写逻辑语句.函数的删除用DROP FUNCTION语句,函数调用则和内置函数一样,需要传入对应类型的列参数
在MySQL 中,定义个变量,一般使用
存储过程
存储过程,就是一系列SQL语句的集合,分为带参数和不带参数
先来看一个简单的存储过程:
delimiter // CREATE PROCEDURE p1() BEGIN select * from user; END// delimiter ;可见创立存储过程使用的关键字是PROCEDURE,然后和函数非常类似,只不过内部是SQL语句,而且没有定义返回值.存储过程实际上返回结果集.建立之后,在python内部,通过cursor.callproc('p1')调用,在mysql内,通过 call p1调用.
import pymysql conn = pymysql.connect( host='localhost', user='root', password='', database='homework', charset='utf8') cursor = conn.cursor() cursor.callproc('p1') conn.commit() res = cursor.fetchall() print(res) cursor.close() conn.close()
存储过程还可以带参数,参数有三种类型,in,out和inout,其中in表示传入,这个值即使内部修改了,也不影响外部,out表示这个变量只能返回值,即使传入了,内部也拿不到这个值,但是执行之后能拿到修改后的值,inout表示既可以传入又可以当成返回值.这个值有可能在调用存储过程之后改变.
带参数的存储过程
只带in的参数比较简单,可以传入常量,对于out和inout参数,必须要传入一个MySQL变量(用 set @name 定义变量),其实就是通过存储过程对变量进行了操作:
delimiter \\ create procedure p1( in i1 int, in i2 int, inout i3 int, out r1 int ) BEGIN DECLARE temp1 int; DECLARE temp2 int default 0; set temp1 = 1; set i3 = i3 + 100; set r1 = i1 + i2 + temp1 + temp2; select * from user; end\\ delimiter ;之后在MySQL里这么调用:
set @t1 =4; set @t2 = 0; CALL p1 (1, 2 ,@t1, @t2); SELECT @t1,@t2;在python中则是这么调用:
import pymysql conn = pymysql.connect( host='localhost', user='root', password='', database='homework', charset='utf8') a = 3 b = 9 cursor = conn.cursor() cursor.callproc('p1', (1, 2, a, b)) conn.commit() res = cursor.fetchall() print(res,a,b) cursor.close() conn.close()可以看到,mysql中的传入两个变量的值已经被修改.python拿到了返回的结果集,但是没有拿到a和b返回值(原理也很简单,二者只是通信,如果没有程序的处理,MySQL不可能来修改python的变量).
如果不仅需要拿结果集,还需要拿到SQL中的返回值,比如在上边的例子里我们指定了a=3和b=9,已经知道p1过程除了返回查询结果之外,还会把第三个位置参数自增100,第四个位置参数等于前两个位置参数的和加1,和传入多少没有关系,在执行之后,其实a和b的值应该被修改成103和4.要如何获得103和4,需要修改python的代码,用一段固定写法获得执行过程后的位置变量,再赋给a和b:
import pymysql conn = pymysql.connect( host='localhost', user='root', password='', database='homework', charset='utf8') a = 3 b = 9 cursor = conn.cursor() cursor.callproc('p1', (1, 2, a, b)) res = cursor.fetchall() cursor.execute('SELECT @_p1_2,@_p1_3') # 固定写法,获得调用过的p1过程的返回值 res2 = cursor.fetchall() a, b = res2[0] conn.commit() print('res is ', res) print('a is {}, b is {} now'.format(a, b)) cursor.close() conn.close()可见res拿到了结果集.使用cursor.execute的特殊写法,再次获取数据,取得刚刚调用过p1之后的第三个和第四个位置变量的返回值(索引从0开始)赋给res2,将其拆解后赋给a和b即可,这样实现了通过mysql的过程帮我们处理python中的数据.
存储过程由于执行的是SQL语句,而且可以伪造返回值,在类似python等程序来操作数据库的时候,通过返回值,可以进行很多逻辑操作,非常实用,等于将数据库的操作和外部程序操作有效结合在了一起.但是用外部程序结合存储过程的缺点是耦合程度太高,很难移植.看几个存储过程的例子:
用存储过程实现事务性操作delimiter \\ create PROCEDURE p1(OUT p_return_code tinyint) BEGIN DECLARE exit handler for sqlexception BEGIN -- ERROR set p_return_code = 1; rollback; END; DECLARE exit handler for sqlwarning BEGIN -- WARNING set p_return_code = 2; rollback; END; START TRANSACTION; alter table tb1 drop column test3; insert into tb2(name)values('seven'); COMMIT; -- SUCCESS set p_return_code = 0; END\\ delimiter ;这段存储过程用一个tinyint类型作为返回值,标记这个存储过程执行之后的状态.DECLARE EXIT HANDLER FOR 是固定用法,后边sqlexception表示出错,sqlwaring表示警告,也都是固定关键字.之后的BEGIN和END之间的就像是python的exception语句,在出错或者出现警告的时候执行,rollback也是关键字,表示回滚操作.START TRANSACTION也是关键字,表示实际开始执行工作的地方,需要执行的语句写在其后,然后用COMMIT关键字提交,如果不出错,则返回值会被设置为0,如果出错,根据是错误还是警告,返回值被设置为1或者2.这样在python中即可得到该存储过程是否成功的结果.
这里故意删除tb1表里一个不存在的字段test3,在mysql里设置变量并且传入,执行如下:
set @vi = 0; call p1(@vi); select @vi;结果发现为1,说明出现错误.而且查看表格并未被改动. 游标
针对一个查询结果集,可以声明一个游标应对这个查询结果集,声明之后,就可以通过移动游标来从结果集中取得数据,游标的性能不高,但是经常可以用来实现一些循环操作.游标只能存在于存储过程或者函数中,离开存储过程或者函数,就失效了.
比如将A表有自增id和number列,要将其全部复制到B表,而且B表的number要等于自增id加上原来的number.可以通过连表然后新增来做,但也可以通过一个存储过程用游标来生成
delimiter // CREATE PROCEDURE p3() BEGIN DECLARE a_id INT; -- 自定义变量1用来接收每次的自增id DECLARE a_number INT; -- 自定义变量2用来接收每次的number DECLARE temp INT default 0; -- 声明用来求和的临时变量 DECLARE done INT DEFAULT FALSE; -- 声明一个标志,标志是否已经取到结尾 DECLARE my_cursor CURSOR FOR select aid,anumber FROM A; -- 这是声明游标的语句,游标指向FOR后边的查询结果 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; -- 这是固定格式,当出现NOT FOUND 错误的时候,将done设置为TRUE OPEN my_cursor; -- 需要先打开游标 loop_name: LOOP -- loop_name可以自定义,后边LOOP关键字表示循环 FETCH my_cursor INTO a_id,a_number; -- FETCH用来从游标取一行结果,每取一次,游标下移一行,取到的行数据需要拆解给同样数量的变量. IF done THEN LEAVE loop_name; END IF; SET temp = a_id + a_number; INSERT INTO B(bnumber) values(temp); END LOOP loop_name; CLOSE my_cursor; -- 运行之后关闭游标,如果多次操作,可以反复打开和关闭,每次关闭之后再打开,游标恢复初始位置. END // delimiter ;游标声明用DECLARE 游标名 CURSOR FOR 查询结果集 来声明,指向了一个结果集,每次用FETCH取数会取结果集的下一行.游标对于查询结果按照行来分别进行操作的时候比较好用,如果操作整个列,没有必要使用游标. 动态执行SQL语句
这是防止SQL注入的方法,PyMySQL里边采取避免自己格式化,传参数给execute方法来避免注入.如果不在程序内使用,采用存储过程也可以动态执行,原理和PyMySQL一样,先格式化标准语句,再将占位符传进去,不能自己去直接写.
delimiter \\ CREATE PROCEDURE p4 (in nid int) BEGIN PREPARE prod FROM 'select * from student where sid > ?'; EXECUTE prod USING @nid; DEALLOCATE prepare prod; END\\ delimiter ;这是一个专门用于查找的存储过程,通过调用存储过程输入参数,避免了自行编写SQL语句