首页 > 数据库 > > 13.6 复合语句语法
参考官方文档:
https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-compound-statements.html
本节介绍BEGIN ... END复合语句的语法以及可用于存储程序主体的其他语句:存储过程和函数,触发器和事件。 这些对象是根据存储在服务器上供以后调用的SQL代码定义的(请参见第23章,存储程序和视图)。
复合语句是一个可以包含其他块的块; 变量声明,条件处理程序和游标; 和流程控制结构,如循环和条件测试。
13.6.1 BEGIN .... END 复合语句语法
[begin_label:]
BEGIN
[statement_list]
END [end_label]
BEGIN ... END语法用于编写复合语句,它可以出现在存储程序(存储过程和函数,触发器和事件)中。 复合语句可以包含多个由BEGIN和END关键字包围的语句。 statement_list表示一个或多个语句的列表,每个语句以分号(;)语句分隔符结尾。 statement_list本身是可选的,所以空的复合语句(BEGIN END)是合法的。
BEGIN ... END 块 可以被嵌套。
使用多个语句要求客户端能够发送包含该语句的语句字符串; 语句分隔符。 在mysql命令行客户端中,这是通过delimiter命令处理的。 改变; 语句结尾分隔符(例如,//)允许; 号用于程序主体。
不支持可选的[NOT] ATOMIC子句。 这意味着在指令块的开始处没有设置事务性保存点,并且在此上下文中使用的BEGIN子句对当前事务没有影响。
注意:
在所有存储的程序中,解析器将BEGIN [WORK]视为BEGIN ... END块的开始。 要在此上下文中开始事务,请改用START TRANSACTION。
13.6.2 Label 语句语法
[begin_label:] BEGIN
[statement_list]
END [end_label]
[begin_label:] LOOP
statement_list
END LOOP [end_label]
[begin_label:] REPEAT statement_list
UNTIL search_condition END REPEAT [end_label]
[begin_label:] WHILE search_condition DO
statement_list
END WHILE [end_label]
BEGIN ... END块以及LOOP,REPEAT和WHILE语句允许使用标签。 这些语句的标签使用遵循以下规则:
要在标签结构中引用标签,请使用ITERATE或LEAVE语句。 以下示例使用这些语句继续迭代或终止循环:
CREATE PROCEDURE doiterate(p1 INT)
BEGIN
label1: LOOP SET
p1 = p1 + 1;
IF p1 < 10 THEN ITERATE label1;
END IF;
LEAVE label1;
END LOOP label1;
END;
块标签的范围不包括块内声明的处理程序的代码。
13.6.3 DECLARE 语法
DECLARE语句用于定义程序本地的各种项目:
只有在BEGIN ... END复合语句中才允许DECLARE,并且必须处于在任何其他语句开始之前。
声明必须遵循特定的顺序。 游标声明必须出现在处理程序声明之前。 变量和条件声明必须出现在游标或处理程序声明之前。
13.6.4 在存储过程中的变量
系统变量和用户定义的变量可以在存储程序中使用,就像它们可以在存储程序上下文中使用一样。 另外,存储的程序可以使用DECLARE来定义局部变量,并且可以声明存储的例程(过程和函数)以获取在例程与其调用者之间传递值的参数。
不允许将值DEFAULT分配给存储过程或函数参数或存储的程序局部变量(例如,使用SET var_name = DEFAULT语句)。 在MySQL 5.7中,这会导致语法错误。
13.6.4.1 本地变量 DECLARE 语法
DECLARE var_name [, var_name] ... type [DEFAULT value]
该语句在存储的程序中声明局部变量。 要为变量提供默认值,请包含DEFAULT子句。 该值可以被指定为一个表达式; 它不一定是常数。 如果缺少DEFAULT子句,则初始值为NULL。
就数据类型和溢出检查而言,局部变量被视为存储的例程参数
变量声明必须出现在游标或处理程序声明之前。
局部变量名称不区分大小写。 允许的字符和引用规则与其他标识符相同,如第9.2节“模式对象名称”中所述。
局部变量的作用域是声明它的BEGIN ... END块。 变量可以在嵌套在声明块中的块中引用,除了声明了具有相同名称的变量的块之外(内部变量覆盖外部局部变量)。
13.6.4.2 局部变量范围和解决方案
局部变量的作用域是声明它的BEGIN ... END块。 变量可以在嵌套在声明块中引用,除了声明具有相同名称的变量的块之外。
因为局部变量仅在存储程序执行期间处于作用域中,所以在存储程序中创建的预处理语句中不允许对它们进行引用。 准备好的语句范围是当前会话,而不是存储的程序,因此语句可以在程序结束后执行,此时变量将不再处于范围内。 例如,SELECT ... INTO local_var不能用作预准备语句。 这个限制也适用于存储过程和函数参数。
一个局部变量不应该与表列具有相同的名称。 如果一个SQL语句(如SELECT ... INTO语句)包含对同名的列和声明局部变量的引用,那么MySQL当前将该引用解释为变量的名称。 考虑下面的过程定义:
CREATE PROCEDURE sp1 (x VARCHAR(5))
BEGIN DECLARE xname VARCHAR(5) DEFAULT 'bob';
DECLARE newname VARCHAR(5);
DECLARE xid INT;
SELECT xname, id INTO newname, xid FROM table1
WHERE xname = xname;
SELECT newname;
END;
MySQL将SELECT语句中的xname解释为对xname变量的引用,而不是xname列。 因此,在调用过程sp1()时,无论table1.xname列的值如何,newname变量都会返回值“bob”。
同样,以下过程中的游标定义包含引用xname的SELECT语句。 MySQL将其解释为对该名称的变量的引用,而不是列引用。
CREATE PROCEDURE sp2 (x VARCHAR(5))
BEGIN DECLARE xname VARCHAR(5) DEFAULT 'bob';
DECLARE newname VARCHAR(5);
DECLARE xid INT;
DECLARE done TINYINT DEFAULT 0;
DECLARE cur1 CURSOR FOR SELECT xname, id FROM table1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN cur1; read_loop: LOOP
FETCH FROM cur1 INTO newname, xid;
IF done THEN LEAVE read_loop;
END IF;
SELECT newname;
END LOOP;
CLOSE cur1;
END;
13.6.5 流控制语句
MySQL支持用于存储程序中流量控制的IF,CASE,ITERATE,LEAVE LOOP,WHILE和REPEAT结构。 它还支持存储函数中的RETURN。
这些结构中的很多都包含其他语句,如以下各节中的语法规范所示。 这样的构造可以嵌套。 例如,一个IF语句可能包含一个WHILE循环,它本身包含一个CASE语句。
MySQL不支持FOR循环。
13.6.5.1 CASE 语法
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list]
...
[ELSE statement_list]
END CASE
CASE
WHEN search_condition
THEN statement_list
[WHEN search_condition THEN statement_list]
... [ELSE statement_list]
END CASE
存储程序的CASE语句实现了一个复杂的条件构造。
注意:
还有一个CASE表达式,它不同于这里描述的CASE语句。 参见第12.4节“控制流量功能”。 CASE语句不能有ELSE NULL子句,并且它以END CASE而不是END结束。
对于第一种语法,case_value是一个表达式。 将该值与每个WHEN子句中的when_value表达式进行比较,直到其中一个相等。 如果找到相等的when_value,则执行相应的THEN子句statement_list。 如果没有when_value相等,则执行ELSE子句statement_list(如果有)。
此语法不能用于测试与NULL的相等性,因为NULL = NULL为假。
对于第二种语法,每个WHEN子句的search_condition表达式将被计算,直到其中一个为真,此时其相应的THEN子句statement_list将执行。 如果没有search_condition为真,则执行ELSE子句statement_list(如果有)。
如果没有when_value或search_condition与测试值相匹配,并且CASE语句不包含ELSE子句,则出现错误 Case not found for CASE statement。
每个statement_list由一个或多个SQL语句组成; 一个空的statement_list是不允许的。
要处理没有任何WHEN子句匹配任何值的情况,请使用包含空BEGIN ... END块的ELSE,如本例所示。
DELIMITER |
CREATE PROCEDURE p()
BEGIN DECLARE v INT DEFAULT 1;
CASE v
WHEN 2 THEN SELECT v;
WHEN 3 THEN SELECT 0;
ELSE
BEGIN
END;
END CASE;
END;
|
13.6.5.2 IF 语法
IF search_condition THEN statement_list
[ELSEIF search_condition THEN statement_list] ...
[ELSE statement_list]
END IF
存储程序的IF语句实现了一个基本的条件构造。
注意:
还有一个IF()函数,它不同于这里描述的IF语句。 IF语句可以有THEN,ELSE和ELSEIF子句,并以END IF结束
如果search_condition的计算结果为true,则执行相应的THEN或ELSEIF子句statement_list。 如果没有search_condition匹配,则执行ELSE子句statement_list。
每个statement_list由一个或多个SQL语句组成; 一个空的statement_list是不允许的。
IF ... END IF块与存储程序中使用的所有其他流控块一样,必须以分号结尾,如下例所示:
DELIMITER //
CREATE FUNCTION SimpleCompare(n INT, m INT) RETURNS VARCHAR(20) BEGIN DECLARE s VARCHAR(20);
IF n > m THEN SET s = '>';
ELSEIF n = m THEN SET s = '=';
ELSE SET s = '<';
END IF;
SET s = CONCAT(n, ' ', s, ' ', m);
RETURN s;
END //
DELIMITER ;
与其他流程控制结构一样,IF ... END IF块可嵌套在其他流程控制结构中,包括其他IF语句。 每个IF必须以自己的END IF结尾,后跟分号。 您可以使用缩进来使嵌套流控块更容易被人读取(尽管这不是MySQL所要求的),如下所示:
DELIMITER //
CREATE FUNCTION VerboseCompare (n INT, m INT) RETURNS VARCHAR(50) BEGIN
DECLARE s VARCHAR(50);
IF n = m THEN SET s = 'equals';
ELSE
IF n > m THEN SET s = 'greater';
ELSE SET s = 'less';
END IF;
SET s = CONCAT('is ', s, ' than');
END IF;
SET s = CONCAT(n, ' ', s, ' ', m, '.');
RETURN s;
END //
DELIMITER ;
在这个例子中,只有在n不等于m时才评估内部IF。
13.6.5.3 ITERATE 语法
ITERATE label
ITERATE只能出现在LOOP,REPEAT和WHILE语句中。 ITERATE的意思是“再次启动循环”。
13.6.5.4 LEAVE 语法
LEAVE label
此语句用于退出具有给定标签的流控制结构。 如果标签用于最外面存储的程序块,则LEAVE退出程序。
LEAVE可以在BEGIN ... END或循环结构(LOOP,REPEAT,WHILE)中使用。
13.6.5.5 LOOP 语法
[begin_label:] LOOP
statement_list
END LOOP [end_label]
LOOP实现了一个简单的循环结构,可以重复执行语句列表,它由一个或多个语句组成,每个语句由一个分号(;)语句分隔符终止。 重复循环内的语句,直到循环结束。 通常,这是通过LEAVE语句完成的。 在存储的函数中,也可以使用RETURN,它完全退出函数。
忽略包含循环终止语句会导致无限循环。
例子:
CREATE PROCEDURE doiterate(p1 INT)
BEGIN
label1: LOOP
SET p1 = p1 + 1;
IF p1 < 10 THEN
ITERATE label1;
END IF;
LEAVE label1;
END LOOP label1;
SET @x = p1;
END;
13.6.5.6 REPEAT 语句
[begin_label:] REPEAT
statement_list
UNTIL search_condition
END REPEAT [end_label]
重复REPEAT语句中的语句列表,直到search_condition表达式为真。 因此,REPEAT总是至少进入一次循环。 statement_list由一个或多个语句组成,每个语句由分号(;)语句分隔符终止。
REPEAT语句可以被标记。
例子:
mysql> delimiter //
mysql> CREATE PROCEDURE dorepeat(p1 INT)
-> BEGIN
-> SET @x = 0;
-> REPEAT
-> SET @x = @x + 1;
-> UNTIL @x > p1 END REPEAT;
-> END
-> // Query OK, 0 rows affected (0.00 sec)
mysql> CALL dorepeat(1000)// Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @x//
+------+
| @x |
+------+
| 1001 |
+------+
1 row in set (0.00 sec)
13.6.5.7 RETURN 语法
RETURN expr
RETURN语句终止存储函数的执行并将值expr返回给函数调用者。 存储函数中必须至少有一个RETURN语句。 如果函数有多个出口点,可能会有多个。
此语句不用于存储过程,触发器或事件。 LEAVE语句可用于退出这些类型的存储程序。
13.6.5.8 WHILE 语法
[begin_label:] WHILE search_condition DO
statement_list
END WHILE [end_label]
只要search_condition表达式为真,WHILE语句中的语句列表就会重复。 statement_list由一个或多个SQL语句组成,每个SQL语句以分号(;)语句分隔符结尾。
WHILE语句可以被标记。
例子:
CREATE PROCEDURE dowhile()
BEGIN
DECLARE v1 INT DEFAULT 5;
WHILE v1 > 0 DO
...
SET v1 = v1 - 1;
END WHILE;
END;
13.6.6 游标
MySQL支持存储程序中的游标。 语法与嵌入式SQL中的一样。 游标具有以下属性:
游标声明必须出现在处理程序声明之前以及变量和条件声明之后。
例子:
CREATE PROCEDURE curdemo()
BEGIN DECLARE done INT DEFAULT FALSE;
DECLARE a CHAR(16);
DECLARE b, c INT;
DECLARE cur1 CURSOR FOR SELECT id,data FROM test.t1;
DECLARE cur2 CURSOR FOR SELECT i FROM test.t2;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
OPEN cur2;
read_loop: LOOP
FETCH cur1 INTO a, b;
FETCH cur2 INTO c;
IF done THEN
LEAVE read_loop;
END IF;
IF b < c THEN
INSERT INTO test.t3 VALUES (a,b);
ELSE
INSERT INTO test.t3 VALUES (a,c);
END IF;
END LOOP;
CLOSE cur1;
CLOSE cur2;
END;
13.6.6.1 cursor CLOSE 语法
CLOSE cursor_name
该语句关闭以前打开的光标。
如果光标未打开,则会发生错误。
如果未明确关闭,则会在声明它的BEGIN ... END块的末尾关闭游标。
13.6.6.2 游标 DECLARE 语法
DECLARE cursor_name CURSOR FOR select_statement
该语句声明一个游标并将其与一个SELECT语句相关联,该语句检索要由游标遍历行。 要稍后获取行,请使用FETCH语句。 SELECT语句检索的列数必须与FETCH语句中指定的输出变量数相匹配。
SELECT语句不能有INTO子句。
游标声明必须出现在处理程序声明之前以及变量和条件声明之后。
存储的程序可能包含多个游标声明,但在给定块中声明的每个游标必须具有唯一的名称。
有关通过SHOW语句获得的信息,在很多情况下可以通过使用具有INFORMATION_SCHEMA表的来获得游标等效信息。
13.6.6.3 Cursor FETCH 语句
FETCH [[NEXT] FROM] cursor_name INTO var_name [, var_name] ...
该语句为与指定游标(必须打开)关联的SELECT语句提取下一行,并推进游标指针。 如果存在一行,则提取的列将存储在命名变量中。 SELECT语句检索的列数必须与FETCH语句中指定的输出变量数相匹配。
如果没有更多行可用,则SQLSTATE值为'02000'时会发生无数据条件。 为了检测这种情况,你可以为它设置一个处理程序(或者对于NOT FOUND条件)。
请注意,另一个操作(如SELECT或另一个FETCH)也可能会导致处理程序通过抛出相同条件来执行。 如果有必要区分哪种操作引发了这种情况,请将操作放在其自己的BEGIN ... END块中,以便它可以与自己的处理程序关联。
13.6.6.4 游标OPEN 语法
OPEN cursor_name
这个语句打开一个先前声明的游标。
13.6.7 条件处理
存储程序执行期间可能会出现需要特殊处理的条件,例如退出当前程序块或继续执行。 处理程序可以定义为一般条件,如警告或异常,或针对特定条件(如特定的错误代码)。 特定的条件可以分配名称并在相关的处理程序中以特有方式处理。
要命名一个条件,请使用DECLARE ... CONDITION语句。 要声明处理程序,请使用DECLARE ... HANDLER语句。
要从诊断区域检索信息,请使用GET DIAGNOSTICS语句(请参见第13.6.7.3节“GET DIAGNOSTICS Syntax”)。
13.6.7.1 DECLARE ... CONDITION 语法
DECLARE condition_name CONDITION FOR
condition_value condition_value:
mysql_error_code | SQLSTATE [VALUE] sqlstate_value
DECLARE ... CONDITION语句声明了一个命名错误条件,将名称与需要特定处理的条件相关联。 该名称可以在随后的DECLARE ... HANDLER语句中引用(请参见第13.6.7.2节“DECLARE ... HANDLER语法”)。
条件声明必须出现在游标或处理程序声明之前。
DECLARE ... CONDITION的condition_value指示与条件名称关联的特定条件或条件类别。 它可以采取以下形式:
不要使用MySQL错误代码0,因为它表示成功,而不是错误条件。 有关MySQL错误代码的列表,请参见第B.3节“服务器错误代码和消息”。
不要使用以'00'开头的SQLSTATE值,因为这些值指示成功而不是错误条件。 有关SQLSTATE值的列表,请参见第B.3节“服务器错误代码和消息”。
在SIGNAL或使用RESIGNAL语句中引用的条件名称必须与SQLSTATE值关联,而不是MySQL错误代码。
使用条件名称可以帮助使存储的程序代码更加清晰。 例如,此处理程序适用于尝试删除不存在的表,但只有在您知道1051是“未知表”的MySQL错误代码时才会显示该处理程序:
DECLARE CONTINUE HANDLER FOR 1051
BEGIN
-- body of handler
END;
通过为条件声明一个名称,更容易看到处理程序的目的:
DECLARE no_such_table CONDITION FOR 1051;
DECLARE CONTINUE HANDLER FOR no_such_table
BEGIN
-- body of handler
END;
以下是针对相同条件的命名条件,但是基于相应的SQLSTATE值而不是MySQL错误代码:
DECLARE no_such_table CONDITION FOR SQLSTATE '42S02';
DECLARE CONTINUE HANDLER FOR no_such_table
BEGIN
-- body of handler
END;
13.6.7.2 DECLARE ... HANDLER语法
DECLARE handler_action
HANDLER FOR condition_value [, condition_value] ...
statement
handler_action:
CONTINUE
| EXIT
| UNDO
condition_value: mysql_error_code
| SQLSTATE [VALUE] sqlstate_value
| condition_name
| SQLWARNING
|NOT FOUND
| SQLEXCEPTION
DECLARE ... HANDLER语句指定处理一个或多个条件的处理程序。 如果出现这些情况之一,则执行指定的语句。 语句可以是一个简单的语句,如SET var_name = value或使用BEGIN和END写入的复合语句。
处理程序声明必须出现在变量或条件声明之后。
handler_action值指示处理程序语句执行后处理程序执行的操作:
DECLARE ... HANDLER的condition_value指示激活处理程序的特定条件或条件类。 它可以采取以下形式:
DECLARE CONTINUE HANDLER FOR 1051
BEGIN -- body of handler
END;
不要使用MySQL错误代码0,因为它表示成功,而不是错误条件。
DECLARE CONTINUE HANDLER FOR SQLSTATE '42S02'
BEGIN
-- body of handler
END;
不要使用以'00'开头的SQLSTATE值,因为这些值指示成功而不是错误条件。
DECLARE CONTINUE HANDLER FOR SQLWARNING
BEGIN
-- body of handler
END;
以'02'开头的SQLSTATE类的简写。 这在游标的上下文中是相关的,用于控制当游标到达数据集的末尾时发生的情况。 如果没有更多行可用,则SQLSTATE值为'02000'时会发生无数据条件。 为了检测这种情况,你可以为它设置一个处理程序或者为一个NOT FOUND条件。
DECLARE CONTINUE HANDLER FOR NOT FOUND
BEGIN
-- body of handler
END;
对于SELECT ... INTO不检索行的var_list语句也会发生NOT FOUND条件。
对于不以'00','01'或'02'开头的SQLSTATE类的简写。
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
BEGIN
-- body of handler
END;
如果发生没有声明处理程序的情况,则采取的操作取决于条件类:
以下示例使用SQLSTATE'23000'的处理程序,该处理程序发生重复键错误:
mysql> CREATE TABLE test.t (s1 INT, PRIMARY KEY (s1)); Query OK, 0 rows affected (0.00 sec)
mysql> delimiter //
mysql> CREATE PROCEDURE handlerdemo ()
-> BEGIN
-> DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1;
-> SET @x = 1;
-> INSERT INTO test.t VALUES (1);
-> SET @x = 2;
-> INSERT INTO test.t VALUES (1);
-> SET @x = 3;
-> END; -> //
Query OK, 0 rows affected (0.00 sec)
mysql> CALL handlerdemo()// Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @x//
+------+
| @x |
+------+
| 3 |
+------+
1 row in set (0.00 sec)
注意@x在执行该过程后为3,这表示在错误发生后继续执行过程的结束。 如果DECLARE ... HANDLER语句不存在,则由于PRIMARY KEY约束,MySQL在第二次INSERT失败后会采取默认操作(EXIT),并且SELECT @x将返回2。
要忽略一个条件,为它声明一个CONTINUE处理程序并将其与一个空白块相关联。 例如:
DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN END;
块标签的范围不包括块内声明的处理程序的代码。 因此,与处理程序关联的语句不能使用ITERATE或LEAVE来引用包含处理程序声明的块的标签。 考虑以下示例,其中REPEAT块具有retry标签:
CREATE PROCEDURE p ()
BEGIN
DECLARE i INT DEFAULT 3;
retry:
REPEAT
BEGIN
DECLARE CONTINUE HANDLER FOR SQLWARNING
BEGIN
ITERATE retry; # illegal
END;
IF i < 0 THEN LEAVE retry; # legal
END IF;
SET i = i - 1;
END;
UNTIL FALSE END REPEAT;
END;
retry标签位于该块内的IF语句的范围内。 它不在CONTINUE处理程序的范围内,因此引用无效并导致错误:
ERROR 1308 (42000): LEAVE with no matching label: retry
要避免在处理程序中引用外部标签,请使用以下策略之一:
DECLARE EXIT HANDLER FOR SQLWARNING BEGIN END;
否则,将清理语句放在处理程序体中:
DECLARE EXIT HANDLER FOR SQLWARNING
BEGIN
block cleanup statements
END;
要继续执行,请在CONTINUE处理程序中设置状态变量,该处理程序可以在封闭块中检查以确定是否调用处理程序。 以下示例使用为此完成的变量:
CREATE PROCEDURE p ()
BEGIN
DECLARE i INT DEFAULT 3;
DECLARE done INT DEFAULT FALSE;
retry:
REPEAT
BEGIN
DECLARE CONTINUE HANDLER FOR SQLWARNING BEGIN
SET done = TRUE;
END;
IF done OR i < 0 THEN
LEAVE retry;
END IF;
SET i = i - 1;
END; U
NTIL FALSE END REPEAT;
END;
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69946831/viewspace-3159316/ ,如需转载,请注明出处,否则将追究法律责任。