zoukankan      html  css  js  c++  java
  • 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>(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 PROCEDURE p ()  
    2 BEGIN     
    3     /* Use comments! */    
    4     UPDATE t SET s1 = 5;     
    5     CREATE TRIGGER t2_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 FROM information_schema.tables;
     5     DECLARE EXIT HANDLER FOR NOT FOUND BEGIN END;    /* 1 */
     6     SET sql_mode='ansi';                             /* 2 */
     7     SET parameter1 = '';
     8     OPEN c;
     9     LOOP
    10         FETCH c INTO variable2;                        /* 3 */
    11         SET parameter1 = parameter1 || variable2 || '.';
    12     END LOOP;
    13     CLOSE c;
    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 @x4 /* 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 FUNCTION rno ()  
    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 BEGIN  
     3     DECLARE v INT;
     4     BEGIN  
     5         DECLARE EXIT HANDLER FOR SQLEXCEPTION, NOT FOUND      
     6           SET v = 0;  
     7         IF p_fk IS NOT NULL THEN  
     8             SELECT 1 INTO v FROM tpk WHERE cpk = p_fk LIMIT 1;
     9             INSERT INTO tfk VALUES (p_fk, p_animal);      
    10         ELSE  
    11             SET v = 1;      
    12         END IF;
    13     END;  
    14     IF v <> 1 THEN  
    15         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 PROCEDURE procedure1 ()  
     2 BEGIN  
     3     CALL procedure2();
     4     SET @x = 1;  
     5 END;   
     6 CREATE PROCEDURE procedure2 ()  
     7 BEGIN  
     8     CALL procedure3();
     9     SET @x = 2;  
    10 END;   
    11 CREATE PROCEDURE procedure3 ()    
    12 BEGIN  
    13     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 PROCEDURE add_book  
     2 (p_book_id INT, p_book_title VARCHAR(100))  
     3     SQL SECURITY DEFINER  
     4 BEGIN  
     5     IF p_book_id < 0 OR p_book_title='' THEN
     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 PROCEDURE add_patron  
     2 (p_patron_id INT, p_patron_name VARCHAR(100))  
     3     SQL SECURITY DEFINER  
     4 BEGIN  
     5     DECLARE v INT DEFAULT 0;  
     6     SELECT COUNT(*FROM patrons INTO v;
     7     IF v > 2 THEN  
     8         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 DEFINER  
     3 BEGIN 
     4     DECLARE v_patron_id, v_book_id INT; 
     5     DECLARE no_more BOOLEAN default FALSE; 
     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             OPEN c1;
    10             SET v_patron_id=NULL;
    11             FETCH c1 INTO v_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             CLOSE c1;
    16         END;
    17         BEGIN
    18             DECLARE c2 CURSOR FOR SELECT book_id FROM transactions WHERE patron_id = p_patron_id;
    19             OPEN c2;
    20             book_loop: LOOP
    21                 FETCH c2 INTO v_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             END LOOP;
    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:BEGIN  
     3     DECLARE temporary_table_exists BOOLEAN;
     4         BEGIN  
     5             DECLARE CONTINUE HANDLER FOR SQLEXCEPTION 
     6             BEGIN
     7             END;
     8             DROP TABLE IF EXISTS Temporary_Table;
     9         END;
    10         BEGIN  
    11             DECLARE v_person_id, v_father_id INT;
    12             DECLARE v_person_name CHAR(20);
    13             DECLARE done, error BOOLEAN DEFAULT FALSE;
    14             DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    15             DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
    16                 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 THEN  
    26             SELECT 'First SELECT failed';
    27             LEAVE proc;
    28         END IF;
    29         IF v_person_id IS NOT NULL THEN  
    30             INSERT INTO Temporary_Table VALUES (v_person_id, v_person_name, v_father_id);
    31         IF error THEN  
    32             SELECT 'First INSERT failed'; LEAVE procEND IF;
    33             CALL hierarchy2(v_person_id);
    34             IF error THEN  
    35                 SELECT 'First CALL hierarchy2() failed';
    36             END IF;
    37         END IF38         SELECT person_id, person_name, father_id FROM Temporary_Table;
    39         IF error THEN  
    40             SELECT 'Temporary SELECT failed'41             LEAVE proc42         END IF;
    43     END;  
    44     IF temporary_table_exists THEN  
    45         DROP TEMPORARY TABLE Temporary_Table;
    46     END IF;  
    47 END 1 CREATE PROCEDURE hierarchy2 (start_with INT)  
     2 proc:BEGIN  
     3     DECLARE v_person_id INT, v_father_id INT;
     4     DECLARE v_person_name CHAR(20);
     5     DECLARE done, error BOOLEAN DEFAULT FALSE;
     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     OPEN c;
    10     IF error THEN
    11         SELECT 'OPEN failed'12         LEAVE proc13     END IF;
    14     REPEAT  
    15         SET v_person_id=NULL;  
    16         FETCH c INTO v_person_id, v_person_name, v_father_id;
    17         IF error THEN
    18             SELECT 'FETCH failed'19             LEAVE proc20         END IF;      
    IF done=FALSE THEN   21        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 = TRUE 31 END REPEAT; 32 CLOSE c; 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 */
  • 相关阅读:
    快速排序法
    ios随机数
    ios简单更改系统TabBar的高度
    ios电话拨打进行监听电话状态
    iosUISegmentedControl的基本设置
    ios使用xcode进行Archive打包上传出现的常见错误
    ios实现文字的自适应
    ios 给view添加一个渐变的背景色
    iOSNSDate的相关操作
    ios导航栏又按钮添加图片后使其保持原色
  • 原文地址:https://www.cnblogs.com/free-coder/p/4779841.html
Copyright © 2011-2022 走看看