SQL必知必会——使用存储过程

1、存储过程

迄今为止,我们使用的大多数SQL语句都是针对一个或多个表的单条语句。并非所有操作都这么简单,经常会有一些复杂的操作需要多条语句才能完成,例如以下的情形。

  • 为了处理订单,必须核对以保证库存中有相应的物品。
  • 如果物品有库存,需要预定,不再出售给别的人,并且减少物品数据以反映正确的库存量。
  • 库存中没有的物品需要订购,这需要与供应商进行某种交互。
  • 关于哪些物品入库(并且可以立即发货)和哪些物品退订,需要通知相应的顾客。

这显然不是一个完整的例子,它甚至超出了本书中所用样例表的范围,但足以表达我们的意思了。执行这个处理需要针对许多表的多条SQL语句。此外,需要执行的具体SQL语句及其次序也不是固定的,它们可能会根据物品是否在库存中而变化。

那么,怎样编写代码呢?可以单独编写每条SQL语句,并根据结果有条件地执行其他语句。在每次需要这个处理时(以及每个需要它的应用中)​,都必须做这些工作。

可以创建存储过程。简单来说,存储过程就是为以后使用而保存的一条或多条SQL语句。可将其视为批文件,虽然它们的作用不仅限于批处理。

存储过程很复杂,这里给出的例子只提供Oracle和SQL Server的语法。

2、为什么要使用存储过程

我们知道了什么是存储过程,那么为什么要使用它们呢?理由很多,下面列出一些主要的。

  • 通过把处理封装在一个易用的单元中,可以简化复杂的操作(如前面例子所述)​。
  • 由于不要求反复建立一系列处理步骤,因而保证了数据的一致性。如果所有开发人员和应用程序都使用同一存储过程,则所使用的代码都是相同的。
  • 上一点的延伸就是防止错误。需要执行的步骤越多,出错的可能性就越大。防止错误保证了数据的一致性。
  • 简化对变动的管理。如果表名、列名或业务逻辑(或别的内容)有变化,那么只需要更改存储过程的代码。使用它的人员甚至不需要知道这些变化。
  • 上一点的延伸就是安全性。通过存储过程限制对基础数据的访问,减少了数据讹误(无意识的或别的原因所导致的数据讹误)的机会。
  • 因为存储过程通常以编译过的形式存储,所以DBMS处理命令所需的工作量少,提高了性能。

换句话说,使用存储过程有三个主要的好处,即简单、安全、高性能。显然,它们都很重要。不过,在将SQL代码转换为存储过程前,也必须知道它的一些缺陷。

  • 不同DBMS中的存储过程语法有所不同。事实上,编写真正的可移植存储过程几乎是不可能的。不过,存储过程的自我调用(名字以及数据如何传递)可以相对保持可移植。因此,如果需要移植到别的DBMS,至少客户端应用代码不需要变动。
  • 一般来说,编写存储过程比编写基本SQL语句复杂,需要更高的技能,更丰富的经验。因此,许多数据库管理员把限制存储过程的创建作为安全措施(主要受上一条缺陷的影响)​。

3、执行存储过程

存储过程的执行远比编写要频繁得多,因此我们先介绍存储过程的执行。执行存储过程的SQL语句很简单,即EXECUTE。EXECUTE接受存储过程名和需要传递给它的任何参数。请看下面的例子(你无法运行这个例子,因为AddNewProduct这个存储过程还不存在)​:

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的过程自动化(而不是依赖于最终用户的输入)​。这也是这个例子使用存储过程的原因。以下是存储过程所完成的工作:

  • 验证传递的数据,保证所有4个参数都有值;
  • 生成用作主键的唯一ID;
  • 将新产品插入Products表,在合适的列中存储生成的主键和传递的数据。

这就是存储过程执行的基本形式。对于具体的DBMS,可能包括以下的执行选择。

  • 参数可选,具有不提供参数时的默认值。
  • 不按次序给出参数,以“参数=值”的方式给出参数值。
  • 输出参数,允许存储过程在正执行的应用程序中更新所用的参数。
  • 用SELECT语句检索数据。
  • 返回代码,允许存储过程返回一个值到正在执行的应用程序。

4、创建存储过程

正如所述,存储过程的编写很重要。为了获得感性认识,我们来看一个简单的存储过程例子,它对邮件发送清单中具有邮件地址的顾客进行计数。

下面是该过程的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
seclare @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 @order_num INTEGER
-- 获取当前最大订单号
SELECT @order_num=MAX(order_num)
FROM Orders
-- 决定下一个订单号
SELECT @order_num=@order_num+1
-- 插入新订单
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(@order_num, GETDATE(), @cust_id)
-- 返回订单号
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 INTO Orders(cust_id)
VALUES(@cust_id)
-- 返回订单号
SELECT order_num = @@IDENTITY;

此存储过程也在Orders表中创建一个新订单。这次由DBMS生成订单号。大多数DBMS都支持这种功能;SQLServer中称这些自动增量的列为标识字段(identityfield)​,而其他DBMS称之为自动编号(auto number)或序列(sequence)​。传递给此过程的参数也是一个,即下订单的顾客ID。订单号和订单日期没有给出,DBMS对日期使用默认值(GETDATE()函数)​,订单号自动生成。怎样才能得到这个自动生成的ID?在SQL Server上可在全局变量@@IDENTITY中得到,它返回到调用程序(这里使用SELECT语句)​。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值