T-SQL 之 存储过程

  当存储过程执行一次后,可以将语句缓存中,这样下次执行的时候直接使用缓存中的语句。这样就可以提高存储过程的性能。

一、存储过程的概念

    存储过程Procedure是一组为了完成特定功能的SQL语句集合,经编译后存储在数据库中,用户通过指定存储过程的名称并给出参数来执行。

    存储过程中可以包含逻辑控制语句和数据操纵语句,它可以接受参数、输出参数、返回单个或多个结果集以及返回值。

    由于存储过程在创建时即在数据库服务器上进行了编译并存储在数据库中,所以存储过程运行要比单个的SQL语句块要快。同时由于在调用时只需用提供存储过程名和必要的参数信息,所以在一定程度上也可以减少网络流量、简单网络负担。

  1、 存储过程的优点

  [1] 存储过程允许标准组件式编程

  存储过程创建后可以在程序中被多次调用执行,而不必重新编写该存储过程的SQL语句。而且数据库专业人员可以随时对存储过程进行修改,但对应用程序源代码却毫无影响,从而极大的提高了程序的可移植性。

  [2] 存储过程能够实现较快的执行速度

      如果某一操作包含大量的T-SQL语句代码,分别被多次执行,那么存储过程要比批处理的执行速度快得多。因为存储过程是预编译的,在首次运行一个存储过程时,查询优化器对其进行分析、优化,并给出最终被存在系统表中的存储计划。而批处理的T-SQL语句每次运行都需要预编译和优化,所以速度就要慢一些。

  [3] 存储过程减轻网络流量

  对于同一个针对数据库对象的操作,如果这一操作所涉及到的T-SQL语句被组织成一存储过程,那么当在客户机上调用该存储过程时,网络中传递的只是该调用语句,否则将会是多条SQL语句。从而减轻了网络流量,降低了网络负载。

  [4] 存储过程可被作为一种安全机制来充分利用

  系统管理员可以对执行的某一个存储过程进行权限限制,从而能够实现对某些数据访问的限制,避免非授权用户对数据的访问,保证数据的安全。

二、创建存储过程的基本语法

---------------------------创建存储过程-----------------------
CREATE PROC[ EDURE ] procedure_name [ ; number ]
    [ { @Parameter Datatype }[ VARYING ] [ = default ] [|OUT|OUTPUT]] 
    ......
    [ WITH { RECOMPILE | ENCRYPTION | RECOMPILE , ENCRYPTION } ]
    [ FOR REPLICATION ]
AS
BEGIN
    sql_statement [ ...n ]
END
--------------调用存储过程-----------------
--存储过程如果有参数,后面加参数格式为:@参数名=value,也可直接为参数值value
EXECUTE Procedure_name

--------------删除存储过程-----------------
--在存储过程中能调用另外一个存储过程,而不能删除另外一个存储过程
IF (EXISTS (SELECT * FROM sys.objects WHERE name = 'procedure_name'))
DROP PROCEDURE procedure_name 

创建存储过程的参数:
  1、procedure_name :存储过程的名称,在前面加#为局部临时存储过程,加##为全局临时存储过程。
  2、; number:是可选的整数,用来对同名的过程分组,以便用一条 DROP PROCEDURE 语句即可将同组的过程一起除去。例如,名为 orders 的应用程序使用的过程可以命名为 orderproc;1、orderproc;2 等。DROP PROCEDURE orderproc 语句将除去整个组。如果名称中包含定界标识符,则数字不应包含在标识符中,只应在 procedure_name 前后使用适当的定界符。
  3、@Parameter: 存储过程的参数。可以有0-多个。用户必须在执行过程时提供每个所声明参数的值(除非定义了该参数的默认值)。存储过程最多可以有 100 个参数。
  使用 @ 符号作为第一个字符来指定参数名称。参数名称必须符合标识符的规则。每个过程的参数仅用于该过程本身;相同的参数名称可以用在其它过程中。默认情况下,参数只能代替常量,而不能用于代替表名、列名或其它数据库对象的名称。有关更多信息,请参见 EXECUTE。
  4、Datatype:参数的数据类型。所有数据类型(包括 text、ntext 和 image)均可以用作存储过程的参数。不过,cursor 数据类型只能用于 OUTPUT 参数。如果指定的数据类型为 cursor,也必须同时指定 VARYING 和 OUTPUT 关键字。有关 SQL Server 提供的数据类型及其语法的更多信息,请参见数据类型。
  说明:对于可以是 cursor 数据类型的输出参数,没有最大数目的限制。
  5、VARYING: 指定作为输出参数支持的结果集(由存储过程动态构造,内容可以变化)。仅适用于游标参数。 

  6、default: 参数的默认值。如果定义了默认值,不必指定该参数的值即可执行过程。默认值必须是常量或 NULL。如果过程将对该参数使用 LIKE 关键字,那么默认值中可以包含通配符(%、_、[] 和 [^])。

  7、OUTPUT :表明参数是返回参数。该选项的值可以返回给 EXEC[UTE]。使用 OUTPUT 参数可将信息返回给调用过程。Text、ntext 和 image 参数可用作 OUTPUT 参数。使用 OUTPUT 关键字的输出参数可以是游标占位符。 

  8、RECOMPILE: 表明 SQL Server 不会缓存该过程的计划,该过程将在运行时重新编译。在使用非典型值或临时值而不希望覆盖缓存在内存中的执行计划时,请使用 RECOMPILE 选项。

  9、ENCRYPTION: 表示 SQL Server 加密 syscomments 表中包含 CREATE PROCEDURE 语句文本的条目。使用 ENCRYPTION 可防止将过程作为 SQL Server 复制的一部分发布。 说明 在升级过程中,SQL Server 利用存储在 syscomments 中的加密注释来重新创建加密过程。 

  10、FOR REPLICATION :指定不能在订阅服务器上执行为复制创建的存储过程。.使用 FOR REPLICATION 选项创建的存储过程可用作存储过程筛选,且只能在复制过程中执行。本选项不能和 WITH RECOMPILE 选项一起使用。 

  11、AS :指定过程要执行的操作。

  12、sql_statement :过程中要包含的任意数目和类型的 Transact-SQL 语句。但有一些限制。

二、ALTER修改存储过程

  ALTER PROC和CREATE PROC的区别如下:

  [1] ALTER PROC期望找到一个已有的存储过程,而CREATE则不是。

  [2] ALTER PROC保留了存储过程上已经建立的任何权限。它在系统对象中保留了对象Id并允许保留依赖关系。例如,如果过程A调用过程B,如果删除并重建B,那么就不能在看到这两者间的依赖关系。如果使用ALTER,则依赖关系仍然存在。

  [3] ALTER PROC在可能调用被修改的存储过程的其他对象上保留了任何依赖信息。

  示例:

ALTER PROC spPerson
AS
SELECT * FROM Person WHERE Id = 45

三、删除存储过程

  删除存储过程的语法最简单:

DROP PROC|PROCEDURE <sproc name>[;]

四、参数化

  如果存储过程没有办法接受一些数据,告诉其要完成的任务,则在大多数情况下,存储过程不会有太大帮助。例如,要删除一条数据,但却不指定Id,则存储过程也不知道要删除哪条,所以使用输入参数非常有必要。

  1、声明参数

  语法如下:

@parameter_name [AS] datatype [=default|NULL] [VARYING] [OUTPUT | OUT]

  参数名称,有一组简单的规则。首先,它必须以@符号(和变量一样)开始。此外,除了不能内嵌空格外,其规则与普通变量规则相同。

  数据类型和名称一样,必须像变量那样声明,采用SQL Server内置的或用户自定义的数据类型。

  声明需要类型时需要注意,当声明CURSOR类型参数时,必须也使用VARYING和OUTPUT选项。同时,OUTPUT可以简写为OUT。

  在默认值方面,参数与变量不同。对于同样的情况,变量一般初始化为NULL值,而参数不是。事实上,如果不提供默认则,则会假设参数是必须的,并且当调用存储过程时需要提供一个初始值。

五、返回值

  返回值的用途非常广泛,例如,返回数据,标识值或是存储过程影响的行数等等。而其实际作用是返回值可用来确定存储过程执行的状态。

  事实上,不管是否提供返回值,程序都会收到一个返回值。SQL Server默认会在完成存储过程时自动返回一个0值。

  为了从存储过程向调用代码传递返回值,只需要使用RETURN语句。

  RETURN [<integer value to return>]

  要特别注意的是:返回值必须是整数

  关于RETURN语句,最重要的是知道它是无条件地从存储过程中退出的。无论运行到存储过程的哪个位置,在调用RETURN语句之后将不会执行任何一行代码。

  这里的无条件,并不是说无论执行到代码的何处都将执行RETURN语句。相反,可以再存储过程中有多个RETURN语句。只有当代码的标准条件结构发出命令的时候,才会执行这些RETURN语句。一旦发生,就不能再退回了。

六、存储过程的优缺点

  1、创建可调用的进程

  很多人并没有意识到要充分使用存储过程,使其作为实现安全性的工具。和视图类似,可以创建一个返回记录集的存储过程而不用赋予用户访问底层数据表的权限。赋予某人执行一个存储过程的权限意味着他们可以在该存储过程中执行任何动作。不过要假设动作是在存储过程的上下文中执行的。

  2、存储过程和性能

  一般来说,存储过程有助于系统性能的提高。但是,如果设计的存储过程缺乏只能,那么它会使在其创建的进程变得非常缓慢。

  存储过程的运行示意图如下:

  

  首先运行CREATE PROC过程。这回解析查询以确保会实际运行这些代码。它与直接运行脚本的区别在于CREATE PROC命令可以利用所谓的延迟名称解析。延迟名称解析可以忽略一些对象还不存在的事实。

  在创建了存储过程后,它将等待第一次执行。在那时,存储过程被优化,而查询计划被编译并且缓存到系统上。后续几次运行该存储过程时,除非通过使用WITH RECOMPILE选项指定,否则都会使用缓存的查询计划而不是创建一个新的查询计划。这意味着每次使用该存储过程时,存储过程都会跳过很多优化和编译工作。节省的确切时间取决于批处理的复杂性,批处理中表的大小,以及每个表上索引的数量。通常,节省的时间不是很多。但对于大多数场景来说可能是1秒或更少-但通过百分比可以计算出此区别(1秒比2秒快了100%)。当需要进行多次调用时或针对循环的情况,这一区别会变得更明显。

  3、存储过程的不利方面

  对于存储过程的不利之处要认识到的最重要的一点事,除非手动地干预(使用WITH RECOMPILE选项),否则只会在第一次运行存储过程的时候,或者当查询所涉及的表更新了统计信息时,才对存储过程进行优化。
  这种"一次优化,多次使用"的策略节省了存储过程的时间,但是该策略也是一把双刃剑。如果查询是动态的(即是在使用EXEC命令时建立的),那么只会在第一次运行时对存储过程进行优化,但是会发现以后再也不这样了。简而言之,可能会使用错误的计划。

  4、WITH RECOMPILE选项

  可以利用存储过程提供的安全性代码和代码封装方面的好处,但还是忽略了预编译代码方面的影响。可以回避未使用正确的查询计划的问题,因为可以确保为特定一次运行创建新的计划。方法就是使用WITH RECOMPILE选项。
  使用该选项的方式有两种:

  1、可以在运行时包含WITH RECOMPILE。

  EXEC spMySproc '1/1/2004'
  WITH RECOMPILE

  这告诉SQL Server抛弃已有的执行计划并且创建一个新的计划-但只是这一次。也就是说,只是这次使用WITH RECOMPILE选项来执行存储过程。

  也可以通过在存储过程中包含WITH RECOMPILE选项来使之变得更持久。如果使用这种方式,则在CREATE PROC或ALTER PROC语句中的AS语句前添加WITH RECOMPILE选项即可。

  如果通过该选项创建存储过程,那么无论在运行时选择了其他什么选项,每次运行存储过程都会重新编译它。

三、几种特殊存储过程

  1、 不缓存存储过程

--WITH RECOMPILE 不缓存
if (object_id('proc_temp', 'P') is not null)
    drop proc proc_temp
go
create proc proc_temp
with recompile
as
    select * from student;
go

exec proc_temp;

  2、 加密存储过程

--加密WITH ENCRYPTION 
if (object_id('proc_temp_encryption', 'P') is not null)
    drop proc proc_temp_encryption
go
create proc proc_temp_encryption
with encryption
as
    select * from student;
go

exec proc_temp_encryption;
exec sp_helptext 'proc_temp';
exec sp_helptext 'proc_temp_encryption';

  3、 带游标参数存储过程

if (object_id('proc_cursor', 'P') is not null)
    drop proc proc_cursor
go
create proc proc_cursor
    @cur cursor varying output
as
    set @cur = cursor forward_only static for
    select id, name, age from student;
    open @cur;
go
--调用
declare @exec_cur cursor;
declare @id int,
        @name varchar(20),
        @age int;
exec proc_cursor @cur = @exec_cur output;--调用存储过程
fetch next from @exec_cur into @id, @name, @age;
while (@@fetch_status = 0)
begin
    fetch next from @exec_cur into @id, @name, @age;
    print 'id: ' + convert(varchar, @id) + ', name: ' + @name + ', age: ' + convert(char, @age);
end
close @exec_cur;
deallocate @exec_cur;--删除游标

  4、 分页存储过程

---存储过程、row_number完成分页
if (object_id('pro_page', 'P') is not null)
    drop proc proc_cursor
go
create proc pro_page
    @startIndex int,
    @endIndex int
as
    select count(*) from product
;    
    select * from (
        select row_number() over(order by pid) as rowId, * from product 
    ) temp
    where temp.rowId between @startIndex and @endIndex
go
--drop proc pro_page
exec pro_page 1, 4
--
--分页存储过程
if (object_id('pro_page', 'P') is not null)
    drop proc pro_stu
go
create procedure pro_stu(
    @pageIndex int,
    @pageSize int
)
as
    declare @startRow int, @endRow int
    set @startRow = (@pageIndex - 1) * @pageSize +1
    set @endRow = @startRow + @pageSize -1
    select * from (
        select *, row_number() over (order by id asc) as number from student 
    ) t
    where t.number between @startRow and @endRow;

exec pro_stu 2, 2;
原文地址:https://www.cnblogs.com/xinaixia/p/5820581.html