这一课介绍什么是存储过程,为什么要使用存储过程,如何使用存储过程,以及创建和使用存储过程的基本语法。
迄今为止,我们使用的大多数SQL语句都是针对一个或多个表的单条语句。并非所有操作都这么简单,经常会有一些复杂的操作需要多条语句才能完成。例如以下的情形:
说明:具体DBMS的支持
Microsoft Access和SQLite不支持存储过程。因此,本课的内容不适用它们。
MySQL 5已经支持存储过程。因此,本课的内容不适用MySQL较早的版本。
说明:还有更多内容
存储过程很复杂,全面介绍它需要很大篇幅。本课不打算讲解存储过程的所有内容,只给出简单介绍,让读者对它们的功能有所了解。因此,这里给出的例子只提供Oracle和SQL Server的语法。
我们知道了什么是存储过程,那么为什么要使用它们呢?理由很多,下面列出一些主要的。
说明:不能编写存储过程?你依然可以使用
大多数DBMS将编写存储过程所需的安全和访问权限与执行存储过程所需的安全和访问权限区分开来。这是好事情,即使你不能(或不想)编写自己的存储过程,也仍然可以在适当的时候执行别的存储过程。
存储过程的执行远比编写要频繁得多,因此我们先介绍存储过程的执行。执行存储过程的SQL语句很简单,即EXECUTE。EXECUTE接受存储过程名和需要传递给它的任何参数。请看下面的例子:
输入▼
EXECUTE AddNewProduct( 'JTS01',
'Stuffed Eiffel Tower',
6.49,
'Plush stuffed toy with the text La
-Tour Eiffel in red white and blue' );
分析▼
这里执行一个名为AddNewProduct的存储过程,将一个新产品添加到Products表中。AddNewProduct有四个参数,分别是:供应商ID(Vendors表的主键)、产品名、价格和描述。这4个参数匹配存储过程中4个预期变量(定义为存储过程自身的组成部分)。此存储过程将新行添加到Products表,并将传入的属性赋给相应的列。
我们注意到,在Products表中还有另一个需要值的列prod_id列,它是这个表的主键。为什么这个值不作为属性传递给存储过程?要保证恰当地生成此ID,最好是使生成此ID的过程自动化(而不是依赖于最终用户的输入)。这也是这个例子使用存储过程的原因。以下是存储过程所完成的工作:
正如所述,存储过程的编写很重要。为了获得感性认识,我们来看一个简单的存储过程例子,它对邮件发送清单中具有邮件地址的顾客进行计数。
下面是该过程的Oracle版本:
输入▼
CREATE PROCEDURE MailingListCount (
ListCount OUT INTEGER
) IS
v_rows INTEGER;
BEGIN
SELECT COUNT(*) INTO v_rows
FROM Customers
WHERE NOT cust_email IS NULL;
ListCount := v_rows;
END;
分析▼
这个存储过程有一个名为ListCount的参数。此参数从存储过程返回一个值而不是传递一个值给存储过程。关键字OUT用来指示这种行为。Oracle支持IN(传递值给存储过程)、OUT(从存储过程返回值,如这里)、INOUT(既传递值给存储过程也从存储过程传回值)类型的参数。存储过程的代码括在BEGIN和END语句中,这里执行一条简单的SELECT语句,它检索具有邮件地址的顾客。然后用检索出的行数设置ListCount(要传递的输出参数)。
调用Oracle例子可以像下面这样:
输入▼
var ReturnValue NUMBER
EXEC MailingListCount(:ReturnValue);
SELECT ReturnValue;
分析▼
这段代码声明了一个变量来保存存储过程返回的任何值,然后执行存储过程,再使用SELECT语句显示返回的值。
下面是该过程的SQL Server版本:
输入▼
CREATE PROCEDURE MailingListCount
AS
DECLARE @cnt INTEGER
SELECT @cnt = COUNT(*)
FROM Customers
WHERE NOT cust_email IS NULL;
RETURN @cnt;
分析▼
此存储过程没有参数。调用程序检索SQL Server的返回代码支持的值。其中用DECLARE语句声明了一个名为@cnt的局部变量(SQL Server中所有局部变量名都以@起头);然后在SELECT语句中使用这个变量,让它包含COUNT()函数返回的值;最后,用RETURN @cnt语句将计数返回给调用程序。
调用SQL Server例子可以像下面这样:
输入▼
DECLARE @ReturnValue INT
EXECUTE @ReturnValue=MailingListCount;
SELECT @ReturnValue;
分析▼
这段代码声明了一个变量来保存存储过程返回的任何值,然后执行存储过程,再使用SELECT语句显示返回的值。
下面是另一个例子,这次在Orders表中插入一个新订单。此程序仅适用于SQL Server,但它说明了存储过程的某些用途和技术:
输入▼
CREATE PROCEDURE NewOrder @cust_id CHAR(10)
AS
-- Declare variable for order number
DECLARE @order_num INTEGER
-- Get current highest order number
SELECT @order_num=MAX(order_num) FROM Orders
-- Determine next order number
SELECT @order_num=@order_num+1
-- Insert new order
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(@order_num, GETDATE(), @cust_id)
-- Return order number
RETURN @order_num;
分析▼
此存储过程在Orders表中创建一个新订单。它只有一个参数,即下订单顾客的ID。订单号和订单日期这两列在存储过程中自动生成。代码首先声明一个局部变量来存储订单号。接着,检索当前最大订单号(使用MAX()函数)并增加1(使用SELECT语句)。然后用INSERT语句插入由新生成的订单号、当前系统日期(用GETDATE()函数检索)和传递的顾客ID组成的订单。最后,用RETURN @order_num返回订单号(处理订单物品需要它)。请注意,此代码加了注释,在编写存储过程时应该多加注释。
说明:注释代码
应该注释所有代码,存储过程也不例外。增加注释不影响性能,因此不存在缺陷(除了增加编写时间外)。注释代码的好处很多,包括使别人(以及你自己)更容易地理解和更安全地修改代码。
对代码进行注释的标准方式是在之前放置–(两个连字符)。有的DBMS还支持其他的注释语法,不过所有DBMS都支持–,因此在注释代码时最好都使用这种语法。
下面是相同SQL Server代码的一个很不同的版本:
输入▼
CREATE PROCEDURE NewOrder @cust_id CHAR(10)
AS
-- Insert new order
INSERT INTO Orders(cust_id) VALUES(@cust_id)
-- Return order number
SELECT order_num = @@IDENTITY;
分析▼
此存储过程也在Orders表中创建一个新订单。这次由DBMS生成订单号。大多数DBMS都支持这种功能;SQL Server中称这些自动增量的列为标识字段(identity field),而其他DBMS称之为自动编号(auto number)或序列(sequence)。传递给此过程的参数也是一个,即下订单的顾客ID。订单号和订单日期没有给出,DBMS对日期使用默认值(GETDATE()函数),订单号自动生成。怎样才能得到这个自动生成的ID?在SQL Server上可在全局变量@@IDENTITY中得到,它返回到调用程序(这里使用SELECT语句)。
可以看到,借助存储过程,可以有多种方法完成相同的工作。不过,所选择的方法受所用DBMS特性的制约。
上一篇:第18课 SQL入门之使用视图