SQL之恋(5):走近存储过程

软件世界

  存储过程(Stored Procedures)是存储在数据库服务器中并且被编译执行的SQL批处理过程。可以说存储过程是关系型数据库的命根子。

  一、为什么使用存储过程

  把业务逻辑封装在存储过程中起码有如下好处:

  ●使反映业务逻辑的代码模块化、集中化,使维护管理工作更简易;

  ●允许不同的客户端应用程序访问存储过程,共享业务逻辑;

  ●在存储过程执行之前SQL Server会对它进行“伪编译”,执行效率高;

  ●存储过程一旦运行,就被装入高速缓冲区,以备下次调用,使用存储过程可以减少代码从磁盘装载的过程,提高服务器的工作效率;

  ●客户端调用存储过程只需要极少的语句,能够有效地降低网络流量;

  ●在安全方面,存储过程的优势体现在以下两个方面:其一,存储过程是对付来自Web的“SQL注入式”攻击的有效手段;其二,SQL Server允许在存储过程上设定执行权限。

  例如,我们不希望车间主任直接查看(SELECT)工资表,但需要车间主任根据所辖员工的工资基数(来自工资表)来计算每月奖金,这时我们可以封杀车间主任对工资表的SELECT权限,同时编写一个存储过程来处理奖金的计算(这个存储过程中有对工资表的SELECT 操作),并把这个存储过程的执行权限向车间主任开放。

  二、另一种声音

  有些开发人员对存储过程“不屑一顾”,他们的观点是:由于存储过程中不可避免地使用了大量SQL“方言”,因此使用存储过程会导致数据库在不同的DBMS间迁移时发生困难!

  笔者以为,相对于存储过程在系统构架、系统维护、系统效率和系统安全方面的卓越贡献,所谓的“跨DBMS”的问题是不值得深究的,因为绝大多数系统没有跨DBMS的现实需求;即使需要跨DBMS,大型系统依然无法抵挡存储过程的诱惑,开发人员宁愿维护多个DBMS上的存储过程。所以笔者奉劝初学者切不可因噎废食!

  三、创建存储过程

  SQL Server支持六种存储过程:用户定义存储过程、系统存储过程、扩展存储过程、临时存储过程、全局临时存储过程和远程存储过程,本文只讨论用户定义存储过程的创建和执行。

  打开SQL Server的“查询分析器”,登录到SQL Server。输入如下代码,并按F5键执行,就可以在示例数据库中创建存储过程SalesByCategory_TEST,它的作用是:查询1996到1998年中任一年份按产品类别汇总的销售情况。

  --第一部分:设定当前数据库

  USE NorthWind

  GO

  --第二部分:删除NorthWind 数据库中的同名存储过程

  IF EXISTS(SELECT * FROM sysobjects WHERE ID=OBJECT_ID('SalesByCategory_TEST') AND OBJECTPROPERTY(ID,'IsProcedure')=1)

  BEGIN

  DROP PROCEDURE SalesByCategory_TEST

  END

  GO

  --第三部分:创建存储过程

  CREATE PROCEDURE SalesByCategory_TEST

  @CategoryName nvarchar(15), @OrdYear int= 1998,@Msg nvarchar(50) OUTPUT

  AS

  IF @OrdYear != 1996 AND @OrdYear != 1997 AND @OrdYear != 1998

  BEGIN

  SELECT @OrdYear = 1998

  END

  SELECT ProductName,TotalPurchase=ROUND(SUM(CONVERT(decimal(14,2), OD.Quantity * (1-OD.Discount) * OD.UnitPrice)), 0)

  FROM [Order Details] OD, Orders O, Products P, Categories C

  WHERE OD.OrderID = O.OrderID

  AND OD.ProductID = P.ProductID

  AND P.CategoryID = C.CategoryID

  AND C.CategoryName = @CategoryName

  AND DATEPART(YEAR,O.OrderDate) = @Or dYear

  GROUP BY ProductName ORDER BY Product Name

  SELECT @Msg=CAST(@OrdYear AS CHAR(4))+' 年度,'+@CategoryName+' 类产品的销售信息汇总。'

  RETURN @@ROWCOUNT

  GO

  四、代码分析

  以上代码分为三个部分。

  1.第一部分是设定当前数据库为示例数据库NorthWind;

  2.第二部分的作用是保证NorthWind 数据库中不存在名为SalesByCatego ry_TEST存储过程。

  3.代码的第三部分是创建存储过程,详细分析见上表。

  五、调用存储过程

  在SQL Server里调用存储过程很简单,主要通过 EXEC 命令。在“查询分析器”里打开新窗口,输入如下代码即可。

  DECLARE @COUNT INT

  DECLARE @YEAR INT

  DECLARE @MSG nvarchar(50)

  SELECT @YEAR=1997

  --下面四条语句任选一句输入

  --提示:注意怎样从存储过程读取“返回值”;怎样使用“输入输出参数”。

  EXEC @COUNT=SalesByCategory_TEST 'Condi ments',@YEAR,@MSG OUTPUT

  --提示:DEFAULT关键字让存储过程启用参数的默认值。

  EXEC @COUNT=SalesByCategory_TEST 'Condi ments',DEFAULT,@MSG OUTPUT

  --提示:忽略具有默认值的参数,表示让存储过程启用参数的默认值。

  EXEC SalesByCategory_TEST 'Condiments',@YEAR,@MSG OUTPUT

  --提示:调用存储过程时指定参数名称,就可以颠倒参数的顺序。

  EXEC @COUNT=SalesByCategory_TEST @MSG=@MSG OUTPUT,@CategoryName='Condiments',@Or dYear=@YEAR

  PRINT @COUNT

  PRINT @MSG

  GO

  六、存储过程与INSERT语句结合

  在SQL Server里存储过程可以被简单调用,也可以和INSERT 语句结合。上文的调用过程属于前者,下面我们看看怎样实现后者。

  --建立临时表 #Temp

  CREATE TABLE #TEMP(ProductName nvarchar(40),TotalPurchase money)

  --把存储过程返回的结果集插入到临时表中

  INSERT INTO #TEMP (ProductName ,TotalPur chase) EXEC SalesByCategory 'Condiments',DE FAULT,''

  --显示临时表

  SELECT * FROM #TEMP

  --删除临时表

  DROP TABLE #TEMP

  GO

  读到这里,朋友们或许会产生几个疑问,存储过程能不能与其他SQL 语句(例如SELECT和UPDATE)结合?SQL Server是否提供事件机制,自动触发存储过程?这些问题的答案你将在下期文章中找到,解决方法就是存储过程的“变体”──用户定义函数和触发器。