mysql进阶之存储过程
往往看别人的代码会有这样的感慨:
看不懂
理还乱
是离愁
别是一番滋味在心头
在mysql开发中使用存储过程的理由:
让我们使用一些简单的示例来了解什么是存储过程,使用下边演示程序的前提是必须正确安装了mysql。
下边的程序来源于[PHP和MySQL WEB开发(4th)]这本书,我们使用mysql中的books数据库,这个数据库中的表有一下几个:
mysql> show tables;
+-----------------+
| Tables_in_books |
+-----------------+
| book_reviews |
| books |
| customers |
| order_items |
| orders |
+-----------------+
5 rows in set (0.00 sec)
我们用到了orders这个表:
mysql> select * from orders;
+---------+------------+--------+------------+
| orderid | customerid | amount | date |
+---------+------------+--------+------------+
| 1 | 3 | 69.98 | 2007-04-02 |
| 2 | 1 | 49.99 | 2007-04-15 |
| 3 | 2 | 74.98 | 2007-04-19 |
| 4 | 3 | 24.99 | 2007-05-01 |
| 5 | 3 | 69.98 | 2007-04-02 |
| 6 | 1 | 49.99 | 2007-04-15 |
| 7 | 2 | 74.98 | 2007-04-19 |
| 8 | 3 | 24.99 | 2007-05-01 |
| 9 | 2 | 69.98 | 2008-09-02 |
| 10 | 3 | 45.90 | 2009-09-09 |
+---------+------------+--------+------------+
10 rows in set (0.00 sec)
程序basic_stored_procedure.sql
:
# Basic stored procedure example
delimiter //
create procedure total_orders (out total float)
begin
select sum(amount) into total from orders;
end
//
delimiter ;
打开终端并启动mysql,使用有权限的账户登录mysql,使用某个数据库。上边的程序basic_stored_procedure.sql
是封装在一个后缀为sql的文件中的,因此要使用mysql调用这个文件。
使用命令source /path/basic_stored_procedure.sql
,path是你保存这个文件的路径。
然后使用命令call total_orders(@t);
。可以看出,使用关键字call来调用该过程。call total_orders(@t);
这个语句将调用total_orders过程并传入一个用来保存结果的变量。要查看该结果,需要查看该变量。使用下边的语句:
select @t;
结果为:
mysql> call total_orders(@t);
Query OK, 1 row affected (0.01 sec)
mysql> select @t;
+------------------+
| @t |
+------------------+
| 555.760009765625 |
+------------------+
1 row in set (0.00 sec)
现在我们逐行分析程序basic_stored_procedure.sql
中的代码:
elimiter //
将语句末尾的分隔符从当前值(这个分隔符通常是分号,除非以前改变了分隔符)改为双斜杠字符。这样做的目的是可以在存储过程中使用分号分隔符,这样mysql就会将分号当做是存储过程的代码,不会执行这些代码create procedure total_orders (out total float)
创建了实际的存储过程,该存储过程的名称是total_orders。他只有一个total参数,该参数是需要计算的值。out表示该参数将被传出或返回
BEGIN
和END
就好比函数中的{
}
,用来标识一个语句块select sum(amount) into total from orders;
这就是我们实际中使用的查询语句delimiter ;
delimiter重新把分隔符定义为分号;
示例1中是使用的procedure
创建过程的方法,在示例2中,我们将使用function
来创建函数。函数接受参数并返回一个唯一值。
程序basic_function.sql
:
# Basic syntax to create a function
delimiter //
create function add_tax (price float) returns float
return price*1.1;
//
delimiter ;
returns float
,它指定了返回值的类型。该值可以使任何有效的mysql类型return price*1.1;
使用return
可以返回一个值begin
和end
。可以使用它们,但他们不是必须的。就像php或者c中,如果一个语句块只含有一个语句,可以以调用内置函数的相同方式调用一个存储函数使用方法示例1中有所不同。
select add_tax(100);
结果如下:
mysql> select add_tax(100);
+--------------+
| add_tax(100) |
+--------------+
| 110 |
+--------------+
1 row in set (0.01 sec)
在定义了过程和函数之后可使用下边语句来查看过程或函数的代码:
查询:
show create procedure total_orders;
show create function add_tax;
删除:
drop procedure total_orders;
drop function add_tax;
使用declare语句,可以在begin...end语句块之间声明局部变量,就像函数中的局部变量一样。例如,可以对add_tax()
函数进行修改,使其使用一个局部变量来保存税率,如下:
程序basic_function_with_variables.sql
:
# Basic syntax to create a function
delimiter //
create function add_tax (price float) returns float
begin
declare tax float default 0.10;
return price*(1+tax);
end
//
delimiter ;
在下边的这个例子中,我们将编写一个存储过程,该存储过程将计算出最大金额的订单,并且返回该订单的orderid(很明显一个简单的查询,就可以计算出该数目,但是这个简单的例子只是说明了如何使用游标和控制结构)
程序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 c1 cursor for select orderid, amount from orders;
declare continue handler for sqlstate '02000' set done = 1;
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
保存当前行的orderidthis_amount
保存当前行的amountl_id
保存最大金额的orderidl_amount
保存最大金额的amount 默认值为0.0done
用于循环中的标记,当循环结束后会被标记为1,默认为0,也就是false declare continue handler for sqlstate '02000' set done = 1;
是一个声明句柄,它类似于存储过程中的一个异常。
这里边有一个关键字continue
,这个关键字是和exit
关键字相对应的。continue
语句会执行完指定操作后继续循环,而exit
语句会退出将从最近的begin...end语句块中退出。 在这里的指定的操作就是set done = 1
。
既然声明了句柄,就要告诉程序句柄在什么时候调用,在这个例子中,当sqlstate '02000'
语句被调用时会执行句柄。那么这个sqlstate '02000'
是什么意思呢?该句柄将在无法再找到记录行后调用。也就是说当遍历完所有的结果集后就会调用。
因此,declare continue handler for sqlstate '02000' set done = 1;
的意思就是当遍历完结果集之后把done的值设为1。
游标(Cursor)是处理数据的一种方法,为了查看或者处理结果集中的数据,游标提供了在结果集中一次以行或者多行前进或向后浏览数据的能力。我们可以把游标当作一个指针,它可以指定结果中的任何位置,然后允许用户对指定位置的数据进行处理。(关于游标的详细信息,请看这篇博文SQLServer游标(Cursor)简介和使用说明)
注意,句柄要定义在游标之后,不然会报ERROR 1338 (42000): Cursor declaration after handler declaration错误。
declare c1 cursor for select orderid, amount from orders;
这个游标名称为c1,这只是他要保存内容的定义,该查询还不会执行。使用open c1;
来真正运行这个查询。
要获得每一个数据行,必须运行一个fetch语句。可以在一个repeat循环中完成此操作。:
repeat
...
until done end repeat;
只有在循环的尾部才检测done的值,除了使用repeat还可以使用while和loop循环
while condition do
...
end while
loop
...
end loop
这些循环没有内置的循环条件,但是可以通过leave语句退出循环。请注意,存储过程不支持for循环
fetch c1 into this_id, this_amount;
这行代码将获得一个数据行,并把查询到的两个属性保存到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;
close c1;
将关闭这个游标
set largest_id=l_id;
将最大的值赋值给out参数,不能使用局部变量给外部调用。
调用方法:
call largest_order(@l);
select @l;
结果如下:
mysql> call largest_order(@l);
Query OK, 0 rows affected (0.00 sec)
mysql> select @l;
+------+
| @l |
+------+
| 3 |
+------+
1 row in set (0.01 sec)
如有错误之处,请给予指出,多谢。