• python 全栈开发,Day64(视图,触发器,函数,存储过程,事务)


     昨日内容回顾

    pymysql:属于python的一个模块
    
    pip3 install pymysql
    
    conn  = pymysql.connect(...,charset = 'uft8')
    
    创建游标
    conn.cursor(dic)
    
    (1)修改数据,删除数据,增加数据一定要commit
        cursor.execut(sql,{}|()|[]) 一定要注意sql注入
    (2)fetchone()
       fetchmany(4)
       fetchall()
    
    cursor.close()
    conn.close()
    View Code

    一、视图

    一、视图的定义

    视图是虚拟表或逻辑表,它被定义为具有连接的SQL SELECT查询语句。因为数据库视图与数据库表类似,它由行和列组成,因此可以根据数据库表查询数据。其内容由查询定义。
    但是,视图并不在数据库中以存储的数据值集形式存在,行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。简单的来说视图是由其定义结果组成的表;

    二、视图的优点

    1、数据库视图允许简化复杂查询,通过数据库视图,您只需使用简单的SQL语句,而不是使用具有多个连接的复杂的SQL语句。
    
    2、安全性。一般是这样做的:创建一个视图,定义好该视图所操作的数据。之后将用户权限与视图绑定。这样的方式是使用到了一个特性:grant语句可以针对视图进行授予权限。

    三、视图的缺点

    1、性能:从数据库视图查询数据可能会很慢,特别是如果视图是基于其他视图创建的。
    
    2、表依赖关系:将根据数据库的基础表创建一个视图。每当更改与其相关联的表的结构时,都必须更改视图。
    四、创建视图

    四、创建视图

    语法:

    CREATE VIEW 视图名称 AS  SQL语句

    临时表应用举例:

    #两张有关系的表,分别是课程表和老师表
    
    #创建课程表
    CREATE TABLE `course` (
      `cid` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '课程id',
      `cname` varchar(20) DEFAULT NULL COMMENT '课程名',
      `teacher_id` int(11) DEFAULT NULL COMMENT '老师id',
      PRIMARY KEY (`cid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='课程表';
    
    #创建老师表
    CREATE TABLE `teacher` (
      `tid` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '老师id',
      `tname` varchar(20) DEFAULT NULL COMMENT '老师名',
      PRIMARY KEY (`tid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='老师表';
    
    #插入数据
    INSERT INTO `course` (`cid`, `cname`, `teacher_id`) VALUES ('1', '生物', '1');
    INSERT INTO `course` (`cid`, `cname`, `teacher_id`) VALUES ('2', '物理', '2');
    INSERT INTO `course` (`cid`, `cname`, `teacher_id`) VALUES ('3', '体育', '3');
    INSERT INTO `course` (`cid`, `cname`, `teacher_id`) VALUES ('4', '美术', '2');
    
    INSERT INTO `teacher` (`tid`, `tname`) VALUES ('1', '张磊');
    INSERT INTO `teacher` (`tid`, `tname`) VALUES ('2', '李平');
    INSERT INTO `teacher` (`tid`, `tname`) VALUES ('3', '刘海燕');
    INSERT INTO `teacher` (`tid`, `tname`) VALUES ('4', '朱云海');
    INSERT INTO `teacher` (`tid`, `tname`) VALUES ('5', '李杰');
    
    #查询李平老师教授的课程名
    #子查询出临时表,作为teacher_id等判断依据
    mysql> select cname from course where teacher_id = (select tid from teacher where tname='李平');
    +--------+
    | cname  |
    +--------+
    | 物理   |
    | 美术   |
    +--------+
    2 rows in set (0.01 sec)
    View Code

    视图的创建:

    #创建老师表的视图
    mysql> create view teacher_view as select * from teacher;
    Query OK, 0 rows affected (0.39 sec)
    
    #查询视图
    mysql> select * from teacher_view;
    +-----+-----------+
    | tid | tname     |
    +-----+-----------+
    |   1 | 张磊      |
    |   2 | 李平      |
    |   3 | 刘海燕    |
    |   4 | 朱云海    |
    |   5 | 李杰      |
    +-----+-----------+
    5 rows in set (0.00 sec)
    
    #查询老师表
    mysql> select * from teacher;
    +-----+-----------+
    | tid | tname     |
    +-----+-----------+
    |   1 | 张磊      |
    |   2 | 李平      |
    |   3 | 刘海燕    |
    |   4 | 朱云海    |
    |   5 | 李杰      |
    +-----+-----------+
    5 rows in set (0.00 sec)
    
    #发现2个表的数据是一摸一样的,没有区别
    View Code

     查看数据库文件,发现teacher_view是没有数据文件ibd的,

    视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。

    五、使用视图

    举例1:

    # 往真实表中插入一条数据,查看一下视图,发现视图表也会跟着更新
    mysql> INSERT INTO teacher (tname) VALUES ('高圆圆');
    Query OK, 1 row affected (0.10 sec)
    
    #查看视图,发现更新了一条数据
    mysql> select * from teacher_view;
    +-----+-----------+
    | tid | tname     |
    +-----+-----------+
    |   1 | 张磊      |
    |   2 | 李平      |
    |   3 | 刘海燕    |
    |   4 | 朱云海    |
    |   5 | 李杰      |
    |   6 | 高圆圆    |
    +-----+-----------+
    6 rows in set (0.00 sec)
    View Code

    举例2:

    #往视图表中插入一条数据,真实表也会发现变化
    mysql> INSERT INTO teacher_view (tname) VALUES ('董卿');
    Query OK, 1 row affected (0.09 sec)
    
    #查看真实表,发现也更新了
    mysql> select * from teacher;
    +-----+-----------+
    | tid | tname     |
    +-----+-----------+
    |   1 | 张磊      |
    |   2 | 李平      |
    |   3 | 刘海燕    |
    |   4 | 朱云海    |
    |   5 | 李杰      |
    |   6 | 高圆圆    |
    |   7 | 董卿      |
    +-----+-----------+
    7 rows in set (0.00 sec)
    View Code

    如果将2张表联合生产视图,是不能修改视图的数据的。因为它不知道,该从哪个表里面插入数据

    举例:

    #创建联合表的视图
    create view ct as select * from course left join teacher on teacher.tid = 
    course.teacher_id;
    
    #查看视图记录
    mysql> select * from ct;
    +-----+--------+------------+------+-----------+
    | cid | cname  | teacher_id | tid  | tname     |
    +-----+--------+------------+------+-----------+
    |   1 | 生物   |          1 |    1 | 张磊      |
    |   2 | 物理   |          2 |    2 | 李平      |
    |   3 | 体育   |          3 |    3 | 刘海燕    |
    |   4 | 美术   |          2 |    2 | 李平      |
    +-----+--------+------------+------+-----------+
    4 rows in set (0.00 sec)
    
    #插入失败
    mysql> insert into ct values(7,'哈哈',7,7,'张三丰');
    ERROR 1471 (HY000): The target table ct of the INSERT is not insertable-into
    View Code

    六、修改视图

    # 语法:ALTER VIEW 视图名称 AS SQL语句
    mysql> alter view teacher_view as select * from teacher where tid > 3;
    Query OK, 0 rows affected (0.09 sec)
    
    #查看视图记录,发现改变了。
    mysql> select * from teacher_view;
    +-----+-----------+
    | tid | tname     |
    +-----+-----------+
    |   4 | 朱云海    |
    |   5 | 李杰      |
    |   6 | 高圆圆    |
    |   7 | 董卿      |
    +-----+-----------+
    4 rows in set (0.00 sec)
    View Code

    七、删除视图

    # 语法:DROP VIEW 视图名称
    
    mysql> drop view teacher_view;
    Query OK, 0 rows affected (0.00 sec)
    View Code

    注意:视图只能用到查询

    如果使用视图查询会很慢,不推荐使用。因为它会消费MySQL性能

    二、触发器

    使用触发器可以定制用户对表进行【增、删、改】操作时前后的行为,注意:没有查询

    一、创建触发器

    有6个动作,分别是增前,增后。删前,删后。改前,改后

    # 插入前
    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
    View Code

    例子:用户和日志表。每次创建一个用户之后,就在日志布表中生成这条记录

    准备表:

    # 创建用户表
    create table user(
        id int primary key auto_increment,
        name varchar(20) not null,
        reg_time datetime, # 注册用户的时间
        affirm enum('yes','no') # no表示该用户执行失败
    );
    
    #创建日志表
    create table userLog(
        id int primary key auto_increment,
        u_name varchar(20) not null,
        u_reg_time datetime # 注册用户的时间
    );
    View Code

    小知识点:

    默认情况下,mysql语句是以分号来作为结束符的。通过delimiter命令,可以修改结束符。

    比如修改为//

    #修改结束符为//
    mysql> delimiter //
    
    #执行sql语句,以分号结尾
    mysql> select * from course;
    #发现结束不了,必须用//才行
        -> 
        -> //
    +-----+--------+------------+
    | cid | cname  | teacher_id |
    +-----+--------+------------+
    |   1 | 生物   |          1 |
    |   2 | 物理   |          2 |
    |   3 | 体育   |          3 |
    |   4 | 美术   |          2 |
    +-----+--------+------------+
    4 rows in set (0.00 sec)
    View Code

    delimiter修改是全局的,不要轻易修改。修改之后,要还原回来。

    二 使用触发器

    触发器无法由用户直接调用,而知由于对表的【增/删/改】操作被动引发的。

    # 创建触发器 delimiter 默认情况下,delimiter是分号 触发器名称应遵循命名约定[trigger time]_[table name]_[trigger event]
    delimiter //
    create trigger after_user_insert after insert on user for each row
    begin
        if new.affirm = 'yes' then
            insert into userLog(u_name,u_reg_time) values(new.name,new.reg_time);
        end if;
    
    end //
    delimiter ;
    
    #往用户表中插入记录,触发触发器,根据if的条件决定是否插入数据
    mysql> insert into user(name,reg_time,affirm) values ('张三',now(),'yes'),('李四',now(),'yes'),('王五',now(),'no');
    Query OK, 3 rows affected (0.13 sec)
    Records: 3  Duplicates: 0  Warnings: 0
    
    # 查看日志表,发现多了两条记录 ,大家应该看到for each row就明白了
    mysql> select * from userlog;
    +----+--------+---------------------+
    | id | u_name | u_reg_time          |
    +----+--------+---------------------+
    |  1 | 张三   | 2018-06-15 16:07:15 |
    |  2 | 李四   | 2018-06-15 16:07:15 |
    +----+--------+---------------------+
    2 rows in set (0.00 sec)
    View Code

    注意:请注意,在为INSERT定义的触发器中,可以仅使用NEW关键字。不能使用OLD关键字。但是,在为DELETE定义的触发器中,没有新行,因此您只能使用OLD关键字。在UPDATE触发器中,OLD是指更新前的行,而NEW是更新后的行

    new.name表示表里面的字段name

    当new.affirm为yes才插入,否则不插入

    三 删除触发器

    语法:
    drop trigger 触发器名;
    
    #删除触发器after_user_insert
    mysql> drop trigger after_user_insert;
    Query OK, 0 rows affected (0.00 sec)
    View Code

    注意:

    不建议使用触发器

    trigger对以后的维护以及开发不怎么好,后面可能忘了定义过trigger,导致一些莫名的bug
    如果是面向用户的高并发应用,都不要使用

    在并发不高的项目,比如管理系统中,可以使用触发器

    三、函数

     MySQL中提供了许多内置函数:

     内置函数

    一、数学函数
        ROUND(x,y)
            返回参数x的四舍五入的有y位小数的值
    
        RAND()
            返回0到1内的随机值,可以通过提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。
    
    二、聚合函数(常用于GROUP BY从句的SELECT查询中)
        AVG(col)返回指定列的平均值
        COUNT(col)返回指定列中非NULL值的个数
        MIN(col)返回指定列的最小值
        MAX(col)返回指定列的最大值
        SUM(col)返回指定列的所有值之和
        GROUP_CONCAT(col) 返回由属于一组的列值连接组合而成的结果    
    
    三、字符串函数
    
        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'
    
    四、日期和时间函数
        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'
    
    五、加密函数
        MD5()    
            计算字符串str的MD5校验和
        PASSWORD(str)   
            返回字符串str的加密版本,这个加密过程是不可逆转的,和UNIX密码加密过程使用不同的算法。
    
    六、控制流函数            
        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  
    
    内置函数
    View Code

    更多函数:中文猛击这里 OR 官方猛击这里

    打开官方链接,里面有21个内置函数 

    掌握内置函数中的时间格式化DATE_FORMAT()的用法

    官网示例:

    mysql> SELECT DATE_FORMAT('2009-10-04 22:23:00', '%W %M %Y');
    +------------------------------------------------+
    | DATE_FORMAT('2009-10-04 22:23:00', '%W %M %Y') |
    +------------------------------------------------+
    | Sunday October 2009                            |
    +------------------------------------------------+
    1 row in set (0.34 sec)
    
    mysql> SELECT DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s');
    +------------------------------------------------+
    | DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s') |
    +------------------------------------------------+
    | 22:23:00                                       |
    +------------------------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT DATE_FORMAT('1900-10-04 22:23:00','%D %y %a %d %m %b %j');
    +-----------------------------------------------------------+
    | DATE_FORMAT('1900-10-04 22:23:00','%D %y %a %d %m %b %j') |
    +-----------------------------------------------------------+
    | 4th 00 Thu 04 10 Oct 277                                  |
    +-----------------------------------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT DATE_FORMAT('1997-10-04 22:23:00','%H %k %I %r %T %S %w');
    +-----------------------------------------------------------+
    | 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                        |
    +-----------------------------------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT DATE_FORMAT('1999-01-01', '%X %V');
    +------------------------------------+
    | DATE_FORMAT('1999-01-01', '%X %V') |
    +------------------------------------+
    | 1998 52                            |
    +------------------------------------+
    1 row in set (0.00 sec)
    
    mysql> SELECT DATE_FORMAT('2006-06-00', '%d');
    +---------------------------------+
    | DATE_FORMAT('2006-06-00', '%d') |
    +---------------------------------+
    | 00                              |
    +---------------------------------+
    1 row in set (0.00 sec)
    View Code

    举2个例子

    #返回参数x的四舍五入的有y位小数的值
    mysql> select round(113.34545,3);
    +--------------------+
    | round(113.34545,3) |
    +--------------------+
    |            113.345 |
    +--------------------+
    1 row in set (0.00 sec)
    
    #返回0到1内的随机值,可以通过提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。
    mysql> select rand();
    +--------------------+
    | rand()             |
    +--------------------+
    | 0.8292567104373655 |
    +--------------------+
    1 row in set (0.33 sec)
    View Code

    举例:博客园 时间处理

    # 1、博客园 时间处理
    CREATE TABLE blog (
        id INT PRIMARY KEY auto_increment,
        NAME CHAR (32),
        sub_time datetime
    );
    
    #2、插入数据
    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字段的值,按照格式后的结果即"年月"来分组
    SELECT DATE_FORMAT(sub_time,'%Y-%m') as y_m,COUNT(1) as num  FROM blog GROUP BY DATE_FORMAT(sub_time,'%Y-%m');
    
    
    # 结果:
    +---------+-----+
    | y_m     | num |
    +---------+-----+
    | 2015-03 |   2 |
    | 2016-07 |   4 |
    | 2017-03 |   3 |
    +---------+-----+
    rows in set (0.00 sec)
    View Code

    关于更多时间处理,请参考官网链接

    https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-format

    下图只列举了一部分

    四、存储过程

    一、存储过程的定义

    存储过程是存储在数据库目录中的一些的声明性SQL语句。
    Java,Python,PHP等应用程序可以调用存储过程。

    MySQL是最受欢迎的开源RDBMS,被社区和企业广泛使用。 然而,在它发布的第一个十年期间,它不支持存储过程,存储函数触发器事件。自从MySQL 5.0版本以来,这些功能被添加到MySQL数据库引擎,使其更加灵活和强大。

    二、存储过程的优点

    #1、通常存储过程有助于提高应用程序的性能。当创建,存储过程被编译之后,就存储在数据库中。 但是,MySQL实现的存储过程略有不同。 MySQL存储过程按需编译。 在编译存储过程之后,MySQL将其放入缓存中。 MySQL为每个连接维护自己的存储过程高速缓存。 如果应用程序在单个连接中多次使用存储过程,则使用编译版本,否则存储过程的工作方式类似于查询。
    
    # 2、存储过程有助于减少应用程序和数据库服务器之间的流量,因为应用程序不必发送多个冗长的SQL语句,而只能发送存储过程的名称和参数。
    
    #3、存储的程序对任何应用程序都是可重用的和透明的。 存储过程将数据库接口暴露给所有应用程序,以便开发人员不必开发存储过程中已支持的功能。
    
    #4、存储的程序是安全的。 数据库管理员可以向访问数据库中存储过程的应用程序授予适当的权限,而不向基础数据库表提供任何权限。

    除了这些优点之外,存储过程有其自身的缺点,在数据库中使用它们之前,您应该注意这些缺点。

    三、存储过程的缺点

    #1、如果使用大量存储过程,那么使用这些存储过程的每个连接的内存使用量将会大大增加。 此外,如果您在存储过程中过度使用大量逻辑操作,则CPU使用率也会增加,因为数据库服务器的设计不当于逻辑运算。
    
    #2、存储过程的构造使得开发具有复杂业务逻辑的存储过程变得更加困难。
    
    #3、很难调试存储过程。只有少数数据库管理系统允许您调试存储过程。不幸的是,MySQL不提供调试存储过程的功能。
    
    #4、开发和维护存储过程并不容易。开发和维护存储过程通常需要一个不是所有应用程序开发人员拥有的专业技能。这可能会导致应用程序开发和维护阶段的问题。

    MySQL存储过程有自己的优点和缺点。开发应用程序时,您应该决定是否应该或不应该根据业务需求使用存储过程。

    四、一个简单的Mysql存储过程示例

    delimiter //
     create procedure b1()
       begin
       select *  from blog;
       end //
    delimiter ;

    解释:

    1.第一个命令是delimiter //,它与存储过程语法无关。 delimter语句将标准分隔符 - 分号(;)更改为://。 在这种情况下,分隔符从分号(;)更改为双斜杠//。为什么我们必须更改分隔符? 因为我们想将存储过程作为整体传递给服务器,而不是让mysql工具一次解释每个语句。 在END关键字之后,使用分隔符//来指示存储过程的结束。 最后一个命令(DELIMITER;)将分隔符更改回分号(;)。

    2.使用create procedure语句创建一个新的存储过程。在create procedure语句之后指定存储过程的名称。在这个示例中,存储过程的名称为:b1,并把括号放在存储过程的名字之后。

    3.begin和end之间的部分称为存储过程的主体。将声明性SQL语句放在主体中以处理业务逻辑。 在这个存储过程中,我们使用一个简单的select语句来查询blog表中的数据。

    mysql中调用存储过程 

    语法:
    call 存储过程名();
    
    #调用b1,返回的是begin和end之间的sql语句
    mysql> call b1();
    +----+---------+---------------------+
    | id | NAME    | sub_time            |
    +----+---------+---------------------+
    |  1 | 第1篇   | 2015-03-01 11:31:21 |
    |  2 | 第2篇   | 2015-03-11 16:31:21 |
    |  3 | 第3篇   | 2016-07-01 10:21:31 |
    |  4 | 第4篇   | 2016-07-22 09:23:21 |
    |  5 | 第5篇   | 2016-07-23 10:11:11 |
    |  6 | 第6篇   | 2016-07-25 11:21:31 |
    |  7 | 第7篇   | 2017-03-01 15:33:21 |
    |  8 | 第8篇   | 2017-03-01 17:32:21 |
    |  9 | 第9篇   | 2017-03-01 18:31:21 |
    +----+---------+---------------------+
    9 rows in set (0.00 sec)
    
    Query OK, 0 rows affected (0.02 sec)
    View Code

    在python中基于pymysql调用

    #导入模块
    import pymysql
    
    # 1.连接
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='', db='db1', charset='utf8')
    
    # 2.创建游标
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    
    # 3.执行存储过程b1
    cursor.callproc('b1')
    #返回多个记录(rows)
    result = cursor.fetchall()
    print('返回记录数:',len(result))
    
    # 关闭连接,游标和连接都要关闭
    cursor.close()
    conn.close()
    View Code

    执行py文件,执行输出:

    返回记录数: 9

    五、声明变量

    要在存储过程中声明变量,可以使用delclare语句,如下

    DECLARE variable_name datatype(size) DEFAULT default_value;

    下面来更详细地解释上面的语句:

    首先,在DECLARE关键字后面要指定变量名。变量名必须遵循MySQL表列名称的命名规则。
    其次,指定变量的数据类型及其大小。变量可以有任何MySQL数据类型,如INT,VARCHAR,DATETIME等。
    第三,当声明一个变量时,它的初始值为NULL。但是可以使用DEFAULT关键字为变量分配默认值。

    实现:

    delimiter //
     create procedure b2()
       begin
       DECLARE n int DEFAULT 1;
       set n  = 5;
       select *  from blog where id = n;
       end //
    delimiter ;
    
    # mysql中调用存储过程
    mysql> call b2();
    +----+---------+---------------------+
    | id | NAME    | sub_time            |
    +----+---------+---------------------+
    |  5 | 第5篇   | 2016-07-23 10:11:11 |
    +----+---------+---------------------+
    1 row in set (0.00 sec)
    View Code

    六、存储过程传参

    在现实应用中,开发的存储过程几乎都需要参数。这些参数使存储过程更加灵活和有用。 在MySQL中,参数有三种模式:IN,OUT或INOUT。
    IN - 是默认模式。在存储过程中定义IN参数时,调用程序必须将参数传递给存储过程。 另外,IN参数的值被保护。这意味着即使在存储过程中更改了IN参数的值,在存储过程结束后仍保留其原始值。换句话说,存储过程只使用IN参数的副本。
    OUT - 可以在存储过程中更改OUT参数的值,并将其更改后新值传递回调用程序。请注意,存储过程在启动时无法访问OUT参数的初始值。
    INOUT - INOUT参数是IN和OUT参数的组合。这意味着调用程序可以传递参数,并且存储过程可以修改INOUT参数并将新值传递回调用程序。

    在存储过程中定义参数的语法如下:

    MODE param_name param_type(param_size)

    根据存储过程中参数的目的,MODE可以是IN,OUT或INOUT。
    param_name是参数的名称。参数的名称必须遵循MySQL中列名的命名规则。
    在参数名之后是它的数据类型和大小。和变量一样,参数的数据类型可以是任何有效的MySQL数据类型

    ps:如果存储过程有多个参数,则每个参数由逗号(,)分隔。

    1. in

    delimiter //
     create procedure b3(
         in blogName varchar(30)
     )
       begin
       select *  from blog where NAME = blogName;
       end //
    delimiter ;
    View Code

    mysql中调用存储过程

    mysql> call b3('第5篇');
    +----+---------+---------------------+
    | id | NAME    | sub_time            |
    +----+---------+---------------------+
    |  5 | 第5篇   | 2016-07-23 10:11:11 |
    +----+---------+---------------------+
    1 row in set (0.00 sec)
    View Code

    python中调用存储过程

    #导入模块
    import pymysql
    
    # 1.连接
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='', db='db1', charset='utf8')
    
    # 2.创建游标
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    
    # 3.执行存储过程b3,传入参数。注意:参数结尾必须有一个逗号
    cursor.callproc('b3',args=('第5篇',))
    #返回多个记录(rows)
    result = cursor.fetchall()
    print('返回记录数:',len(result))
    
    # 关闭连接,游标和连接都要关闭
    cursor.close()
    conn.close()
    View Code

    执行py文件,输出:

    返回记录数: 1

    2. out

    delimiter //
     create procedure b4(
         in year int,
         out count  int
     )
       begin
           SELECT COUNT(1) into count  FROM blog GROUP BY DATE_FORMAT(sub_time,'%Y') having max(DATE_FORMAT(sub_time,'%Y')) = year ;
           set count = 6;
       end //
    delimiter ;
    
    #调用存储过程
    mysql> call b4(2016,@count);
    Query OK, 1 row affected (0.00 sec)
    
    #查看返回值,out只能返回值
    mysql> select @count;
    +--------+
    | @count |
    +--------+
    |      6 |
    +--------+
    1 row in set (0.00 sec)
    View Code

    3. inout

    inout:既可以传入又可以返回

    delimiter //
     create procedure b5(
         inout n1 int
     )
       begin
        select * from blog where id > n1;
       end //
    delimiter ;
    View Code

    mysql中调用存储过程(必须先定义一个用户变量)

    #必须先定义一个用户变量
    mysql> set @n1=5;
    Query OK, 0 rows affected (0.00 sec)
    
    #再调用,就可以了
    mysql> call b5(@n1);
    +----+---------+---------------------+
    | id | NAME    | sub_time            |
    +----+---------+---------------------+
    |  6 | 第6篇   | 2016-07-25 11:21:31 |
    |  7 | 第7篇   | 2017-03-01 15:33:21 |
    |  8 | 第8篇   | 2017-03-01 17:32:21 |
    |  9 | 第9篇   | 2017-03-01 18:31:21 |
    +----+---------+---------------------+
    4 rows in set (0.00 sec)
    View Code

    在python中基于pymysql调用

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    #导入模块
    import pymysql
    
    # 1.连接
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='', db='db1', charset='utf8')
    
    # 2.创建游标
    cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
    
    # 3.执行存储过程b3,传入参数。注意:参数结尾必须有一个逗号
    cursor.callproc('b5',args=('4',))
    #返回多个记录(rows)
    result = cursor.fetchall()
    print('返回记录数:',len(result))
    
    cursor.execute('select @n1')
    print(cursor.fetchall())
    
    # 关闭连接,游标和连接都要关闭
    cursor.close()
    conn.close()
    View Code

    执行py文件,输出:

    返回记录数: 5
    [{'@n1': None}]

    为什么n1输出None呢?因为n1是一个用户变量

    用户变量与数据库连接有关, 这某个连接中声明变量, 在断开连接时这个变量就会消失, 且在这个连接中声明的变量无法在另一个连接中使用

    五、事务

    事务用于将某些操作的多个SQL作为原子性操作,一旦有某一个出现错误,即可回滚到原来的状态,从而保证数据库数据完整性。

    举例说明:

    准备基础数据

    #创建用户表
    create table user2(
    id int primary key auto_increment,
    name char(32),
    balance int
    );
    
    #插入数据
    insert into user2(name,balance)
    values
    ('wsb',1000),
    ('egon',1000),
    ('ysb',1000);
    View Code

    模拟异常,并进行回滚

    #先模拟出现异常,回滚到初始状态
    #原子操作
    #表示开始事务
    mysql> start transaction; 
    Query OK, 0 rows affected (0.00 sec)
    
    #买支付100元
    mysql> update user2 set balance=900 where name='wsb'; 
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    #中介拿走10元
    mysql> update user2 set balance=1010 where name='egon'; 
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    #卖家拿到90元
    mysql> update user2 set balance=1090 where name='ysb'; 
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    #查看数据,发现不对
    mysql> select * from user2;
    +----+------+---------+
    | id | name | balance |
    +----+------+---------+
    |  1 | wsb  |     900 |
    |  2 | egon |    1010 |
    |  3 | ysb  |    1090 |
    +----+------+---------+
    3 rows in set (0.00 sec)
    
    #回滚到初始状态
    mysql> rollback;
    Query OK, 0 rows affected (0.10 sec)
    
    mysql> select * from user2;
    +----+------+---------+
    | id | name | balance |
    +----+------+---------+
    |  1 | wsb  |    1000 |
    |  2 | egon |    1000 |
    |  3 | ysb  |    1000 |
    +----+------+---------+
    3 rows in set (0.00 sec)
    View Code

    注意:一旦执行commit,就无法回滚了!

    举例:

    #原子操作
    start transaction;
    update user2 set balance=900 where name='wsb'; #买支付100元
    update user2 set balance=1010 where name='egon'; #中介拿走10元
    update user2 set balance=1090 where name='ysb'; #卖家拿到90元
    commit;
    
    #查看表记录,发现不对
    mysql> select * from user;
    +----+--------+---------------------+--------+
    | id | name   | reg_time            | affirm |
    +----+--------+---------------------+--------+
    |  1 | 张三   | 2018-06-15 16:07:15 | yes    |
    |  2 | 李四   | 2018-06-15 16:07:15 | yes    |
    |  3 | 王五   | 2018-06-15 16:07:15 | no     |
    +----+--------+---------------------+--------+
    3 rows in set (0.00 sec)
    
    #执行回滚操作
    mysql> rollback;
    Query OK, 0 rows affected (0.00 sec)
    
    #再查看表记录,发现回不来了...
    mysql> select * from user2;
    +----+------+---------+
    | id | name | balance |
    +----+------+---------+
    |  1 | wsb  |     900 |
    |  2 | egon |    1010 |
    |  3 | ysb  |    1090 |
    +----+------+---------+
    3 rows in set (0.00 sec)
    View Code

    下面是操作:当p_return_code为1时,表示异常,立马回滚。当为2时,出现警告,立马回滚原始状态。0表示成功

    delimiter //
    create PROCEDURE b6(
        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; 
            insert into blog(name,sub_time) values('ok',now());
        COMMIT; 
    
        -- SUCCESS 
        set p_return_code = 0; #0代表执行成功
    
    END //
    delimiter ;
    
    #设置局部变量
    mysql> set @res=123;
    Query OK, 0 rows affected (0.00 sec)
    
    #调用事务
    mysql> call b6(@res);
    Query OK, 0 rows affected (0.36 sec)
    
    #查看局部变量,发现归0了。说明执行成功
    mysql> select @res;
    +------+
    | @res |
    +------+
    |    0 |
    +------+
    1 row in set (0.00 sec)
    
    #查看表,会发现有一条ok记录
    mysql> select * from blog;
    
    
    | 26 | ok   | 2018-06-15 21:27:02 |
    View Code

    DECLARE exit handler for sqlexception
    DECLARE exit handler for sqlwarning

    这2个表示异常处理,由于无法模拟异常。只能简单的执行存储过程,就可以了。

  • 相关阅读:
    Android Lint简介
    免费HTTP数据抓包Fiddler2[4.6.1.2]以及显示中文包内容的方法
    IE6、7下bug
    图表插件
    学习:使用svg
    jQuery Transit
    jQuery基础学习笔记(1)
    HTTP协议详解学习
    html5学习笔记
    html释疑
  • 原文地址:https://www.cnblogs.com/xiao987334176/p/9187248.html
Copyright © 2020-2023  润新知