MYSQL存储过程注释详解

1 MySQL存储过程简介及优缺点

1.1 存储过程的定义

存储过程是存储在数据库目录中的一段声明性SQL语句。
触发器,其他存储过程以及Java,Python,PHP等应用程序可以调用存储过程。

在这里插入图片描述
自身的存储过程称为递归存储过程。
大多数数据库管理系统支持递归存储过程。
但是,MySQL不支持它。
在MySQL中实现递归存储过程之前,您应该检查MySQL数据库的版本。

1.2 在 MySQL 中存储过程

MySQL是最受欢迎的开源RDBMS,被社区和企业广泛使用。

然而,在它发布的第一个十年期间,它不支持存储过程,存储函数,触发器和事件。
自从MySQL 5.0版本以来,这些功能被添加到MySQL数据库引擎,使其更加灵活和强大。

1.3 MySQL 存储过程的优点

通常存储过程有助于提高应用程序的性能。

当创建,存储过程被编译之后,就存储在数据库中。

但是,MySQL实现的存储过程略有不同。

MySQL存储过程按需编译。

在编译存储过程之后,MySQL将其放入缓存中。

MySQL为每个连接维护自己的存储过程高速缓存。

如果应用程序在单个连接中多次使用存储过程,则使用编译版本,否则存储过程的工作方式类似于查询。

存储过程有助于减少应用程序和数据库服务器之间的流量,因为应用程序不必发送多个冗长的SQL语句,而只能发送存储过程的名称和参数。

存储的程序对任何应用程序都是可重用的和透明的。

存储过程将数据库接口暴露给所有应用程序,以便开发人员不必开发存储过程中已支持的功能。

存储的程序是安全的。 数据库管理员可以向访问数据库中存储过程的应用程序授予适当的权限,而不向基础数据库表提供任何权限。

除了这些优点之外,存储过程有其自身的缺点,在数据库中使用它们之前,您应该注意这些缺点。

1.4 MySQL 存储过程的缺点

如果使用大量存储过程,那么使用这些存储过程的每个连接的内存使用量将会大大增加。

此外,如果您在存储过程中过度使用大量逻辑操作,则CPU使用率也会增加,因为数据库服务器的设计不等于逻辑运算。

存储过程的构造使得开发具有复杂业务逻辑的存储过程变得更加困难。

很难调试存储过程。只有少数数据库管理系统允许您调试存储过程。不幸的是,MySQL不提供调试存储过程的功能。

开发和维护存储过程并不容易。开发和维护存储过程通常需要一个不是所有应用程序开发人员拥有的专业技能。这可能会导致应用程序开发和维护阶段的问题。

MySQL存储过程有自己的优点和缺点。开发应用程序时,您应该决定是否应该或不应该根据业务需求使用存储过程。

2 MySQL 存储过程入门

我们将逐步介绍如何使用 CREATE PROCEDURE 语句开发第一个 MySQL 存储过程。
另外,我们将向您展示如何从SQL语句调用存储过程。

1.1 编写第一个MySQL存储过程

我们将开发一个名为 GetAllProducts() 的简单存储过程来帮助您熟悉创建存储过程的语法。

GetAllProducts() 存储过程从 products 表中选择所有产品。

启动 mysql 客户端工具并键入以下命令:

DELIMITER //
 CREATE PROCEDURE GetAllProducts()
   BEGIN
   SELECT *  FROM products;
   END //
DELIMITER ;

在这里插入图片描述

让我们来详细地说明上述存储过程:

第一个命令是 DELIMITER //,它与存储过程语法无关。

DELIMITER 语句将标准分隔符 - 分号 (;) 更改为://
在这种情况下,分隔符从分号 (;) 更改为双斜杠 //

为什么我们必须更改分隔符?
因为我们想将存储过程作为整体传递给服务器,而不是让 mysql 工具一次解释每个语句。 在 END 关键字之后,使用分隔符 // 来指示存储过程的结束。

最后一个命令 (DELIMITER;) 将分隔符更改回分号 (;)

使用 CREATE PROCEDURE 语句创建一个新的存储过程。

CREATE PROCEDURE 语句之后指定存储过程的名称。
在这个示例中,存储过程的名称为:GetAllProducts,并把括号放在存储过程的名字之后。

BEGINEND 之间的部分称为存储过程的主体。

将声明性 SQL 语句放在主体中以处理业务逻辑。
在这个存储过程中,我们使用一个简单的 SELECT 语句来查询 products 表中的数据。

1.2 调用存储过程

要调用存储过程,可以使用以下SQL命令:

CALL STORED_PROCEDURE_NAME();

使用 CALL 语句调用存储过程,例如调用 GetAllProducts() 存储过程,则使用以下语句:

CALL GetAllProducts();

在这里插入图片描述
如果您执行上述语句,将查询获得 products 表中的所有产品。

3 MySQL 存储过程的变量

变量是一个命名数据对象,变量的值可以在存储过程执行期间更改。
我们通常使用存储过程中的变量来保存直接/间接结果。
这些变量是存储过程的本地变量。

注意:变量必须先声明后,才能使用它。

3.1 声明变量

要在存储过程中声明一个变量,可以使用 DECLARE 语句,如下所示:

DECLARE variable_name datatype(size) DEFAULT default_value;

下面来更详细地解释上面的语句:

首先,在 DECLARE 关键字后面要指定变量名。
变量名必须遵循 MySQL 表列名称的命名规则。

其次,指定变量的数据类型及其大小。
变量可以有任何 MySQL 数据类型,如 INT,VARCHAR,DATETIME 等。

当声明一个变量时,它的初始值为 NULL。
但是可以使用 DEFAULT 关键字为变量分配默认值。

例如,可以声明一个名为 total_sale 的变量,数据类型为 INT,默认值为 0,如下所示:

DECLARE total_sale INT DEFAULT 0;

MySQL 允许您使用单个 DECLARE 语句声明共享相同数据类型的两个或多个变量,如下所示:

DECLARE x, y INT DEFAULT 0;

我们声明了两个整数变量 x 和 y,并将其默认值设置为 0。

3.2 分配变量值

当声明了一个变量后,就可以开始使用它了。
要为变量分配一个值,可以使用 SET 语句,例如:

DECLARE total_count INT DEFAULT 0;
SET total_count = 10;

上面语句中,分配 total_count 变量的值为 10
除了 SET 语句之外,还可以使用 SELECT INTO 语句将查询的结果分配给一个变量。
请参阅以下示例:

DECLARE total_products INT DEFAULT 0

SELECT COUNT(*) INTO total_products
FROM products

在上面的例子中:
首先,声明一个名为 total_products 的变量,并将其值初始化为 0

然后,使用 SELECT INTO 语句来分配值给 total_products 变量,从 示例数据库(yiibaidb) 中的 products 表中选择的产品数量。

3.3 变量范围(作用域)

一个变量有自己的范围(作用域),它用来定义它的生命周期。

如果在存储过程中声明一个变量,那么当达到存储过程的 END 语句时,它将超出范围,因此在其它代码块中无法访问。

如果您在 BEGIN END 块内声明一个变量,那么如果达到 END,它将超出范围。

可以在不同的作用域中声明具有相同名称的两个或多个变量,因为变量仅在自己的作用域中有效。

但是,在不同范围内声明具有相同名称的变量不是很好的编程习惯。

@ 符号开头的变量是会话变量。

直到会话结束前它可用和可访问。

4 MySQL 存储过程参数

在现实应用中,开发的存储过程几乎都需要参数。
这些参数使存储过程更加灵活和有用。
在 MySQL中,参数有三种模式:IN,OUTINOUT

IN - 是默认模式。
在存储过程中定义IN参数时,调用程序必须将参数传递给存储过程。

另外,IN参数的值被保护。
这意味着即使在存储过程中更改了IN参数的值,在存储过程结束后仍保留其原始值。
换句话说,存储过程只使用IN参数的副本。

OUT - 可以在存储过程中更改 OUT 参数的值,并将其更改后新值传递回调用程序。
请注意,存储过程在启动时无法访问 OUT 参数的初始值。

INOUT - INOUT 参数是 IN 和 OUT 参数的组合。
这意味着调用程序可以传递参数,并且存储过程可以修改 INOUT 参数并将新值传递回调用程序。

在存储过程中定义参数的语法如下:

MODE param_name param_type(param_size)

上面语法说明如下:
根据存储过程中参数的目的,MODE 可以是 IN,OUT 或 INOUT。

param_name 是参数的名称。
参数的名称必须遵循 MySQL 中列名的命名规则。

在参数名之后是它的数据类型和大小。
和变量一样,参数的数据类型可以是任何有效的MySQL数据类型。

如果存储过程有多个参数,则每个参数由逗号 (,) 分隔。

4.1 MySQL存储过程参数示例

1 IN 参数示例

以下示例说明如何使用 GetOfficeByCountry 存储过程中的 IN 参数来查询选择位于特定国家/地区的办公室。

USE `yiibaidb`;
DROP procedure IF EXISTS `GetOfficeByCountry`;

DELIMITER $$
USE `yiibaidb`$$

CREATE PROCEDURE GetOfficeByCountry (IN countryName VARCHAR(255))
BEGIN
	SELECT
		*
	FROM
		offices
	WHERE
		country = countryName ; END$$

DELIMITER ;

countryName 是存储过程的 IN 参数。
在存储过程中,我们查询位于 countryName 参数指定的国家/地区的所有办公室。

假设我们想要查询在美国(USA)的所有办事处,我们只需要将一个值(USA)传递给存储过程,如下所示:

CALL GetOfficeByCountry('USA')

在这里插入图片描述

要在法国获得所有办事处,我们将 France 字符串传递给 GetOfficeByCountry 存储过程,如下所示:

CALL GetOfficeByCountry('France')

在这里插入图片描述

2 OUT 参数示例

以下存储过程通过订单状态返回订单数量。

它有两个参数:
orderStatus:IN 参数,它是要对订单计数的订单状态。
total:存储指定订单状态的订单数量的 OUT 参数。

以下是 CountOrderByStatus 存储过程的源代码。

USE `yiibaidb`;

DROP PROCEDURE
IF EXISTS `CountOrderByStatus`;

DELIMITER $$
CREATE PROCEDURE CountOrderByStatus (
	IN orderStatus VARCHAR (25),
	OUT total INT
)
BEGIN
	SELECT
		count(orderNumber) INTO total
	FROM
		orders
	WHERE
		STATUS = orderStatus ; END$$
DELIMITER ;

在这里插入图片描述
要获取发货订单的数量,我们调用 CountOrderByStatus 存储过程,并将订单状态传递为已发货,并传递参数 (@total) 以获取返回值。

CALL CountOrderByStatus('Shipped',@total);
SELECT @total;

在这里插入图片描述

3 INOUT 参数示例

以下示例演示如何在存储过程中使用 INOUT 参数。如下查询语句:

DELIMITER $$
CREATE PROCEDURE set_counter (
	INOUT count INT (4),
	IN inc INT (4)
)
BEGIN

SET count = count + inc ; END$$
DELIMITER ;

上面查询语句是如何运行的 ?

set_counter 存储过程接受一个 INOUT 参数 (count) 和一个 IN 参数 (inc)。

在存储过程中,通过 inc 参数的值增加计数器 (count)。

下面来看看如何调用 set_counter 存储过程:

SET @counter = 1;
CALL set_counter(@counter,1); -- 2
CALL set_counter(@counter,1); -- 3
CALL set_counter(@counter,5); -- 8
SELECT @counter; -- 8

5 MySQL 存储过程返回多个值

MySQL存储函数只返回一个值。
要开发返回多个值的存储过程,需要使用带有 INOUT 或 OUT 参数的存储过程。

如果您不熟悉 INPUT 或 OUT 参数的用法,请查看存储过程参数教程的详细信息。

5.1 返回多个值的存储过程示例

我们来看看示例数据库 (yiibaidb) 中的 orders 表。

mysql> desc orders;
+----------------+-------------+------+-----+---------+-------+
| Field          | Type        | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+-------+
| orderNumber    | int(11)     | NO   | PRI | NULL    |       |
| orderDate      | date        | NO   |     | NULL    |       |
| requiredDate   | date        | NO   |     | NULL    |       |
| shippedDate    | date        | YES  |     | NULL    |       |
| status         | varchar(15) | NO   |     | NULL    |       |
| comments       | text        | YES  |     | NULL    |       |
| customerNumber | int(11)     | NO   | MUL | NULL    |       |
+----------------+-------------+------+-----+---------+-------+
7 rows in set

以下存储过程接受客户编号,并返回发货 (shipped),取消 (canceled),解决 (resolved) 和争议 (disputed) 的订单总数。

DELIMITER $$
CREATE PROCEDURE get_order_by_cust (
	IN cust_no INT,
	OUT shipped INT,
	OUT canceled INT,
	OUT resolved INT,
	OUT disputed INT
)
BEGIN
	-- shipped
	SELECT
		count(*) INTO shipped
	FROM
		orders
	WHERE
		customerNumber = cust_no
	AND STATUS = 'Shipped' ; -- canceled
	SELECT
		count(*) INTO canceled
	FROM
		orders
	WHERE
		customerNumber = cust_no
	AND STATUS = 'Canceled' ; -- resolved
	SELECT
		count(*) INTO resolved
	FROM
		orders
	WHERE
		customerNumber = cust_no
	AND STATUS = 'Resolved' ; -- disputed
	SELECT
		count(*) INTO disputed
	FROM
		orders
	WHERE
		customerNumber = cust_no
	AND STATUS = 'Disputed' ;
	END

在这里插入图片描述
除 IN 参数之外,存储过程还需要 4 个额外的 OUT 参数:
shipped, canceled, resolved 和 disputed

在存储过程中,使用带有 COUNT 函数的 SELECT 语句根据订单状态获取相应的订单总数,并将其分配给相应的参数。

要使用 get_order_by_cust 存储过程,可以传递客户编号和四个用户定义的变量来获取输出值。执行存储过程后,使用 SELECT 语句输出变量值。

SELECT @shipped,@canceled,@resolved,@disputed;

+----------+-----------+-----------+-----------+
| @shipped | @canceled | @resolved | @disputed |
+----------+-----------+-----------+-----------+
|       22 |         0 |         1 |         1 |
+----------+-----------+-----------+-----------+
1 row in set

5.2 从PHP调用返回多个值的存储过程

以下代码片段显示如何从PHP程序中调用返回多个值的存储过程。

<?php
/**
 * Call stored procedure that return multiple values
 * @param $customerNumber
 */
function call_sp($customerNumber)
{
    try {
        $pdo = new PDO("mysql:host=localhost;dbname=yiibaidb", 'root', 'root');

        // execute the stored procedure
        $sql = 'CALL get_order_by_cust(:no,@shipped,@canceled,@resolved,@disputed)';
        $stmt = $pdo->prepare($sql);

        $stmt->bindParam(':no', $customerNumber, PDO::PARAM_INT);
        $stmt->execute();
        $stmt->closeCursor();

        // execute the second query to get values from OUT parameter
        $r = $pdo->query("SELECT @shipped,@canceled,@resolved,@disputed")
                  ->fetch(PDO::FETCH_ASSOC);
		/*
		print_r($r);exit;
		Array
		(
			[@shipped] => 22
			[@canceled] => 0
			[@resolved] => 1
			[@disputed] => 1
		)
		*/
        if ($r) {
            printf('Shipped: %d, Canceled: %d, Resolved: %d, Disputed: %d',
                $r['@shipped'],
                $r['@canceled'],
                $r['@resolved'],
                $r['@disputed']);
        }
    } catch (PDOException $pe) {
        die("Error occurred:" . $pe->getMessage());
    }
}

call_sp(141);
PS D:\tmp> php .\dump.php
Shipped: 22, Canceled: 0, Resolved: 1, Disputed: 1
PS D:\tmp>

@ 符号之前的用户定义的变量与数据库连接相关联,因此它们可用于在调用之间进行访问。

6 MySQL if 语句

MySQL IF 语句允许您根据表达式的某个条件或值结果来执行一组SQL语句。

要在MySQL中形成一个表达式,可以结合文字,变量,运算符,甚至函数来组合。

表达式可以返回 TRUE,FALSE 或 NULL,这三个值之一。

请注意,有一个 IF 函数与本文中指定的 IF 语句是不同的。

6.1 MySQL IF语句语法

下面说明了 IF 语句的语法:

IF expression THEN 
   statements;
END IF;

如果表达式 (expression) 计算结果为 TRUE,那么将执行 statements 语句,否则控制流将传递到 END IF 之后的下一个语句。

以下流程图演示了IF语句的执行过程:

在这里插入图片描述

6.2 MySQL IF ELSE 语句

如果表达式计算结果为 FALSE 时执行语句,请使用 IF ELSE 语句,如下所示:

IF expression THEN
   statements;
ELSE
   else-statements;
END IF;

以下流程图说明了 IF ELSE 语句的执行过程:

在这里插入图片描述

6.3 MySQL IF ELSEIF ELSE 语句

如果要基于多个表达式有条件地执行语句,则使用 IF ELSEIF ELSE 语句如下:

IF expression THEN
   statements;
ELSEIF elseif-expression THEN
   elseif-statements;
...
ELSE
   else-statements;
END IF;

如果表达式 (expression) 求值为 TRUE,则 IF 分支中的语句 (statements) 将执行;

如果表达式求值为 FALSE,则如果 elseif_expression 的计算结果为 TRUE,MySQL将执行elseif-expression,否则执行 ELSE 分支中的 else-statements 语句。

具体流程如下

在这里插入图片描述

6.2 MySQL IF语句示例

以下示例说明如何使用IF ESLEIF ELSE语句,GetCustomerLevel() 存储过程接受客户编号和客户级别的两个参数。

首先,它从 customers 表中获得信用额度
然后,根据信用额度,它决定客户级别:PLATINUM , GOLD 和 SILVER 。
参数 p_customerlevel 存储客户的级别,并由调用程序使用。

USE yiibaidb;

DELIMITER $$

CREATE PROCEDURE GetCustomerLevel(
    in  p_customerNumber int(11), 
    out p_customerLevel  varchar(10)
)
BEGIN
    DECLARE creditlim double;

    SELECT creditlimit INTO creditlim
    FROM customers
    WHERE customerNumber = p_customerNumber;

    IF creditlim > 50000 THEN
			SET p_customerLevel = 'PLATINUM';
    ELSEIF (creditlim <= 50000 AND creditlim >= 10000) THEN
      SET p_customerLevel = 'GOLD';
    ELSEIF creditlim < 10000 THEN
      SET p_customerLevel = 'SILVER';
    END IF;

END $$

在这里插入图片描述

call GetCustomerLevel(114,@p_customerLevel);

select @p_customerLevel;

在这里插入图片描述

以下流程图演示了确定客户级别的逻辑 :

在这里插入图片描述

1 SHOW STATUS 语句查看存储过程

 SHOW PROCEDURE STATUS  [ like ‘pattern’] ;

参数 PROCEDURE 表示查询存储过程;
参数 LIKE ‘pattern’ 用来匹配存储过程的名称。

 show procedure status like 'proc%';

2 使用 SHOW CREATE 语句查看存储过程的定义

SHOW CREATE PROCEDURE proc_name ;|\G     

参数 PROCEDURE 表示查询存储过程;
参数 proc_name 表示存储过程的名称。

show create procedure proc_age;

3 存储过程的删除

DROP PROCEDURE proc_name;    

关键字 DROP PROCEDURE 用来表示实现删除存储过程;
参数 proc_name 表示所要删除的存储过程名称。

7 MySQL CASE 语句

除了IF语句,MySQL提供了一个替代的条件语句CASE。
MySQL CASE语句使代码更加可读和高效。

7.1 简单 CASE 语句

我们来看一下简单 CASE 语句的语法:

CASE  case_expression
   WHEN when_expression_1 THEN commands
   WHEN when_expression_2 THEN commands
   ...
   ELSE commands
END CASE;

您可以使用简单 CASE 语句来检查表达式的值与一组唯一值的匹配。

case_expression 可以是任何有效的表达式。

我们将 case_expression 的值与每个 WHEN 子句中的 when_expression 进行比较,例如when_expression_1,when_expression_2 等。

如果 case_expression 和 when_expression_n 的值相等,则执行相应的 WHEN 分支中的命令 (commands)。

如果 WHEN 子句中的 when_expression 与 case_expression 的值匹配,则 ELSE 子句中的命令将被执行。

ELSE 子句是可选的。

如果省略 ELSE 子句,并且找不到匹配项,MySQL将引发错误。

以下示例说明如何使用简单的 CASE 语句:

DELIMITER $$

CREATE PROCEDURE GetCustomerShipping(
 in  p_customerNumber int(11), 
 out p_shiping        varchar(50)
)

BEGIN
 DECLARE customerCountry varchar(50);
 SELECT country INTO customerCountry
 FROM customers
 WHERE customerNumber = p_customerNumber;

 CASE customerCountry
 WHEN  'USA' THEN
    SET p_shiping = '2-day Shipping';
 WHEN 'Canada' THEN
    SET p_shiping = '3-day Shipping';
 ELSE
    SET p_shiping = '5-day Shipping';
 END CASE;
END $$

上面存储过程是如何工作的?

GetCustomerShipping 存储过程接受客户编号作为 IN 参数,并根据客户所在国家返回运送时间。

在存储过程中,首先,我们根据输入的客户编号得到客户的国家。然后使用简单CASE语句来比较客户的国家来确定运送期。如果客户位于美国(USA),则运送期为2天。 如果客户在加拿大,运送期为3天。 来自其他国家的客户则需要5天的运输时间。

以下流程图显示了确定运输时间的逻辑。

在这里插入图片描述
以下是上述存储过程的测试脚本:

SET @customerNo = 112;

SELECT country into @country
FROM customers
WHERE customernumber = @customerNo;

CALL GetCustomerShipping(@customerNo,@shipping);

SELECT @customerNo AS Customer,
       @country    AS Country,
       @shipping   AS Shipping;

在这里插入图片描述

7.2 可搜索 CASE 语句

简单CASE语句仅允许您将表达式的值与一组不同的值进行匹配。

为了执行更复杂的匹配,如范围,您可以使用可搜索CASE语句。

可搜索CASE语句等同于IF语句,但是它的构造更加可读。

以下说明可搜索CASE语句的语法:

CASE
    WHEN condition_1 THEN commands
    WHEN condition_2 THEN commands
    ...
    ELSE commands
END CASE;

MySQL评估求值WHEN子句中的每个条件,直到找到一个值为TRUE的条件,然后执行THEN子句中的相应命令(commands)。

如果没有一个条件为TRUE,则执行ELSE子句中的命令(commands)。如果不指定ELSE子句,并且没有一个条件为TRUE,MySQL将发出错误消息。

MySQL不允许在THEN或ELSE子句中使用空的命令。 如果您不想处理ELSE子句中的逻辑,同时又要防止MySQL引发错误,则可以在ELSE子句中放置一个空的BEGIN END块。

以下示例演示如何使用可搜索CASE语句来根据客户的信用额度来查找客户级:SILVER,GOLD或PLATINUM。

DELIMITER $$

CREATE PROCEDURE GetCustomerLevel(
 in  p_customerNumber int(11), 
 out p_customerLevel  varchar(10)
)
BEGIN
 DECLARE creditlim double;

 SELECT creditlimit INTO creditlim
 FROM customers
 WHERE customerNumber = p_customerNumber;

    CASE  
 WHEN creditlim > 50000 THEN 
    SET p_customerLevel = 'PLATINUM';
 WHEN (creditlim <= 50000 AND creditlim >= 10000) THEN
    SET p_customerLevel = 'GOLD';
 WHEN creditlim < 10000 THEN
    SET p_customerLevel = 'SILVER';
 END CASE;

END $$

在上面查询语句逻辑中,如果信用额度是:

大于50K,则客户是PLATINUM客户。
小于50K,大于10K,则客户是GOLD客户。
小于10K,那么客户就是SILVER客户。

我们可以通过执行以下测试脚本来测试存储过程:

CALL GetCustomerLevel(112,@level);
SELECT @level AS 'Customer Level';

在这里插入图片描述

8 MySQL 存储过程循环

MySQL循环语句 (包括 WHILE,REPEAT 和 LOOP) 来根据条件反复运行代码块。

MySQL提供循环语句,允许您根据条件重复执行一个SQL代码块。

MySQL中有三个循环语句:WHILE,REPEAT 和 LOOP。

我们将在以下部分中更详细地检查每个循环语句。

8.1 WHILE循环

WHILE expression DO
   statements
END WHILE

WHILE 循环在每次迭代开始时检查表达式。

如果 expressionevaluates 为 TRUE,MySQL将执行 WHILE 和 END WHILE 之间的语句,直到 expressionevaluates 为 FALSE。

WHILE 循环称为预先测试条件循环,因为它总是在执行前检查语句的表达式。

下面的流程图说明了 WHILE 循环语句:

在这里插入图片描述
以下是在存储过程中使用 WHILE 循环语句的示例:

DELIMITER $$
 DROP PROCEDURE IF EXISTS test_mysql_while_loop $$
 CREATE PROCEDURE test_mysql_while_loop()
 BEGIN
 DECLARE x  INT;
 DECLARE str  VARCHAR(255);

 SET x = 1;
 SET str =  '';

 WHILE x  <= 5 DO
 SET  str = CONCAT(str,x,',');
 SET  x = x + 1; 
 END WHILE;

 SELECT str;
 END $$
DELIMITER ;

在上面的 test_mysql_while_loop 存储过程中:

1、首先,重复构建str字符串,直到x变量的值大于 5。
2、然后,使用 SELECT 语句显示最终的字符串。

要注意,如果不初始化 x 变量的值,那么它默认值为 NULL。
因此,WHILE 循环语句中的条件始终为 TRUE,并且您将有一个不确定的循环,这是不可预料的。

下面来测试 test_mysql_while_loopstored 调用存储过程:

CALL test_mysql_while_loop();

在这里插入图片描述

8.2 REPEAT 循环

REPEAT
 statements;
UNTIL expression
END REPEAT

首先,MySQL执行语句,然后评估求值表达式 (expression)。

如果表达式 (expression) 的计算结果为 FALSE,则 MySQL 将重复执行该语句,直到该表达式计算结果为 TRUE。

因为 REPEAT 循环语句在执行语句后检查表达式 (expression),因此 REPEAT 循环语句也称为测试后循环。

下面的流程图说明了 REPEAT 循环语句的执行过程:

在这里插入图片描述
我们可以使用 REPEAT 循环语句重写 test_mysql_while_loop 存储过程,使用 WHILE 循环语句:

DELIMITER $$
 DROP PROCEDURE IF EXISTS mysql_test_repeat_loop$$
 CREATE PROCEDURE mysql_test_repeat_loop()
 BEGIN
 DECLARE x INT;
 DECLARE str VARCHAR(255);

 SET x = 1;
        SET str =  '';

 REPEAT
 SET  str = CONCAT(str,x,',');
 SET  x = x + 1; 
        UNTIL x  > 5
        END REPEAT;

        SELECT str;
 END $$
DELIMITER ;

要注意的是UNTIL表达式中没有分号 (;)
执行上面查询语句,得到以下结果

mysql> CALL mysql_test_repeat_loop();
+------------+
| str        |
+------------+
| 1,2,3,4,5, |
+------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

8.3 LOOP,LEAVE 和 ITERATE 语句

有两个语句允许您用于控制循环:

1 LEAVE 语句用于立即退出循环,而无需等待检查条件。
LEAVE 语句的工作原理就类似 PHP,C/C++,Java 等其他语言的 break 语句一样。

2 ITERATE 语句允许您跳过剩下的整个代码并开始新的迭代。
ITERATE 语句类似于PHP,C/C++,Java 等中的 continue 语句。

MySQL 还有一个 LOOP 语句,它可以反复执行一个代码块,另外还有一个使用循环标签的灵活性。

以下是使用 LOOP 循环语句的示例。

CREATE PROCEDURE test_mysql_loop()
 BEGIN
 DECLARE x  INT;
 DECLARE str  VARCHAR(255);
 SET x = 1;
 SET str =  '';

 loop_label:  LOOP
 IF  x > 10 THEN 
 LEAVE  loop_label;
 END  IF;

 SET  x = x + 1;
 IF (x mod 2) THEN
     ITERATE  loop_label;
 ELSE
    SET  str = CONCAT(str,x,',');
 END IF;
    END LOOP;    
    SELECT str;
END;
  • 以上存储过程仅构造具有偶数字符串的字符串,例如 2,4,6 等。
  • 在 LOOP 语句之前放置一个 loop_label 循环标签。
  • 如果 x 的值大于 10,则由于 LEAVE 语句,循环被终止。
  • 如果 x 的值是一个奇数,ITERATE 语句忽略它下面的所有内容,并开始一个新的迭代。
  • 如果 x 的值是偶数,则 ELSE 语句中的块将使用偶数构建字符串。

执行上面查询语句,得到以下结果:

mysql> CALL test_mysql_loop();
+-------------+
| str         |
+-------------+
| 2,4,6,8,10, |
+-------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

mysql>

9 MySQL 游标

要处理存储过程中的结果集,请使用游标。
游标允许您迭代查询返回的一组行,并相应地处理每行。

MySQL游标为只读,不可滚动和敏感。

只读:无法通过光标更新基础表中的数据。

不可滚动:只能按照 SELECT 语句确定的顺序获取行。
不能以相反的顺序获取行。
此外,不能跳过行或跳转到结果集中的特定行。

敏感:
有两种游标:敏感游标和不敏感游标。
敏感游标指向实际数据,不敏感游标使用数据的临时副本。
敏感游标比一个不敏感的游标执行得更快,因为它不需要临时拷贝数据。
但是,对其他连接的数据所做的任何更改都将影响由敏感游标使用的数据,因此,如果不更新敏感游标所使用的数据,则更安全。 MySQL游标是敏感的。

您可以在存储过程,存储函数和触发器中使用MySQL游标。

9.1 使用 MySQL 游标

首先,必须使用 DECLARE 语句声明游标:

DECLARE cursor_name CURSOR FOR SELECT_statement;

游标声明必须在变量声明之后。
如果在变量声明之前声明游标,MySQL将会发出一个错误。
游标必须始终与 SELECT 语句相关联。

接下来,使用 OPEN 语句打开游标。
OPEN 语句初始化游标的结果集,因此您必须在从结果集中提取行之前调用 OPEN 语句。

OPEN cursor_name;

然后,使用 FETCH 语句来检索光标指向的下一行,并将光标移动到结果集中的下一行。

FETCH cursor_name INTO variables list;

之后,可以检查是否有任何行记录可用,然后再提取它。
最后,调用 CLOSE 语句来停用光标并释放与之关联的内存,如下所示:

CLOSE cursor_name;

当光标不再使用时,应该关闭它。

当使用MySQL游标时,还必须声明一个 NOT FOUND 处理程序来处理当游标找不到任何行时的情况。

因为每次调用 FETCH 语句时,游标会尝试读取结果集中的下一行。

当光标到达结果集的末尾时,它将无法获得数据,并且会产生一个条件。
处理程序用于处理这种情况。

要声明一个 NOT FOUND 处理程序,参考以下语法:

DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = 1;

finished 是一个变量,指示光标到达结果集的结尾。
请注意,处理程序声明必须出现在存储过程中的变量和游标声明之后。

下图说明了 MySQL 游标如何工作。

在这里插入图片描述

9.2 MySQL 游标示例

为了更好地演示,我们将开发一个存储过程,来获取MySQL示例数据库(yiibaidb)中employees表中所有员工的电子邮件列表。

首先,声明一些变量,一个用于循环员工电子邮件的游标和一个 NOT FOUND 处理程序:

DECLARE finished INTEGER DEFAULT 0;
DECLARE email varchar(255) DEFAULT "";

-- declare cursor for employee email
DEClARE email_cursor CURSOR FOR 
SELECT email FROM employees;

-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER 
FOR NOT FOUND SET finished = 1;

接下来,使用 OPEN 语句打开 email_cursor :

OPEN email_cursor;

然后,迭代电子邮件列表,并使用分隔符 (;) 连接每个电子邮件:

get_email: LOOP
 FETCH email_cursor INTO v_email;
 IF v_finished = 1 THEN 
 LEAVE get_email;
 END IF;
 -- build email list
 SET email_list = CONCAT(v_email,";",email_list);
END LOOP get_email;

之后,在循环中,使用 v_finished 变量来检查列表中是否有任何电子邮件来终止循环。
最后,使用 CLOSE 语句关闭游标:

CLOSE email_cursor;

build_email_list 存储过程所有代码如下:

DELIMITER $$

CREATE PROCEDURE build_email_list (INOUT email_list varchar(4000))
BEGIN

 DECLARE v_finished INTEGER DEFAULT 0;
 DECLARE v_email varchar(100) DEFAULT "";

 -- declare cursor for employee email
 DEClARE email_cursor CURSOR FOR 
 SELECT email FROM employees;

 -- declare NOT FOUND handler
 DECLARE CONTINUE HANDLER 
        FOR NOT FOUND SET v_finished = 1;

 OPEN email_cursor;

 get_email: LOOP

 FETCH email_cursor INTO v_email;

 IF v_finished = 1 THEN 
 LEAVE get_email;
 END IF;

 -- build email list
 SET email_list = CONCAT(v_email,";",email_list);

 END LOOP get_email;

 CLOSE email_cursor;

END $$

DELIMITER ;

可以使用以下脚本测试 build_email_list 存储过程:

SET @email_list = "";
CALL build_email_list(@email_list);
SELECT @email_list;

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weiguang102/article/details/126062035