时间:2021-07-01 10:21:17 帮助过:3人阅读
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 @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 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 proc; END IF; 33 CALL hierarchy2(v_person_id); 34 IF error THEN 35 SELECT ‘First CALL hierarchy2() failed‘; 36 END IF; 37 END IF; 38 SELECT person_id, person_name, father_id FROM Temporary_Table; 39 IF error THEN 40 SELECT ‘Temporary SELECT failed‘; 41 LEAVE proc; 42 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 proc; 13 END IF; 14 REPEAT 15 &nbs