SQLServer游标详解
一、游标概念
我们知道,关系数据库所有的关系运算其实是集合与集合的运算,它的输入是集合输出同样是集合,有时需要对结果集逐行进行处理,这时就需要用到游标。我们对游标的使用一本遵循“五步法”:声明游标—>打开游标—>读取数据—>关闭游标—>删除游标。以下就从这五步对游标的使用进行说明,并给出具体实例。
二、“五步法”讲解
1、声明游标(DECLARE CURSOR)
(1) DECLARE CURSOR 既接受基于 ISO 标准的语法,也接受使用一组 Transact-SQL 扩展的语法。
ISO 语法 DECLARE cursor_name [ INSENSITIVE ] [ SCROLL ] CURSOR FOR select_statement [ FOR { READ ONLY | UPDATE [ OF column_name [ ,...n ] ] } ] [;] Transact-SQL 扩展语法 DECLARE cursor_name CURSOR [ LOCAL | GLOBAL ] (说明游标的“作用域”) [ FORWARD_ONLY | SCROLL ] (说明游标的“方向”) [ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ] (“说明游标的“类型”) [ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ] [ TYPE_WARNING ] FOR select_statement [ FOR UPDATE [ OF column_name [ ,...n ] ] ] [;]
下面只对“T-SQL扩展语法”进行介绍,主要游标介绍作用域、方向及类型
(2)游标的作用域
LOCAL
说明所声明的游标为局部的,其作用域为创建它的批处理、存储过程或触发器,即在批处理、调用它的存储过程或触发器执行完成后,该游标被系统隐式释放。但,若游标作为存储过程OUTPUT 的输出参数,在存储过程终止后给游标变量分配参数可以继续引用游标,如果 OUTPUT 参数将游标传递回来,则游标在最后引用它的变量释放或离开作用域时释放。
GLOBAL
指定该游标的作用域对来说连接是全局的。在由连接执行的任何存储过程或批处理中,都可以引用该游标名称。该游标仅在断开连接时隐式释放。
注意:如果 GLOBAL 和 LOCAL 参数都未指定,则默认值由 default to local cursor 数据库选项的设置控制。在 SQL Server 7.0 版中,该选项默认为 FALSE,以便与 SQL Server 的早期版本匹配,在早期版本中,所有游标都是全局的。
declare testcur cursor --声明时未指定"local"或"global"关键字,系统默认游标是"global(全局)"的. for select 学号,姓名 from XSB GO open testcur fetch next from testcur GO -------------------------------------------- declare testcur cursor local --声明时指定"local"关键字 for select 学号,姓名 from XSB open testcur fetch next from testcur GO --在这个批处理结束后,游标自动释放,即失效 ----------------------------------------------- declare testcur1 cursor local --声明时指定"local"关键字 for select 学号,姓名 from XSB GO --在这个批处理后,游标就失效了,后面对游标操作时,提示"名为 'testcur1' 的游标不存在"错误. open testcur1 fetch next from testcur1 GO
(3)游标方向
FORWARD_ONLY
指定游标只能从第一行滚动到最后一行。FETCH NEXT 是唯一支持的提取选项。如果在指定 FORWARD_ONLY 时不指定 STATIC、KEYSET 和 DYNAMIC 关键字,则游标作为 DYNAMIC 游标进行操作。如果 FORWARD_ONLY 和 SCROLL 均未指定,则除非指定 STATIC、KEYSET 或 DYNAMIC 关键字,否则默认为 FORWARD_ONLY。STATIC、KEYSET 和 DYNAMIC 游标默认为 SCROLL。与 ODBC 和 ADO 这类数据库 API 不同,STATIC、KEYSET 和 DYNAMIC Transact-SQL 游标支持 FORWARD_ONLY。
SCROLL
指定所有的提取选项(FIRST、LAST、PRIOR、NEXT、RELATIVE、ABSOLUTE)均可用。如果未在 ISO DECLARE CURSOR 中指定 SCROLL,则 NEXT 是唯一支持的提取选项。如果也指定了 FAST_FORWARD,则不能指定 SCROLL。
declare directionCur cursor --不指定移动方向,则默认为“forward_only” for select 学号,姓名 from XSB Go open directionCur fetch next from directionCur Go fetch prior from directionCur --错误“提取类型 prior 不能与只进游标一起使用。” Go close directionCur2 deallocate directionCur2 Go ----------------------------------------- declare directionCur1 cursor forward_only --指定“forward_only”方向 for select 学号,姓名 from XSB Go open directionCur1 fetch next from directionCur1 Go fetch prior from directionCur1 --错误“提取类型 prior 不能与只进游标一起使用。” Go close directionCur2 deallocate directionCur2 Go ----------------------------------------- declare directionCur2 cursor scroll --指定“scroll”方向 for select 学号,姓名 from XSB Go open directionCur2 fetch next from directionCur2 Go fetch prior from directionCur2 Go close directionCur2 deallocate directionCur2 Go --------------------------------- --结论:若游标没有指定任何访问或类型参数,则默认为全局、只进、动态游标。(这是在SQLServer2008 R2上的测试结果,具体取决于软件设置) --"scroll"和"fast_forward"不能一起使用 declare forwardTest cursor scroll fast_forward --报错“游标选项 SCROLL 和 FAST_FORWARD 冲突。” for select 学号,姓名 from XSB Go --在 SQL Server 2000 中,FAST_FORWARD 和 FORWARD_ONLY 游标选项是互相排斥的。如果指定了二者,则会引发错误。 --在 SQL Server 2005 及更高版本中,这两个关键字可以用在同一个 DECLARE CURSOR 语句中。 declare forwardTest1 cursor forward_only fast_forward for select * from XSB Go
(4)游标类型
STATIC
定义一个游标,以创建将由该游标使用的数据的临时复本。对游标的所有请求都从 tempdb 中的这一临时表中得到应答;因此,在对该游标进行提取操作时返回的数据中不反映对基表所做的修改,并且该游标不允许修改。及该游标是只读的。
KEYSET
指定当游标打开时,游标中行的成员身份和顺序已经固定。对行进行唯一标识的键集内置在 tempdb 内一个称为 keyset 的表中。
DYNAMIC
定义一个游标,以反映在滚动游标时对结果集内的各行所做的所有数据更改。行的数据值、顺序和成员身份在每次提取时都会更改。
FAST_FORWARD
指定启用了性能优化的 FORWARD_ONLY、READ_ONLY 游标。如果指定了 SCROLL 或 FOR_UPDATE,则不能也指定 FAST_FORWARD。
2、打开游标(OPEN)
语法
OPEN { { [ GLOBAL ] cursor_name } | cursor_variable_name }
示例
--打开游标并读取所有行 declare XSBcur cursor --声明 for Select * from XSB Go open XSBcur --打开 fetch next from XSBcur --获取数据 while @@FETCH_STATUS = 0 begin fetch next from XSBcur end close XSBcur --关闭 deallocate XSBcur --删除
全局变量 @@CURSOR_ROWS
该变量保存着最后打开的游标中的数据行数,当其值为0时,表示没有游标打开;其值为-1时,表示游标为动态的;当其值为-m(m为正整数)时,游标采用异步方式填充,m为当前键集中已填充的
行数;当其值为m(m为正整数)时,游标已被完全填充,m是游标中的数据行数。
3、读取数据
语法
FETCH [ [ NEXT | PRIOR | FIRST | LAST | ABSOLUTE { n | @nvar } | RELATIVE { n | @nvar } ] FROM ] { { [ GLOBAL ] cursor_name } | @cursor_variable_name } [ INTO @variable_name [ ,...n ] ] --into说明将读取的游标数据存放到指定的变量中
示例
----------------------------------- declare stuCur cursor scroll for select 学号,姓名 from XSB GO open stuCur Go --读取数据开始 fetch next from stuCur --读取当前行的下一行,并使其置为当前行(刚开始时游标置于表头的前一行,即若表是从0开始的,游标最初置于-1处,所以第一次读取的是头一行) fetch prior from stuCur --读取当前行的前一行,并使其置为当前行 fetch first from stuCur --读取游标的第一行,并使其置为当前行(不能用于只进游标) fetch last from stuCur --读取游标的最后一行,并使其置为当前行(不能用于只进游标) fetch absolute 2 from stuCur --读取从游标头开始向后的第2行,并将读取的行作为新的行 fetch relative 3 from stuCur --读取从当前行开始向后的第3行,并将读取的行作为新的行 fetch relative-2 from stuCur --读取当前行的上两行,并将读取的行作为新的行 --读取数据结束 GO close stuCur Go deallocate stuCur Go ---------------------------
全局变量 @@FETCH_STATUS
FETCH语句的执行状态保存在全局变量@@FETCH_STATUS中,其值为0表示上一个FETCH执行成功;为-1表示所要读取的行不在结果集中;为-2表示被提取的行已不存在(已被删除)。
4、关闭游标(CLOSE)
语法
CLOSE { { [ GLOBAL ] cursor_name } | cursor_variable_name }
示例
close stuCur --若该游标事先声明并已打开 Go
5、删除游标(DEALLOCATE)
对游标进行操作的语句使用游标名称或游标变量引用游标。DEALLOCATE 删除游标与游标名称或游标变量之间的关联。如果一个名称或变量是最后引用游标的名称或变量,则将释放游标,游标使用的任何资源也随之释放。用于保护提取隔离的滚动锁在 DEALLOCATE 上释放。用于保护更新(包括通过游标进行的定位更新)的事务锁一直到事务结束才释放。
语法
DEALLOCATE { { [ GLOBAL ] cursor_name } | @cursor_variable_name }
示例
游标变量使用下列两种方法之一与游标关联:
--声明一个游标 declare abc cursor scroll for select * from XSB --(1)通过名称,使用set语句将游标设置为游标变量 declare @mycur cursor set @mycur = abc --(2)也可以不定义游标名称而创建游标并将其与变量关联 declare @mycursor cursor set @mycursor = cursor local scroll for Select * from XSB
DEALLOCATE @cursor_variable_name 语句只删除对游标名称变量的引用。直到批处理、存储过程或触发器结束时变量离开作用域,才释放变量。在 DEALLOCATE @cursor_variable_name 语句之后,可以使用 SET 语句将变量与另一个游标关联。游标可以理解为指针。
declare @mycur cursor set @mycur = cursor local scroll for select * from XSB deallocate @mycur set @mycur = cursor local scroll for select * from XSB GO --不必显式释放游标变量。变量在离开作用域时被隐式释放。
以下脚本显示游标如何持续到最后的名称或持续到引用它们的变量已释放。
USE AdventureWorks2008R2; GO -- Create and open a global named cursor that -- is visible outside the batch. DECLARE abc CURSOR GLOBAL SCROLL FOR SELECT * FROM Sales.SalesPerson; OPEN abc; GO -- Reference the named cursor with a cursor variable. DECLARE @MyCrsrRef1 CURSOR; SET @MyCrsrRef1 = abc; -- Now deallocate the cursor reference. DEALLOCATE @MyCrsrRef1; -- Cursor abc still exists. FETCH NEXT FROM abc; GO -- Reference the named cursor again. DECLARE @MyCrsrRef2 CURSOR; SET @MyCrsrRef2 = abc; -- Now deallocate cursor name abc. DEALLOCATE abc; -- Cursor still exists, referenced by @MyCrsrRef2. FETCH NEXT FROM @MyCrsrRef2; -- Cursor finally is deallocated when last referencing -- variable goes out of scope at the end of the batch. GO -- Create an unnamed cursor. DECLARE @MyCursor CURSOR; SET @MyCursor = CURSOR LOCAL SCROLL FOR SELECT * FROM Sales.SalesTerritory; -- The following statement deallocates the cursor -- because no other variables reference it. DEALLOCATE @MyCursor; GO
三、用到的数据
1、创建表的脚本
CREATE TABLE [dbo].[XSB]( [学号] [char](6) NOT NULL, [姓名] [char](8) NOT NULL, [性别] [bit] NULL, [出生时间] [date] NULL, [专业] [char](12) NULL, [总学分] [int] NULL, [备注] [varchar](500) NULL, PRIMARY KEY CLUSTERED ( [学号] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
2、插入数据
insert into XSB values ('081101','王林','true','1990-2-10','计算机',50,null), ('081102','陈平','true','1991-2-1','计算机',50,null), ('081103','王燕','false','1989-10-6','计算机',50,null), ('081104','韦严平','true','1990-8-26','计算机',50,null), ('081106','李芳芳','true','1990-11-20','计算机',50,null), ('081107','李明','true','1990-5-1','计算机',54,'已提前修完数据结构,并获学分'), ('081108','林一凡','true','1989-8-5','计算机',52,'已提前修完一门课'), ('081109','张强民','true','1989-8-11','计算机',50,null), ('081110','张蔚','false','1991-7-22','计算机',50,'三好生'), ('081111','赵琳','false','1989-10-6','计算机',50,null), ('081113','严红','false','1989-8-11','计算机',48,'一门不及格,待补考'), ('081201','王敏','true','1989-6-10','通信工程',42,null), ('081202','王林','true','1989-6-10','通信工程',40,'一门不及格,待补考')
GO
四、参考资料
http://technet.microsoft.com/zh-cn/library/ms181441(v=sql.105).aspx