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 */
  • 相关阅读:
    【Educational Codeforces Round 101 (Rated for Div. 2) C】Building a Fence
    【Codeforces Round #698 (Div. 2) C】Nezzar and Symmetric Array
    【Codeforces Round #696 (Div. 2) D】Cleaning
    【Codeforces Round #696 (Div. 2) C】Array Destruction
    【Educational Codeforces Round 102 D】Program
    【Educational Codeforces Round 102 C】No More Inversions
    【Good Bye 2020 G】Song of the Sirens
    【Good Bye 2020 F】Euclid's nightmare
    使用mobx入门
    requestAnimationFrame 控制速度模拟setinterval
  • 原文地址:https://www.cnblogs.com/free-coder/p/4779841.html
Copyright © 2011-2022 走看看