mysql 存储过程 with_MySQL存储过程之细节

1. ALTER and DROP

1 ALTER PROCEDURE p6 COMMENT 'Unfinished' //

2 DROP PROCEDURE p6 //

2. 与Oracle / SQL Server / DB2 / ANSI比较

1) 与Oracle比较

1> 摘要:

a. Oracle允许在打开后再声明;MySQL必须在开始的时候使用

b. Oracle允许"CURSOR cursorname IS"这样的声明方式;MySQL必须使用"DECLARE cursorname CURSOR"声明.

c. Oracle不强制需要"()";MySQL必须有"()";

d. Oracle允许在函数中访问表元素;MySQL不允许.

e. Oracle支持"packages";MySQL不支持.

2> 数据迁移的技巧

把a:=b类似的赋值语句改成SET a=b

将过程中的RETURN语句改为LEAVE (label_name是为过程体定义的标号),如:

1 /*在Oracle存储过程中*/

2 CREATE PROCEDURE

3 ...4 RETURN;5 ...6 /*在MySQL存储过程中*/

7 CREATE PROCEDURE()8 label_at_start: BEGIN

9 ...10 LEAVE label_at_start;11 END

这一步仅在过程中需要,因为函数支持RETURN.

3> 平行比较

Oracle

MySQL

CREATE PROCEDURE

CREATE PROCEDURE

sp_name

sp_name

AS

BEGIN

variable1 INTEGER

DECLARE variable1 INTEGER

variable1 :=55

SET variable1 = 55;

END

END

2) 与SQL Server对比

1> 摘要:

a. SQL Server参数名字必须以@开关;MySQL参数名是常规标识符.

b. SQL Server可以同时进行多个声明 DECLARE v1 [data type], v2 [data type]; ;MySQL每次只能声明一个 DECLARE v1 [data type]; DECLARE v2 [data type] .

c. SQL Server存储过程体中没有BEGIN/END;MySQL必须有

d. SQL Server中语句不需要分号结束;MySQL中除最后一句外必须有分号

e. SQL Server可以进行SET NOCOUNT设置和IF @@ROWCOUNT判断;MySQL没有,但可以使用FOUND_ROWS()判断.

f. SQL Server中循环使用WHILE...BEGIN语句;MySQL使用WHILE...DO语句

g. SQL Server允许使用SELECT进行指派;MySQL只允许SET进行指派

h. SQL Server允许在函数中访问表;MySQL不允许

Microsoft SQL Server的区别特别多,所以将Microsoft或Sybase程序转换成MySQL程序将会是个冗长的过程,而且区别都是在语法定义上的,所以转换需要更多的特别的技巧.

2>迁移技巧

如果SQL Server的过程中有名为@xxx的变量,必须将其转换,因为@在MySQL中并不代表过程变量,而是全局变量;并且不要简单改成xxx,这可能会和数据库中某个表的某个字段冲突.因此可以把@替换成你的自定义字符串前缀,如var_xxx.

3> 平行对比

SQL Server

MySQL

CREATE PROCEDURE

CREATE PROCEDURE

sp_procedure1

sp_procedure1()

AS

BEGIN

DECLARE @x VARCHAR(100)

DECLARE var_x VARCHAR(100);

EXECUTE sp_procedure2 @x

CALL sp_procedure2(var_x);

DECLARE c CURSOR FOR

DECLARE c CURSOR FOR

SELECT * FROM t

SELECT * FROM t;

END

END

3) 与DB2比较

1>摘要

a. DB2允许PATH和SIGAL语句并允许函数访问表;MySQL不允许

b. DB2允许过程名重载;MySQL不允许

c. DB2有"label_x:...GOTO label_x"语法;MySQL有非正式的"label_x:...GOTO label_x"语法

DB2存储过程基本和MySQL一致,唯一的不同在于MySQL还没有引进DB2的一些语句,还有就是DB2允许重载,因此DB2可以有两个名字一样的存储过程,只通过参数或返回类型来决定执行哪个所以DB2存储过程可以向下与MySQL的兼容.

2> 迁移技巧

此处迁移基本不需要任何技巧,MySQL没有SIGNAL语句,可以在其他地方讨论临时工作区的问题,而对DB2的GOTO语句,可直接用MySQL中的GOTO语句直接代替.PATH(用于确定DBMS寻找过程的数据库目录)问题只需要在过程名前加上前缀就可以避免,关于函数访问表的问题可用带OUT参数的存储过程代替.

3>平行对比

DB2

MySQL

CREATE PROCEDURE

CREATE PROCEDURE

sp_name

sp_name

(parameter1 INTEGER)

(parameter1 INTEGER)

LANGUAGE SQL

LANGUAGE SQL

BEGIN

BEGIN

DECLARE v INTEGER;

DECLARE v INTEGER;

IF parameter1 >= 5 THEN

IF parameter1 >= 5 THEN

CALL p26();

CALL p26();

SET v = 2;

SET v = 2;

END IF;

END IF;

INSERT INTO t VALUES ( v );

INSERT INTO t VALUES ( v );

END @

END //

4) 与sql标准的比较

标准sql的要求:(跟DB2中的一样)

MySQL的目标是支持以下两个标准SQL特性:存储模式和计算完整性.DB2与MySQL相似的原因是两者都支持标准SQL中的存储过程.因此,MySQL和DB2的区别就像我们背离ANSI/ISO标准语法那样,但比Oracle或SQL Server更标准.

3. 编程风格

1 CREATE PROCEDUREp ()2 BEGIN

3 /*Use comments!*/

4 UPDATE t SET s1 = 5;5 CREATE TRIGGERt2_ai ...6 END;//

上例中的编程风格:关键字大写,命名约定中,表名为t,列名为s...

注释和C语言中的一样,在BEGIN后缩进(一般是一个TAB字符);在END前回缩

4. 几个例子

1)字符串连接函数 -- tables_concat():

1 CREATE PROCEDURE tables_concat (OUT parameter1 VARCHAR(1000))2 BEGIN

3 DECLARE variable2 CHAR(100);4 DECLARE c CURSOR FOR SELECT table_name FROMinformation_schema.tables;5 DECLARE EXIT HANDLER FOR NOT FOUND BEGIN END; /*1*/

6 SET sql_mode='ansi'; /*2*/

7 SET parameter1 = '';8 OPENc;9 LOOP10 FETCH c INTO variable2; /*3*/

11 SET parameter1 = parameter1 || variable2 || '.';12 ENDLOOP;13 CLOSEc;14 END;15 /*1: 这里的"BEGIN END"语句没有任何作用,就像其他DBMS中的NULL语句。*/

16 /*2: 将sql_mode设置为'ansi'以便"||"能正常连接,在退出存储过程后sql_mode仍为'ansi'。*/

17 /*3: 另一种跳出循环LOOP的方法:声明EXIT出错处理,当FETCH没有返回行时。*/

这是所有表名连接到一个单一字符串的函数,可以和MySQL内建的group_concat()函数对比一下.以下是我调用该过程的示例和结果:

1 mysql> CALL tables_concat(@x);2 /*Query OK, 0 rows affected (0.05 sec)*/

3 mysql> SELECT @x;4 /*SCHEMATA.TABLES.COLUMNS.CHARACTER_SETS.COLLATIONS.C OLLATION_CHARACTER_SET_APPLICABILITY.ROUTINES.STATIST ICS.VIEWS.USER_PRIVILEGES.SCHEMA_PRIVILEGES.TABLE_PRI VILEGES.COLUMN_PRIVILEGES.TABLE_CONSTRAINTS.KEY_COLUM N_USAGE.TABLE_NAMES.columns_priv.db.fn.func.help_cate gory.help_keyword.help_relation.help_topic.host.proc. tables_priv.time_zone.time_zone_leap_second.time_zone 1 row in set (0.00 sec)*/

下面示例获得符合条件行数,类似其他DBMS中的ROWNUM():

1 CREATE FUNCTIONrno ()2 RETURNS INT

3 BEGIN

4 SET @rno = @rno + 1;5 RETURN @rno;6 END;

使用示例如下:

1 mysql> SET @rno = 0;//

2 /*Query OK, 0 rows affected (0.00 sec)*/

3 mysql> SELECT rno(),s1,s2 FROM t;//

4 /+-------+------+------+

5 | rno() | s1 | s2 |

6 /+-------+------+------+

7 | 1 | 1 | a |

8 | 2 | 2 | b |

9 | 3 | 3 | c |

10 | 4 | 4 | d |

11 | 5 | 5 | e |

12 /+-------+------+------+

13 5 rows in set (0.00 sec)*/

2) running_total()

这个累加的函数建立在;rno()基础上,不同之处在于我们要在每次调用时传值到参数中:

1 CREATE FUNCTION running_total (IN adder INT)2 RETURNS INT

3 BEGIN

4 SET @running_total = @running_total +adder;5 RETURN @running_total;6 END;7 /*下面是函数调用级结果*/

8 mysql> SET @running_total = 0;//

9 /*Query OK, 0 rows affected (0.01 sec)*/

10 mysql> SELECT s1,running_total(s1),s2 FROM t ORDER BY s1;//11 /*

12 +------+-------------------+------+13 | s1   | running_total(s1) | s2   |14 +------+-------------------+------+15 |    1 |                 1 | a    |16 |    2 |                 3 | b    |17 |    3 |                 6 | c    |18 |    4 |                10 | d    |19 |    5 |                15 | e    |20 +------+-------------------+------+21 5 rows in set (0.01 sec)22 */

3) MyISAM外键插入

MyISAM存储引擎不支持外键,但是你可以将这个逻辑加入存储过程引擎进行检查:

1 CREATE PROCEDURE fk_insert (p_fk INT, p_animal VARCHAR(10))2 BEGIN3 DECLARE v INT;4     BEGIN5     DECLARE EXIT HANDLER FOR SQLEXCEPTION, NOTFOUND6    SET v = 0;7      IF p_fk IS NOT NULL THEN8       SELECT 1 INTO v FROM tpk WHERE cpk = p_fk LIMIT 1;9         INSERT INTO tfk VALUES(p_fk, p_animal);10 ELSE11       SET v = 1;12 END IF;13     END;14    IF v <> 1 THEN15      DROP TABLE`The insertion failed`;16     END IF;17 END;

注意:SQLEXCEPTION或NOT FOUND条件都会导致v变0,而如果这些条件为假,则v会变成1,因为SELECT会给v赋值1,而EXIT HANDLER没有运行.以下看看运行结果:

1 mysql> CREATE TABLE tpk (cpk INT PRIMARY KEY);//2 /*Query OK, 0 rows affected (0.01 sec)*/

3 mysql> CREATE TABLE tfk (cfk INT, canimal VARCHAR(10));//4 /*Query OK, 0 rows affected (0.00 sec)*/

5 mysql> INSERT INTO tpk VALUES (1),(7),(10);//6 /*Query OK, 3 rows affected (0.01 sec)  Records: 3  Duplicates: 0  Warnings: 0*/

7 mysql> CALL fk_insert(1,'wombat');//8 /*Query OK, 1 row affected (0.02 sec)*/

9 mysql> CALL fk_insert(NULL,'wallaby');//10 /*Query OK, 0 rows affected (0.00 sec)*/

11 mysql> CALL fk_insert(17,'wendigo');//12 /*ERROR 1051 (42S02): Unknown table 'The insertion failed'*/

4) 错误传递

如果过程1调用过程2,过程2调用过程3,过程3中的错误就会传递到过程1.如果没有异常处理器捕获异常,那异常就会传递,导致过程2出错,进而最后导致过程1出错,最终异常传递到了调用者(MySQL客户端实例).这种特性使得标准SQL中存在SIGNAL语句来使异常强制发生,其他DBMS中也有类似措施(RAISEERROR).MySQL还不支持SIGNAL,直到支持此特性出来之前,可以用下面的异常处理方式:

1 CREATE PROCEDUREprocedure1 ()2 BEGIN3 CALL procedure2();4     SET @x = 1;5 END;6 CREATE PROCEDUREprocedure2 ()7 BEGIN8 CALL procedure3();9     SET @x = 2;10 END;11 CREATE PROCEDUREprocedure3 ()12 BEGIN13   DROP TABLE error.`error #7815`;14     SET @x = 3;15 END;16 /*调用过程1后结果如下:*/

17 mysql> CALL procedure1()//18 /*ERROR 1051 (42S02): Unknown table 'error #7815'*/

@x并没有改变,因为没有一条"SET @x = ..."语句成功被执行,而使用DROP可以产生一些可供诊断的错误信息.

5) 库

对库的应用有详细的规格说明,为使拥有权限的用户都能调用过程,可以如下设置: GRANT ALL ON database-name.* TO user-name; 如果要其他用户只有访问过程的权限,只要定义SQL SECURITY DEFINER特性就可以了,而这个选项是默认的,但最好显式的声明出来.

下面是一个向数据库中添加书本的过程,这里必须测试书的id是否确定,书名是否为空.例子是对MySQL不支持的CHECK限制功能的替代.

1 CREATE PROCEDUREadd_book2 (p_book_id INT, p_book_title VARCHAR(100))3 SQL SECURITY DEFINER4 BEGIN5 IF p_book_id 

6      SELECT 'Warning:7 Bad parameters';8     END IF;9   INSERT INTO books VALUES(p_book_id, p_book_title);10 END;

我们需要一个添加买主的过程,过程必须检查是否有超过一个的买主,如有则给出警告.这个可以在一个子查询中完成:

IF (SELECT COUNT(*) FROM table-name) > 2) THEN ... END IF;

不过,目前子查询功能有漏洞,于是可用"SELECT COUNT(*) INTO variable-name"代替.

1 CREATE PROCEDUREadd_patron2 (p_patron_id INT, p_patron_name VARCHAR(100))3 SQL SECURITY DEFINER4 BEGIN5 DECLARE v INT DEFAULT 0;6    SELECT COUNT(*) FROM patrons INTOv;7     IF v > 2 THEN8     SELECT 'warning: already there are',v,'patrons!';9     END IF;10   INSERT INTO patrons VALUES(p_patron_id,p_patron_name);11 END;

下面需要书本付帐的过程,在事务处理过程中我们希望显示已经拥有本书的买主,及其拥有的书,这些信息可以通过对游标CURSOR的Fetch来获得,可以有两种不同的方法来测试是否fetch数据已完毕:检查变量在fetch动作后是否为NULL;通过NOT FOUND错误处理捕获fetch的失败动作.

1 CREATE PROCEDURE checkout (p_patron_id INT, p_book_id INT)2 SQL SECURITY DEFINER3 BEGIN

4 DECLARE v_patron_id, v_book_id INT;5 DECLARE no_more BOOLEAN defaultFALSE;6 DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more=TRUE;7 BEGIN

8 DECLARE c1 CURSOR FOR SELECT patron_id FROM transactions WHERE book_id =p_book_id;9 OPENc1;10 SET v_patron_id=NULL;11 FETCH c1 INTOv_patron_id;12 IF v_patron_id IS NOT NULL THEN

13 SELECT 'Book is already out to this patron:', v_patron_id;14 END IF;15 CLOSEc1;16 END;17 BEGIN

18 DECLARE c2 CURSOR FOR SELECT book_id FROM transactions WHERE patron_id =p_patron_id;19 OPENc2;20 book_loop: LOOP21 FETCH c2 INTOv_book_id;22 IF no_more THEN

23 LEAVE book_loop;24 END IF;25 SELECT 'Patron already has this book:', v_book_id;26 ENDLOOP;27 END;28 INSERT INTO transactions VALUES (p_patron_id, p_book_id); END;

6) 分层次

hierarchy()过程实现的是其他DBMS中CONNECT BY部分功能

1 CREATE PROCEDURE hierarchy (start_with CHAR(10))2 proc:BEGIN3    DECLAREtemporary_table_exists BOOLEAN;4     BEGIN5      DECLARE CONTINUE HANDLER FORSQLEXCEPTION6 BEGIN

7 END;8        DROP TABLE IF EXISTSTemporary_Table;9      END;10     BEGIN11      DECLARE v_person_id, v_father_id INT;12       DECLARE v_person_name CHAR(20);13 DECLARE done, error BOOLEAN DEFAULTFALSE;14 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done =TRUE;15        DECLARE CONTINUE HANDLER FORSQLEXCEPTION16        SET error =TRUE;17 18      CREATE TEMPORARY TABLE Temporary_Table (person_id INT, person_name CHAR(20), father_id INT);19       IF error THEN

20        SELECT 'CREATE TEMPORARY failed';21   LEAVE proc;22   END IF;23       SET temporary_table_exists=TRUE;24      SELECT person_id, person_name INTO v_person_id, v_person_name FROM Persons WHERE person_name = start_with limit 1;25       IF error THEN26         SELECT 'First SELECT failed';27   LEAVE proc;28   END IF;29        IF v_person_id IS NOT NULL THEN30        INSERT INTO Temporary_Table VALUES(v_person_id, v_person_name, v_father_id);31        IF error THEN32        SELECT 'First INSERT failed'; LEAVE proc; END IF;33 CALL hierarchy2(v_person_id);34          IF error THEN35         SELECT 'First CALL hierarchy2() failed';36   END IF;37        END IF;38      SELECT person_id, person_name, father_id FROMTemporary_Table;39     IF error THEN40     SELECT 'Temporary SELECT failed';41 LEAVE proc;42 END IF;43 END;44 IF temporary_table_exists THEN45    DROP TEMPORARY TABLETemporary_Table;46     END IF;47 END; 1 CREATE PROCEDURE hierarchy2 (start_with INT)

2 proc:BEGIN3   DECLARE v_person_id INT, v_father_id INT;4     DECLARE v_person_name CHAR(20);5  DECLARE done, error BOOLEAN DEFAULTFALSE;6     DECLARE c CURSOR FOR SELECT person_id, person_name, father_id FROM Persons WHERE father_id =start_with;7    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done =TRUE;8     DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET error =TRUE;9     OPENc;10    IF error THEN

11    SELECT 'OPEN failed';12 LEAVE proc;13 END IF;14 REPEAT15     SET v_person_id=NULL;16      FETCH c INTOv_person_id, v_person_name, v_father_id;17       IF error THEN

18     SELECT 'FETCH failed';19 LEAVE proc;20 END IF;

IF done=FALSE THEN21        INSERT INTO Temporary_Table VALUES(v_person_id, v_person_name, v_father_id);22          IF error THEN

23        SELECT 'INSERT in hierarchy2() failed';24 END IF;25 CALL hierarchy2(v_person_id);26        IF error THEN

27      SELECT 'Recursive CALL hierarchy2() failed';28 END IF;29      END IF;30      UNTIL done =TRUE31 ENDREPEAT;32 CLOSEc;33 IF error THEN

34 SELECT 'CLOSE failed';35 END IF;36 END;

下是调用hierarchy()后的结果:

1 mysql> CREATE TABLE Persons (person_id INT, person_name CHAR(20), father_id INT);//

2 /*Query OK, 0 rows affected (0.00 sec)*/

3 mysql> INSERT INTO Persons VALUES (1,'Grandpa',NULL);//

4 /*Query OK, 1 row affected (0.00 sec)*/

5 mysql> INSERT INTO Persons VALUES (2,'Pa-1',1),(3,'Pa-2',1);//

6 /*Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0*/

7 mysql> INSERT INTO Persons VALUES (4,'Grandson-1',2),(5,'Grandson-2',2);//

8 /*Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0*/

9 mysql> call hierarchy('Grandpa')//

10 /*

11 +-----------+-------------+-----------+12 | person_id | person_name | father_id |13 +-----------+-------------+-----------+14 | 1 | Grandpa | NULL |15 | 2 | Pa-1 | 1 |16 | 4 | Grandson-1 | 2 |17 | 5 | Grandson-2 | 2 |18 | 3 | Pa-2 | 1 |19 +-----------+-------------+-----------+20 5 rows in set (0.01 sec)21 Query OK, 0 rows affected (0.01 sec)22 */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值