MySQL存储过程和函数以及游标使用详解

时间:2021-12-30 14:06:41

1. 什么是存储过程和函数

将SQL语句放入一个集合里,然后直接调用存储过程和函数来执行已经定义好的SQL语句,通过存储过程和函数,可以避免开发人员重复编写相同的SQL语句

MYSQL存储过程和函数是保存在服务器中,在服务器中存储和执行,可以减少客户端和服务器端之间数据传输的消耗

存储过程就是一组已经保存在数据库中的语句,并且可以随时地调用

 

存储过程允许标准组件式编程,存储过程在被创建以后可以在程序中被多次调用而不必重新编写该存储过程的SQL语句,而且数据库专业人员可随时对存储过程进行修改,但对应用程序源代码毫无影响。因为应用程序源代码只包含存储过程的调用语句从而极大地提高了程序的可移植性

 

1.1 什么时候要用到存储过程(存储过程的特点):

(1)       存储过程是在服务器端运行的,它的执行速度比较快。

(2)       存储过程执行一次后,就会驻留在高处缓冲存储器中,在以后的操作中,只需要从高处缓冲存储器中调用已经编译好的二进制代码来执行,这样就能提高系统的性能和响应时间。

(3)       使用存储过程,可以确保数据库的安全,因为使用存储过程可以完成数据库的所有操作,因为可以把想要进行的操作都放入SQL语句中,然后通过编程的方式来控制对数据库的访问权限。

 

1.2 利用mysql的存储过程比单独执行mysql的优势在哪里?好处有什么?

 

计算机上调用Transaction-SQL 编写的一段程序原因在于存储过程具有以下优点

 

1 存储过程允许标准组件式编程

    存储过程在被创建以后可以在程序中被多次调用,而不必重新编写该存储过程的SQL语句,而且数据库专业人员可随时对存储过程进行修改,但对应用程序源代码毫无影响,因

 为应用程序源代码只包含存储过程的调用语句,从而极大地提高了程序的可移植性

 

 

2 存储过程能够实现较快的执行速度

    如果某一操作包含大量的Transaction-SQL 代码,或分别被多次执行那么存储过程要比批处理的执行速度快很多。因为存储过程是预编译的,在首次运行一个存储过程时,查询优化器对其进行分析优化,并给出最终被存在系统表中的执行计划,而批处理的Transaction-SQL 语句在每次运行时都要进行编译和优化,因此速度相对要慢一些

 

 

3 存储过程能够减少网络流量

   对于同一个针对数据数据库对象的操作,如查询、修改,如果这一操作所涉及到的Transaction-SQL 语句被组织成一存储过程,那么当在客户计算机上调用该存储过程时,网络中传送的只是该调用语句,否则将是多条SQL 语句从而大大增加了网络流量降低网络负载

 

 

4 存储过程可被作为一种安全机制来充分利用

    系统管理员通过对执行某一存储过程的权限,进行限制从而能够实现对相应的数据访问权限的限制,避免非授权用户对数据的访问,保证数据的安全。 

 

2. 创建存储过程:

 

CREATE PROCEDURE sp_name

([proc_parameter[,…]])

[characteristic]

Routine_body

 

说明:

Sp_name:存储过程名称,自定义,尽量起一个有意义的名称

([proc_parameter[,…]]):需要接收或者输出的参数

[characteristic]:特性

Routine_body:要执行的代码,写在BEGIN和END中,BEGIN和END类似于函数体的{}

 

3.存储过程的参数:

3.1 输入输出的参数

输入输出的参数 参数的名称 参数的类型

3.1.1输入输出的参数包括:

IN:输入,把外界的数据传递到存储过程当中

OUT:输出,把存储过程的运算结果传递到外界

INOUT:输入输出,既可以把外界的数据传递到存储过程当中,又可以把存储过程的运算结果传递到外界

3.1.2参数的类型

可以是MYSQL数据库中的任意类型

3.1.3 特性

LANGUAGE SQL默认的,说明Routine_body部分是由SQL语句注册,即数据库默认的语言

DETERMINISTIC:指明存储过程执行的结果是确定的,每次执行存储过程的时候,相同的输入会得到相同的输出。

NOT DETERMINISTIC:指明存储过程执行的结果不是确定的,每次执行存储过程的时候,相同的输入会得到不同的输出,默认情况下,结果是非确定的。

子查询使用SQL语句的限制:

CONTAINS SQL:表示子程序中可以包含SQL语句,但不包含读或写数据。默认情况下使用该限制。

NO SQL:不包含SQL语句

READS SQL DATA:包含查询数据的语句

MODIFIES SQL DATA:包含写数据的语句

--

SQL SECURITY DEFINER / INVOKER:谁有权限来执行这个存储过程,DEFINER(默认)表示只有定义者自己可以执行,INVOKER表示调用者可以执行。

3.1.4 注释

COMMENT  ‘string’

‘string’:注释信息,可以在创建存储过程的时候指定注释。

4. 改变存储过程默认的定界符

通过DELIMITER来改变

例子:

DELIMITER //

CREATE PROCEDURE sp_demo1()

BEGIN

SELECT * FROM users2;

END

//

DELIMITER ;.

Query OK, 0 rows affected (0.28 sec)

说明:因为存储过程也包含了很多SQL语句,而这些SQL语句也都是以分号结尾的,为了避免定界符的冲突,所以使用DELINITER来改变定界符

注意:

(1)DELIMITER与定界符之间,一定要有一个空格,否则设置将无效。

(2)要注意每次创建存储过程结束后,要将定界符恢复为分号,这是一个好的习惯。

 

5.创建带参数的存储过程:

 

DELIMITER //

CREATE PROCEDURE age_from_user2(IN user_id INT,OUT user_age INT)

READS SQL DATA

BEGIN

SELECT ageINTO user_age FROM user

WHERE id=user_id;

END

//

DELIMITER ;.

 

说明:

(1) INT表示参数的返回值,或者说是参数的数据类型

(2)INTO 参数名:表示将SQL语句执行的结果赋给INTO后面的参数中

 

 

创建IN参数的存储过程的例子:

查询订货量大于外界所传递进来的参数p_in的订单数据

DELIMITER //

CREATE PROCEDURE proc2(IN p_in INT)

BEGIN

SELECT * FROM `order` WHERE onum>p_in;

END

//

DELIMITER ;

 

创建带有OUT输出参数的例子:

DELIMITER //

CREATE PROCEDURE proc3(OUT p_out INT )

BEGIN

SELECT count(*) INTO p_out FROM custom;

END

//

DELIMITER ;

 

说明:INTO 参数名:表示将count(*)的结果赋给INTO后面的参数中

 

创建带有INOUT输入输出参数的存储过程:

DELIMITER //

CREATE PROCEDURE proc4(INOUT p_io INT)

BEGIN

SET p_io=5;

END

//

DELIMITER ;

 

6.创建存储函数

 

存储过程与存储函数本质上是相同的,都属于存储程序,也就是保存在数据库当中的程序,用的时候,都可以随时调用。

 

6.1 存储过程与存储函数的区别

(1)存储过程可以指定IN、OUT参数,存储函数不需要指定输入输出参数,存储函数所有的参数都属于IN参数。

(2)存储函数可以通过RETURN语句将运算的结果返回,但是存储过程不允许调用RETURN语句,存储过程可以通过调用OUT参数,将运算的结果返回给外界。

 

6.2 创建存储函数

CREATE FUNCTION  func_name ([func_parameter[…]])

RETURNS type

[characteristic…]

Routine_body

 

说明:

RETURN type:表示返回值的类型

[characteristic…]:表示函数的特性,与存储过程的特性一致

 

例子:

 

DELIMITER //

CREATE FUNCTION username_from_user(user_id INT)

RETURNS VARCHAR(20)

BEGIN

RETURN (SELECT username FROM users2 WHERE id=user_id);

END

//

DELIMITER ;

说明:

RETURNS 是指定返回值的数据类型

RETURN() 是将返回结果反馈给外界。

 

 

7.调用存储过程

 

CALL sp_name ([parameter[,…]])

 

注意:

(1)      调用的时候,一定要有执行的权限

(2)       调用之后,系统执行存储过程的语句,然后将输出结果返回

 

例子:

CALL sp_demo1();

 

7.1调用有参数的存储过程:

 

先创建一个存储过程:

DELIMITER //

CREATE PROCEDURE age_from_user2(IN user_id INT,OUT user_age INT)

READS SQL DATA

BEGIN

SELECT age INTO user_age FROM user

WHERE id=user_id;

END

//

DELIMITER ;

 

然后调用这个存储过程,注意,变量用@变量名称

CALL age_from_user2(120,@use_age);

 

查看变量的值:

mysql> SELECT @use_age;

+----------+

| @use_age |

+----------+

|       22 |

+----------+

1 row in set (0.00 sec)

 

7.2调用有IN参数的存储过程:

创建IN参数的存储过程的例子:

查询订货量大于外界所传递进来的参数p_in的订单数据

DELIMITER //

CREATE PROCEDURE proc2(IN p_in INT)

BEGIN

SELECT * FROM `order` WHERE onum>p_in;

END

//

DELIMITER ;

调用有IN参数的存储过程:

SET @num=10;

CALL proc2(@num);

 

7.3带有OUT参数的存储过程的调用

创建带有OUT输出参数的例子:

DELIMITER //

CREATE PROCEDURE proc3(OUT p_out INT )

BEGIN

SELECT count(*) INTO p_out FROM custom;

END

//

DELIMITER ;

 

说明:INTO 参数名:表示将count(*)的结果赋给INTO后面的参数中

调用存储过程:

mysql> SET @amount=0;

Query OK, 0 rows affected (0.00 sec)

 

mysql> CALL proc3(@amount);

Query OK, 1 row affected (0.00 sec)

 

mysql> SELECT @amount;

+---------+

| @amount |

+---------+

|       4 |

+---------+

1 row in set (0.00 sec)

 

7.4 调用有INOUT参数的存储过程

创建带有INOUT输入输出参数的存储过程:

DELIMITER //

CREATE PROCEDURE proc4(INOUT p_io INT)

BEGIN

SET p_io=5;

END

//

DELIMITER ;

 

调用有INOUT参数的存储过程:

 

mysql> SET @num=-1;

Query OK, 0 rows affected (0.00 sec)

 

mysql> CALL proc4(@num);

Query OK, 0 rows affected (0.00 sec)

 

mysql> SELECT @num;

+------+

| @num |

+------+

|    5 |

+------+

1 row in set (0.00 sec)

 

8.调用存储函数

调用存储函数与调用系统函数的格式是一样的。

SELECT func_name([parameter[,…]]);

 

例子:

创建存储函数:

DELIMITER //

CREATE FUNCTION username_from_user(user_id INT)

RETURNS VARCHAR(20)

BEGIN

RETURN (SELECT username FROM users2 WHERE id=user_id);

END

//

DELIMITER ;

 

调用存储函数:

mysql> SELECT username_from_user(120);

+-------------------------------------+

| username_from_user(120) |

+-------------------------------------+

| nihao                  |

+-------------------------------------+

1 row in set (0.05 sec)

 

调用存储函数例子:

先创建存储函数

DELIMITER //

CREATE FUNCTION func1(id CHAR)

RETURNS VARCHAR(10)

BEGIN

RETURN(SELECT cname FROM custom WHERE cid=id);

END

//

DELIMITER ;

 

调用存储函数:

mysql> SET @id='110002';

Query OK, 0 rows affected (0.00 sec)

 

mysql> SET @name='';

Query OK, 0 rows affected (0.00 sec)

 

mysql> SELECT func1(@id) INTO @name;

Query OK, 1 row affected (0.00 sec)

 

mysql> SELECT @name;

+--------------------------------------------+

| @name                    |

+--------------------------------------------+

| 湖北众合粮油工业有限公司 |

+--------------------------------------------+

1 row in set (0.00 sec)


9. 查看已创建好的存储过程和函数:

 

查看存储过程:SHOW PROCEDURE STATUS LIKE ‘sp_name’;

查看存储函数:SHOW FUNCTION STATUS LIKE ‘func_name’;

查看全部存储过程:SHOW PROCEDURE STATUS \G

查看全部存储函数:SHOW FUNCTION STATUS \G

 

注意:‘sp_name’和‘func_name’一定要加引号。

例:

SHOW PROCEDURE STATUS LIKE 'age_from_user3';

SHOW PROCEDURE STATUS LIKE 'age_from_user3'\G; (按行输出)

*************************** 1. row ***************************

                  Db: homework (在哪个数据库下)

                Name: age_from_user3 (存储过程的名字)

                Type: PROCEDURE (存储过程的类型)

             Definer: root@localhost (存储过程的定义者)

            Modified: 2014-11-13 20:48:11 (存储过程的修改时间)

             Created: 2014-11-13 20:48:11  (存储过程的创建时间)

       Security_type: DEFINER (安全类型,谁可以执行这个存储过程)

             Comment: (注释)

character_set_client: gbk (客户端的字符集)

collation_connection: gbk_chinese_ci (校验字符集)

  Database Collation: utf8_bin (数据库的字符集)

1 row in set (0.00 sec)

 

查看存储函数:

 

SHOW FUNCTION STATUS LIKE 'username_from_user';

SHOW FUNCTION STATUS LIKE 'username_from_user'\G;

*************************** 1. row ***************************

                  Db: homework

                Name: username_from_user

                Type: FUNCTION

             Definer: root@localhost

            Modified: 2014-11-13 20:40:10

             Created: 2014-11-13 20:40:10

       Security_type: DEFINER

             Comment:

character_set_client: gbk

collation_connection: gbk_chinese_ci

  Database Collation: utf8_bin

1 row in set (0.00 sec)

10.查看存储过程和存储函数的定义

 

SHOW CREATE PROCEDURE sp_name;

SHOW CREATE FUNCTION func_name;

 

例子:

SHOW CREATE PROCEDURE age_from_user3\G;

*************************** 1. row ***************************

           Procedure: age_from_user3

            sql_mode: NO_ENGINE_SUBSTITUTION  (SQL类型)

    Create Procedure: CREATE DEFINER=`root`@`localhost` PROCEDURE `age_from_user3`(IN user_id INT,OUT user_age INT)

    READS SQL DATA

BEGIN

SELECT age INTO user_age FROM users2

WHERE id=user_id;

END

character_set_client: gbk

collation_connection: gbk_chinese_ci

  Database Collation: utf8_bin

1 row in set (0.00 sec)

 

查看函数的创建例子:

mysql> SHOW CREATE FUNCTION username_from_user\G;

*************************** 1. row ***************************

            Function: username_from_user

            sql_mode: NO_ENGINE_SUBSTITUTION

     Create Function: CREATE DEFINER=`root`@`localhost` FUNCTION `username_from_user`(user_id INT) RETURNS varchar(20) CHARSET utf8 COLLATE utf8_bin

BEGIN

RETURN (SELECT username FROM users2 WHERE id=user_id);

END

character_set_client: gbk

collation_connection: gbk_chinese_ci

  Database Collation: utf8_bin

1 row in set (0.00 sec)

 

11. 查看数据库information_schema中的存储过程和函数

数据库information_schema保存了所有的存储过程和存储函数,均保存在了ROUTINES表中。

(1)       切换数据库:mysql> USE information_schema;

(2)       查看ROUTINES表:mysql> SELECT * FROM ROUTINES\G

 

或者使用:SELECT * FROM information_schema.routines \G

 

可以根据查询结果的选项内容,进行条件查询:

SELECT * FROM information_schema.routines WHERE routine_type='FUNCTION' \G

 

12.修改存储过程和函数的属性

我们需要明白这里的修改不是修改其中的SQL语句,而是修改它的安全性以及数据访问。我们也可以通过客户端工具进行查看和修改。

 

ALTER PROCEDURE sp_name  [COMMENT ‘string’];

ALTER FUNCTION func_name  [COMMENT ‘string’];

 

 

修改存储过程:

mysql> ALTER PROCEDURE sp_demo1 COMMENT 'THIS IS A TEST';

Query OK, 0 rows affected (0.00 sec)

 

mysql> SHOW PROCEDURE STATUS LIKE 'sp_demo1'\G;

 

修改存储函数:

ALTER FUNCTION  username_from_user COMMENT'THIS IS A TEST OF FUCTION';

mysql> SHOW FUNCTION STATUS LIKE 'username_from_user'\G;

 

12.1 存储过程的安全性

安全类型有两个

DEFINER:定义者,定义这个存储过程的人可以执行它默认

INVOKER:调用者,调用这个存储过程的人可以执行它

SQL 数据访问选项有4个

CONTAINS SQL:存储过程或者函数包含SQL语句

NO SQL:存储过程或者函数不包含SQL语句

READS SQL DATA:存储过程或者函数的SQL语句是读数据库的数据

MODIFIES SQL DATA:存储过程或者函数的SQL语句是修改数据库的数据

 

12.2 修改存储过程和函数的安全性

修改之前先查看一下属性值:

SELECT * FROM information_schema.routines \G

 

然后使用ALTER 语句修改存储过程proc2的安全类型和数据访问选项:

ALTER PROCEDURE proc2

MODIFIES SQL DATA

SQL SECURITY INVOKER;

 

SHOW PROCEDURE STATUS LIKE 'proc2'\G

SELECT * FROM information_schema.routines \G

 

 

13. 删除存储过程及存储函数

 

DROP PROCEDURE sp_name;

DROP FUNCTION sp_name;

 

当删除不存在的存储过程或者存储函数的时候,会显示报错:

mysql> DROP PROCEDURE sp_demo1;

ERROR 1305 (42000): PROCEDURE homework.sp_demo1 does not exist

 

如果想屏蔽错误,以警告的形式提示,可以使用:

 

DROP PROCEDURE IF EXISTS  sp_name;

DROP FUNCTION IF EXISTS  sp_name;

 

例子:

mysql> DROP PROCEDURE IF EXISTS  sp_name;

Query OK, 0 rows affected, 1 warning (0.00 sec)

 

查看警告的内容:SHOW WARNINGS;

 

 

mysql> SHOW WARNINGS;

+-------+------+-------------------------------------------------------------------------+

| Level | Code | Message                                   |

+-------+------+-------------------------------------------------------------------------+

| Note | 1305 | PROCEDURE homework.sp_name does not exist   |

+-------+------+-------------------------------------------------------------------------+

1 row in set (0.01 sec)

 

删除存储过程例子:

 

mysql> DROP PROCEDURE sp_demo1;

Query OK, 0 rows affected (0.05 sec)

 

mysql> SHOW CREATE PROCEDURE sp_demo1;

ERROR 1305 (42000): PROCEDURE sp_demo1 does not exist

 

删除存储函数例子:

DROP FUNCTION username_from_user;

基于MySQL游标的具体使用详解

测试表 level ;

复制代码代码如下:
create table test.level (name varchar(20));

再 insert 些数据 ;

代码

初始化

复制代码代码如下:
drop procedure if exists useCursor //

建立 存储过程 create
复制代码代码如下:
CREATE PROCEDURE useCursor()

BEGIN


局部变量的定义 declare
复制代码代码如下:
declare tmpName varchar(20) default '' ;  
declare allName varchar(255) default '' ;  
declare cur1 CURSOR FOR SELECT name FROM test.level ;  

MySQL 游标 异常后 捕捉

并设置 循环使用 变量 tmpname 为 null 跳出循环。

复制代码代码如下:
declare CONTINUE HANDLER FOR SQLSTATE '02000' SET tmpname = null; 

开游标
复制代码代码如下:
OPEN cur1;

游标向下走一步
复制代码代码如下:
FETCH cur1 INTO tmpName;

循环体 这很明显 把MySQL 游标查询出的 name 都加起并用 ; 号隔开
复制代码代码如下:
WHILE ( tmpname is not null) DO 
set tmpName = CONCAT(tmpName ,";") ; 
set allName = CONCAT(allName ,tmpName) ; 

游标向下走一步
复制代码代码如下:
FETCH cur1 INTO tmpName;

结束循环体: 
复制代码代码如下:
END WHILE;

关闭游标
复制代码代码如下:
CLOSE cur1;

选择数据
复制代码代码如下:
select allName ;

结束存储过程
复制代码代码如下:
END;//

调用存储过程:
复制代码代码如下:
call useCursor()//

运行结果:
复制代码代码如下:
mysql> call useCursor()//

+--------------------------------------+

| allName                              |

+--------------------------------------+

| f1;c3;c6;c5;c2;c4;c1;f1;f3;f4;f2;f5; |

+--------------------------------------+

1 row in set (0.00 sec)


loop循环游标:
复制代码代码如下:
DELIMITER $$  

DROP PROCEDURE IF EXITS cursor_example$$  
CREATE PROCEDURE cursor_example()  
     READS SQL DATA  
BEGIN  
     DECLARE l_employee_id INT;  
     DECLARE l_salary NUMERIC(8,2);  
     DECLARE l_department_id INT;  
     DECLARE done INT DEFAULT 0;  
     DECLARE cur1 CURSOR FOR SELECT employee_id, salary, department_id FROM employees;  
     DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;  

     OPEN cur1;  
     emp_loop: LOOP  
         FETCH cur1 INTO l_employee_id, l_salary, l_department_id;  
         IF done=1 THEN  
             LEAVE emp_loop;  
         END IF;  
     END LOOP emp_loop;  
     CLOSE cur1;  
END$$  
DELIMITER ;  

repeat循环游标:
复制代码代码如下:
/*创建过程*/
DELIMITER //
DROP PROCEDURE IF EXISTS test //
CREATE PROCEDURE test()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE a VARCHAR(200) DEFAULT '';
    DECLARE c VARCHAR(200) DEFAULT '';

    DECLARE mycursor CURSOR FOR SELECT  fusername FROM uchome_friend;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;

    OPEN mycursor;

    REPEAT 
        FETCH mycursor INTO a;
        IF NOT done THEN
            SET c=CONCAT(c,a);/*字符串相加*/
        END IF;

    UNTIL done END REPEAT;

    CLOSE mycursor;

    SELECT c;
END //
DELIMITER ;

复制代码代码如下:
/*创建过程*/
DELIMITER //
DROP PROCEDURE IF EXISTS test //
CREATE PROCEDURE test()
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE a VARCHAR(200) DEFAULT '';
    DECLARE c VARCHAR(200) DEFAULT '';

    DECLARE mycursor CURSOR FOR SELECT  fusername FROM uchome_friend;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;

    OPEN mycursor;

    REPEAT 
        FETCH mycursor INTO a;
        IF NOT done THEN
            SET c=CONCAT(c,a);/*字符串相加*/
        END IF;

    UNTIL done END REPEAT;

    CLOSE mycursor;

    SELECT c;
END //
DELIMITER ;

多返回值情况游标实例

代码如下:
DELIMITER $$

DROP PROCEDURE IF EXISTS getUserInfo $$

CREATE PROCEDURE getUserInfo(in date_day datetime)
-- 
-- 实例
-- MYSQL存储过程名为:getUserInfo
-- 参数为:date_day日期格式:2008-03-08
--
    BEGIN
declare _userName varchar(12); -- 用户名
declare _chinese int ; -- 语文
declare _math int ;    -- 数学
declare done int;

-- 定义游标
DECLARE rs_cursor CURSOR FOR SELECT username,chinese,math from userInfo where datediff(createDate, date_day)=0;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;

-- 获取昨天的日期
if date_day is null then
   set date_day = date_add(now(),interval -1 day);
end if;

open rs_cursor; 
cursor_loop:loop
   FETCH rs_cursor into _userName, _chinese, _math; -- 取数据

   if done=1 then
    leave cursor_loop;
   end if;

-- 更新表
update infoSum set total=_chinese+_math where UserName=_userName;
end loop cursor_loop;
close rs_cursor;
    END$$
DELIMITER ;