13.5.3 游标和控制结构
现在,让我们来分析一个更复杂的例子。在这个例子中,我们将编写一个存储过程,该存储过程将计算出最大金额的订单,并且返回该订单的orderid(很明显,通过一个简单的查询,就可以计算出该数目,但是这个简单的示例只是说明了如何使用游标和控制结构)。该存储过程的代码如程序清单13-4所示。
程序清单13-4 control_structures_cursors.sql——使用游标和循环来处理一个结果集
Procedure to find the orderid with the largest amount
could be done with max,but just to illustrate stored procedure principles
delimiter//
create procedure largest_order(out largest_id int)
begin
declare this_id int;
declare this_amount float;
declare l_amount float default 0.0;
declare l_id int;
declare done int default 0;
declare continue handler for sqlstate'02000'set done=1;
declare c1 cursor for select orderid,amount from orders;
open c1;
repeat
fetch c1 into this_id,this_amount;
if not done then
if this_amount>l_amount then
set l_amount=this_amount;
set l_id=this_id;
end if;
end if;
until done end repeat;
close c1;
set largest_id=l_id;
end
//
delimiter;
以上代码使用了控制结构(条件语句和循环语句)、游标和声明句柄。下面,我们逐行分析以上代码。
在该存储过程的开始处,声明了一些在该存储过程中使用的局部变量。this_id和this_amount变量保存了当前行的orderid和amount值。l_amount和l_id变量用来存储最大的订单金额和与之对应的ID。由于需要将每一个值与当前最大值进行比较,可以将当前最大值初始化为0。
下一个变量被声明为done,初始化为0(false)。这个变量是循环标记。当遍历了所有需要查看的行,可以将该变量设置为1(true)。
以下代码行:
declare continue handler for sqlstate'02000'set done=1;
是一个声明句柄。它类似于存储过程中的一个异常。在continue句柄和exit句柄中,也可以使用它。就像以上代码所显示的,continue句柄执行了指定的动作,并且继续存储过程的执行。exit句柄将从最近的begin…end代码块中退出。
声明句柄的下一个部分指定了句柄被调用的时间。在这个例子中,该句柄将在sqlstate'02000'语句被执行时调用。你可能会奇怪,这是什么意思,因为该语句非常神秘。这意味着,该句柄将在无法再找到记录行后被调用。我们将逐行处理一个结果集,而且当遍历了所有需要处理的记录行时,这个句柄将被调用。也可以指定等价的FOR NOT FOUND语句。其他选项还包括SQLWARNING和SQLEXCEPTION。
接下来就是游标。一个游标类似于一个数组;它将从一个查询获得结果集(例如,mysqli_query()所返回的),并且允许一次只处理一行(例如,我们可能会使用mysqli_fetch_row()函数)。分析以下游标:
declare c1 cursor for select orderid,amount from orders;
这个游标名称为c1。这只是它将要保存内容的定义。该查询还不会被执行。
接下来一行代码:
open c1;
真正运行这个查询。要获得每一个数据行,必须运行一个fetch语句。可以在一个repeat循环中完成此操作。在这个例子中,循环语句如下所示:
repeat
…
until done end repeat;
请注意,只有在循环语句块的末尾才会检查循环条件。存储过程还支持while循环,如下形式所示:
while condition do
…
end while;
此外,还支持loop循环语句,如下形式所示:
loop
…
end loop
这些循环没有内置的循环条件,但是可以通过leave语句退出循环。
请注意,存储过程不支持for循环。
继续这个例子,以下代码将获得一个数据行:
fetch c1 into this_id,this_amount;
以上代码行将从游标查询中获得一个数据行。该查询所获得两个属性保存在两个指定的局部变量中。
我们可以检查一个数据行是否被获得,然后再将当前循环量与最大的存储值进行比较,通过两个IF语句的方式,如下所示:
if not done then
if this_amount>l_amount then
set l_amount=this_amount;
set l_id=this_id;
end if;
end if;
请注意,变量值将通过set语句进行设置。
除了if…then语句外,存储过程还支持if…then…else语句结构,如下形式所示:
if condition then
…
[elseif condition then]
…
[else]
…
end if
此外,也可以使用case语句,如下形式所示:
case value
when value then statement
[when value then statement…]
[else statement]
end case
回到这个例子,在循环语句末尾,将执行一些清除操作:
close c1;
set largest_id=l_id;
close语句将关闭这个游标。
最后,将所计算出的最大值赋值给OUT参数。不能将该参数作为临时变量,只能用来保存最终值(这种用法类似于其他一些编程语言,例如Ada)。
如果按照以上方式创建了这个存储过程,可以像调用其他存储过程一样调用这个存储过程:
call largest_order(@l);
select@l;
将获得类似于如下所示的输出:
+———+
|@l|
+———+
|3|
+———+
你可以自己检查计算结果是否正确。