游标基础
游标和游标的优点
在数据库中,游标是一个十分重要的概念。游标提供了一种对从表中检索出的数据进行操作的灵活手段,就本质而言,游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。游标总是与一条T_SQL 选择语句相关联因为游标由结果集(可以是零条、一条或由相关的选择语句检索出的多条记录)和结果集中指向特定记录的游标位置组成。当决定对结果集进行处理时,必须声明一个指向该结果集的游标。如果曾经用 C 语言写过对文件进行处理的程序,那么游标就像您打开文件所得到的文件句柄一样,只要文件打开成功,该文件句柄就可代表该文件。对于游标而言,其道理是相同的。可见游标能够实现按与传统程序读取平面文件类似的方式处理来自基础表的结果集,从而把表中数据以平面文件的形式呈现给程序。
我们知道关系数据库管理系统实质是面向集合的,在MS SQL SERVER 中并没有一种描述表中单一记录的表达形式,除非使用where 子句来限制只有一条记录被选中。因此我们必须借助于游标来进行面向单条记录的数据处理。
由此可见,游标允许应用程序对查询语句select 返回的行结果集中每一行进行相同或不同的操作,而不是一次对整个结果集进行同一种操作;它还提供对基于游标位置而对表中数据进行删除或更新的能力;而且,正是游标把作为面向集合的数据库管理系统和面向行的程序设计两者联系起来,使两个数据处理方式能够进行沟通。
游标种类及用法
SQL SERVER 支持三种类型的游标:Transact_SQL 游标,API 服务器游标和客户游标。
(1)Transact_SQL 游标
Transact_SQL 游标是由DECLARE CURSOR 语法定义、主要用在Transact_SQL 脚本、存储过程和触发器中。Transact_SQL 游标主要用在服务器上,由从客户端发送给服务器的Transact_SQL 语句或是批处理、存储过程、触发器中的Transact_SQL 进行管理。 Transact_SQL 游标不支持提取数据块或多行数据。
(2)API 游标
API 游标支持在OLE DB, ODBC 以及DB_library 中使用游标函数,主要用在服务器上。每一次客户端应用程序调用API 游标函数,MS SQL SEVER 的OLE DB 提供者、ODBC驱动器或DB_library 的动态链接库(DLL) 都会将这些客户请求传送给服务器以对API游标进行处理。
(3)客户游标
客户游标主要是当在客户机上缓存结果集时才使用。在客户游标中,有一个缺省的结果集被用来在客户机上缓存整个结果集。客户游标仅支持静态游标而非动态游标。由于服务器游标并不支持所有的Transact-SQL 语句或批处理,所以客户游标常常仅被用作服务器游标的辅助。因为在一般情况下,服务器游标能支持绝大多数的游标操作。
由于API 游标和Transact-SQL 游标使用在服务器端,所以被称为服务器游标,也被称为后台游标,而客户端游标被称为前台游标。在本章中我们主要讲述服务器(后台)游标。
每一个游标必须有四个组成部分这四个关键部分必须符合下面的顺序;
1.DECLARE 游标
2.OPEN 游标
3.从一个游标中FETCH 信息
4.CLOSE 或DEALLOCATE 游标
通常我们使用DECLARE 来声明一个游标声明一个游标主要包括以下主要内容:
游标名字
数据来源(表和列)
选取条件
属性(仅读或可修改)
其语法格式如下:
DECLARE cursor_name [INSENSITIVE] [SCROLL] CURSOR
FOR select_statement
[FOR {READ ONLY | UPDATE [OF column_name [,...n]]}]
其中:
cursor_name
指游标的名字。
INSENSITIVE
表明MS SQL SERVER 会将游标定义所选取出来的数据记录存放在一临时表内(建立在tempdb 数据库下)。对该游标的读取操作皆由临时表来应答。因此,对基本表的修改并不影响游标提取的数据,即游标不会随着基本表内容的改变而改变,同时也无法通过游标来更新基本表。如果不使用该保留字,那么对基本表的更新、删除都会反映到游标中。
另外应该指出,当遇到以下情况发生时,游标将自动设定INSENSITIVE 选项。
在SELECT 语句中使用DISTINCT、 GROUP BY、 HAVING UNION 语句;
使用OUTER JOIN;
所选取的任意表没有索引;
将实数值当作选取的列。
SCROLL
表明所有的提取操作(如FIRST、 LAST、 PRIOR、 NEXT、 RELATIVE、 ABSOLUTE)都可用。如果不使用该保留字,那么只能进行NEXT 提取操作。由此可见,SCROLL 极大地增加了提取数据的灵活性,可以随意读取结果集中的任一行数据记录,而不必关闭再重开游标。
select_statement是定义结果集的SELECT 语句。应该注意的是,在游标中不能使用COMPUTE、COMPU- TE BY、 FOR BROWSE、INTO 语句。
READ ONLY表明不允许游标内的数据被更新尽管在缺省状态下游标是允许更新的。而且在UPDATE或DELETE 语句的WHERE CURRENT OF 子句中,不允许对该游标进行引用。
UPDATE [OF column_name[,…n]]
定义在游标中可被修改的列,如果不指出要更新的列,那么所有的列都将被更新。当游标被成功创建后,游标名成为该游标的惟一标识,如果在以后的存储过程、触发器或Transact_SQL 脚本中使用游标,必须指定该游标的名字。
上面介绍的是SQL_92 的游标语法规则。下面介绍MS SQL SERVER 提供的扩展了的游标声明语法,通过增加另外的保留字,使游标的功能进一步得到了增强其语法规则为;
LOCAL
定义游标的作用域仅限在其所在的存储过程、触发器或批处理中。当建立游标的存储过程执行结束后,游标会被自动释放。因此,我们常在存储过程中使用OUTPUT 保留字,将游标传递给该存储过程的调用者,这样在存储过程执行结束后,可以引用该游标变量,在该种情况下,直到引用该游标的最后一个就是被释放时,游标才会自动释放。
GLOBAL
定义游标的作用域是整个会话层会话层指用户的连接时间它包括从用户登录到SQLSERVER 到脱离数据库的整段时间。选择GLOBAL 表明在整个会话层的任何存储过程、触发器或批处理中都可以使用该游标,只有当用户脱离数据库、时该游标才会被自动释放。
注意:如果既未使用GLOBAL也未使用LOCAL,那么SQL SERVER将使用default local cursor数据库选项,为了与以彰的版本歉容,该选项常设置为FALSE。
FORWARD_ONLY
选项指明在从游标中提取数据记录时,只能按照从第一行到最后一行的顺序,此时只能选用FETCH NEXT 操作。除非使用STATIC, KEYSET 和DYNAMIC 关键字,否则如果未指明是使用FORWARD_ONLY 还是使用SCROLL, 那么FORWARD_ONLY 将成为缺省选项,因为若使用STATIC KEYSET 和DYNAMIC 关键字,则变成了SCROLL 游标。另外如果使用了FORWARD_ONLY, 便不能使用FAST_FORWARD。
STATIC
选项的含义与INSENSITIVE 选项一样,MS SQL SERVER 会将游标定义所选取出来的数据记录存放在一临时表内(建立在tempdb 数据库下)。对该游标的读取操作皆由临时表来应答。因此对基本表的修改并不影响游标中的数据,即游标不会随着基本表内容的改变而改变,同时也无法通过游标来更新基本表。
KEYSET
指出当游标被打开时,游标中列的顺序是固定的,并且MS SQL SERVER 会在tempdb内建立一个表,该表即为KEYSET KEYSET 的键值可惟一识别游标中的某行数据。当游标拥有者或其它用户对基本表中的非键值数据进行修改时,这种变化能够反映到游标中,所以游标用户或所有者可以通过滚动游标提限这些数据。
当其它用户增加一条新的符合所定义的游标范围的数据时,无法由此游标读到该数据。因为Transact-SQL 服务器游标不支持INSERT 语句。
如果在游标中的某一行被删除掉,那么当通过游标来提取该删除行时,@@FETCH_STATUS 的返回值为-2。 @@FETCH_STATUS 是用来判断读取游标是否成功的系统全局变量。
由于更新操作包括两部分:删除原数据插入新数据,所以如果读取原数据,@@FETCH_STATUS 的返回值为-2; 而且无法通过游标来读取新插入的数据。但是如果使用了WHERE CURRENT OF 子句时,该新插入行数据便是可见的。
注意:如果基础表未包含惟一的索引或主键,则一个KEYSET游标将回复成STATIC游标。
DYNAMIC
指明基础表的变化将反映到游标中,使用这个选项会最大程度上保证数据的一致性。然而,与KEYSET 和STATIC 类型游标相比较,此类型游标需要大量的游标资源。
FAST_FORWARD
指明一个FORWARD_ONLY, READ_ONLY 型游标。此选项已为执行进行了优化。如果SCROLL 或FOR_UPDATE 选项被定义,则FAST_FORWARD 选项不能被定义。
SCROLL_LOCKS
指明锁被放置在游标结果集所使用的数据上当。数据被读入游标中时,就会出现锁。这个选项确保对一个游标进行的更新和删除操作总能被成功执行。如果FAST_FORWARD选项被定义,则不能选择该选项。另外,由于数据被游标锁定,所以当考虑到数据并发处理时,应避免使用该选项。
OPTIMISTIC
指明在数据被读入游标后,如果游标中某行数据已发生变化,那么对游标数据进行更新或删除可能会导致失败。如果使用了FAST_FORWARD 选项,则不能使用该选项。
TYPE_WARNING
指明若游标类型被修改成与用户定义的类型不同时,将发送一个警告信息给客户端。
注意:不可以将SQL_92的游标语法规则与MS SQL SERVER的游标扩展用法混合在一起使用。
下面我们将总结一下声明游标时应注意的一些问题。
如果在CURSOR 前使用了SCROLL 或INSENSITIVE 保留字,则不能在CURSOR 和FOR select_statement 之间使用任何的保留字。反之同理。
如果用DECLARE CURSOR 声明游标时,没有选择READ_ONLY、 OPTIMISTIC 或SCROLL_LOCKS 选项时,游标的缺省情况为:
如果SELECT 语句不支持更新,则游标为READ_ONLY;
STATIC 和FAST_FORWARD 类型的游标缺省为READ_ONLY;
DYNAMIC 和KEYSET 游标缺省为OPTIMISTIC。
我们仅能在Transact-SQL 语句中引用游标,而不能在数据库API 函数中引用。
游标被声明以后,可以通过系统过程对其特性进行设置。
对那些有权限对视图、表或某些列执行SELECT 语句的用户而言,它也具有使用游标的缺省权限。
打开游标游标在声明以后,如果要从游标中读取数据必须打开游标。打开一个Transact-SQL服务器游标使用OPEN 命令,其语法规则为:
OPEN { { [GLOBAL] cursor_name } | cursor_variable_name}
各参数说明如下:
GLOBAL
定义游标为一全局游标。
cursor_name
为声明的游标名字。如果一个全局游标和一个局部游标都使用同一个游标名,则如果使用GLOBAL 便表明其为全局游标,否则表明其为局部游标。
cursor_variable_name
为游标变量。当打开一个游标后时,MS SQL SERVER 首先检查声明游标的语法是否正确,如果游标声明中有变量,则将变量值带入。
在打开游标时,如果游标声明语句中使用了INSENSITIVE 或STATIC 保留字,则OPEN产生一个临时表来存放结果集;如果在结果集中任何一行数据的大小超过MS SQL SERVER定义的最大行尺寸时,OPEN 命令将失败;如果声明游标时作用了KEYSET 选项,则OPEN 产生一个临时表来存放键值。所有的临时表都存在tempdb 数据库中。
在游标被成功打开之后,@@CURSOR_ROWS 全局变量将用来记录游标内数据行数。为了提高性能,MS SQL SERVER 允许以异步方式从基础表向KEYSET 或静态游标读入数据,即如果MS SQL SERVER 的查询优化器估计从基础表中返回给游标的数据行已经超过sp_configure cursor threshold 参数值,则MS SQL SERVER 将启动另外一个独立的线程来继续从基础表中读入符合游标定义的数据行,此时可以从游标。中读取数据进行处理而不必等到所有的符合游标定义的数据行都从基础表中读入游标 @@CURSOR_ROWS 变量存储的正是在调用@@CURSOR_ROWS 时,游标已从基础表读入的数据行。@@CURSOR_ROWS 的返回值有以下四个,如表13-1 所示。
如果所打开的游标在声明时带有SCROLL 或INSENSITIVE 保留字,那么@@CURSOR_ROWS 的值为正数且为该游标的所有数据行。如果未加上这两个保留字中的一个,则@@CURSOR_ROWS 的值为-1, 说明该游标内只有一条数据记录。
当游标被成功打开以后,就可以从游标中逐行地读取数据,以进行相关处理。从游标中读取数据主要使用FETCH 命令。其语法规则为:
各参数含义说明如下:
NEXT
返回结果集中当前行的下一行,并增加当前行数为返回行行数。如果FETCH NEXT是第一次读取游标中数据,则返回结果集中的是第一行而不是第二行。
PRIOR
返回结果集中当前行的前一行,并减少当前行数为返回行行数。如果FETCH PRIOR是第一次读取游标中数据,则无数据记录返回,并把游标位置设为第一行。
FIRST
返回游标中第一行。
LAST
返回游标中的最后一行。
ABSOLUTE {n | @nvar}
如果n 或@nvar 为正数,则表示从游标中返回的数据行数。如果n 或@nvar 为负数,则返回游标内从最后一行数据算起的第n 或@nvar 行数据。若n 或@nvar 超过游标的数据子集范畴,则@@FETCH_STARS 返回-1, 在该情况下,如果n 或@nvar 为负数,则执行FETCH NEXT 命令会得到第一行数据,如果n 或@nvar为正值,执行FETCH PRIOR 命令则会得到最后一行数据。n 或@nvar 可以是一固定值也可以是一smallint, tinyint 或int 类型的变量。
RELATIVE {n | @nvar}
若n 或@nvar 为正数,则读取游标当前位置起向后的第n 或@nvar 行数据;如果n 或@nvar 为负数,则读取游标当前位置起向前的第n 或@nvar 行数据。若n 或@nvar 超过游标的数据子集范畴,则@@FETCH_STARS 返回-1, 在该情况下,如果n 或@nvar 为负数,则执行FETCH NEXT 命令则会得到第一行数据;如果n 或@nvar 为正值,执行FETCH PRIOR 命令则会得到最后一行数据。n 或@nvar 可以是一固定值也可以是一smallint, tinyint或int 类型的变量。
INTO @variable_name[,...n]
允许将使用FETCH 命令读取的数据存放在多个变量中。在变量行中的每个变量必须与游标结果集中相应的列相对应,每一变量的数据类型也要与游标中数据列的数据类型相匹配。
@@FETCH_STATUS 全局变量返回上次执行FETCH 命令的状态。在每次用FETCH从游标中读取数据时,都应检查该变量,以确定上次FETCH 操作是否成功,来决定如何进行下一步处理。@@FETCH_STATUS 变量有三个不同的返回值,如表13-2。
在使用FETCH 命令从游标中读取数据时,应该注意以下的情况:
当使用SQL-92 语法来声明一个游标时,没有选择SCROLL 选项时,只能使用FETCH NEXT 命令来从游标中读取数据,即只能从结果集第一行按顺序地每次读取一行,由于不能使用FIRST、 LAST、 PRIOR, 所以无法回滚读取以前的数据。如果选择了SCROLL 选项,则可能使用所有的FETCH 操作。
当使用MS SQL SERVER 的扩展语法时,必须注意以下约定:
如果定义了FORWARD-ONLY 或FAST_FORWARD 选项,则只能使用FETCH NEXT命令;
如果没有定义DYNAMIC, FORWARD_ONLY 或FAST_FORWARD 选项,而定义了KEYSET, STATIC 或SCROLL 中的任何一个,则可使用所有的FETCH 操作;DYNAMIC SCROLL 游标支持所有的FETCH, 选项但禁用ABSOLUTE 选项。
关闭游标
1、使用CLOSE 命令关闭游标
在处理完游标中数据之后必须关闭游标来释放数据结果集和定位于数据记录上的锁。CLOSE 语句关闭游标,但不释放游标占用的数据结构。如果准备在随后的使用中再次打开游标,则应使用CLOSE 命令。其关闭游标的语法规则为:
CLOSE { { [GLOBAL] cursor_name } | cursor_variable_name }
2、自动关闭游标
我们已经了解到游标可应用在存储过程、触发器和Transact_SQL脚本中。如果在声明游标与释放游标之间使用了事务结构,则在结束事务时游标会自动关闭。其具体的情况如下所示:
(1)、声明一个游标
(2)、打开游标
(3)、读取游标
(4)、BEGIN TRANSATION
(5)、数据处理
(6)、COMMIT TRANSATION
(7)、回到步骤3
在这样的应用环境中。当从游标中读取一条数据记录进行以BEGIN TRANSATION为开头,COMMIT TRANSATION 或ROLLBACK 为结束的事务处理时,在程序开始运行后,第一行数据能够被正确返回,经由步骤7, 程序回到步骤3, 读取游标的下一行,此时常会发现游标未打开的错误信息。其原因就在于当一个事务结束时,不管其是以COMMIT TRANSATION 还是以ROLLBACK TRANSATION 结束,MS SQL SERVER 都会自动关闭游标,所以当继续从游标中读取数据时就会造成错误。
解决这种错误的方法就是使用SET 命令将CURSOR_CLOSE_ON_COMMIT 这一参数设置为OFF 状态。其目的就是让游标在事务结束时仍继续保持打开状态,而不会被关闭。使用SET 命令的格式为:
SET CURSOR_CLOSE_ON_COMMIT OFF
释放游标
在使用游标时,各种针对游标的操作或者引用游标名,或者引用指向游标的游标变量。当CLOSE 命令关闭游标时,并没有释放游标占用的数据结构。因此常使用DEALLOCATE 命令。通过该命令可以删除掉游标与游标名或游标变量之间的联系,并且释放游标占用的所有系统资源。其语法规则为:
DEALLOCATE { { [GLOBAL] cursor_name } | @cursor_variable_name}
当使用DEALLOCATE @cursor_variable_name 来删除游标时,游标变量并不会被释放,除非超过使用该游标的存储过程、触发器的范围(即游标的作用域)。