SQL Server中的游标介绍


Posted in SQL Server onMay 20, 2022

游标是面向行的,它会使开发人员变懒,懒得去想用面向集合的查询方式实现某些功能。

在性能上,游标会吃更多的内存,减少可用的并发,占用带宽,锁定资源,当然还有更多的代码量。用一个比喻来说明为什么游标会占用更多的资源。当你从ATM机取款的时候,是一次取1000的效率更高呢,还是10次100呢?

游标是非常邪恶的一种存在,使用游标经常会比使用面向集合的方法慢2-3倍,当游标定义在大数据量时,这个比例还会增加。如果可能,尽量使用while,子查询,临时表,函数,表变量等来替代游标,记住,游标永远只是你最后无奈之下的选择,而不是首选。

既然游标那么多缺点,为什么要学习游标呢?

  • 现存系统有一些游标,我们查询必须通过游标来实现。
  • 作用一个备用方式,当使用while、子查询,临时表,表变量,自建函数或其他方式仍然无法实现某些查询的时候,可以使用游标实现。

游标的定义语法:

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中,定义一个游标可以使非常简单,也可以相对复杂,这主要取决于游标的参数。而游标的参数设置取决于你对游标原理的了解程度。 
游标其实可以理解成一个定义在特定数据集上的指针,我们可以控制这个指针遍历数据集,或者仅仅是指向特定的行,所以游标是定义在以SELECT开始的数据集上的。

SQL Server中的游标介绍

游标分为游标类型和游标变量。

游标变量支持两种方式赋值,定义时赋值和先定义后赋值,定义游标变量像定义其他局部变量一样,在游标前加”@”。

注意,如果定义全局的游标,只支持定义时直接赋值,并且不能在游标名称前面加“@”。

两种定义方式如下:

--定义后直接赋值
declare test_Cursor cursor for
    select * from Person;

--先定义后赋值
declare @TEST_Cursor2 cursor;

set @TEST_Cursor2 = cursor for
    select * from Person;

参数解释:

1、LOCAL和GLOBAL二选一

如果不指定游标作用域,默认作用域为GLOBAL。

--全局游标,跨GLOBAL
declare test_Cursor cursor global for
    select * from Person;

--局部游标,跨LOCAL
declare test_Cursor2 cursor local for
    select * from Person;

go --用GO结束上面的作用域

open test_Cursor;
open test_Cursor2; --此行代码报错,报游标不存在,因此可以理解局部游标不跨批处理,批处理结束后,将被隐式释放,无法在其他批处理中调用

2、FORWARD_ONLY 和 SCROLL 二选一

  • FORWARD_ONLY意味着游标只能从数据集开始向数据集结束的方向读取,FETCH NEXT是唯一的选项。默认为Forward_Only。
  • SCROLL支持游标在定义的数据集中向任何方向,或任何位置移动。
--不加参数,默认为Forward_Only
declare test_Cursor cursor for
    select * from Person;

--加Forward_Only
declare test_Cursor2 cursor forward_only for
    select * from Person;

--加SCROLL
declare test_Cursor3 cursor scroll for
    select * from Person;

open test_Cursor;
open test_Cursor2;
open test_Cursor3;

fetch last from test_Cursor; --报错 fetch: 提取类型 last 不能与只进游标一起使用。
fetch last from test_Cursor2; --报错 fetch: 提取类型 last 不能与只进游标一起使用。
fetch last from test_Cursor3; --正确执行

3、游标的分类:STATIC、 KEYSET 、DYNAMIC 和 FAST_FORWARD 四选一

这四个关键字是游标所在数据集所反映的表数据和游标读取出数据的关系

  • STATIC:当游标被建立时,将会创建FOR后面的SELECT语句所包含数据集的副本存入tempdb数据库中,任何对于底层表内数据的更改不会影响到游标内容。
  • DYNAMIC:和STATIC完全相反的选项,当底层数据库更改时,游标的内容也会随之得到反映,在下一次fecth中,数据内容会随之更改。
  • KEYSET:可以理解为介于STATIC和DYNAMIC的折中方案,将游标所在结果集的唯一能确定每一行的主键存入tempdb,当结果集中任何行改变或者删除时,@@FETCH_STATUS会为-2,KEYSET无法探测新加入的数据。
  • FAST_FORWARD:可以理解为FORWARD_ONLY的优化版本。FORWARD_ONLY执行的是静态计划,而FAST_FORWARD是根据情况进行选择采用动态计划还是静态计划,大多数情况下FAST_FORWARD要比FORWARD_ONLY性能略好。

4、READ_ONLY 、 SCROLL_LOCKS 和 OPTIMISTIC 三选一

  • READ_ONLY:意味着声明的游标只能读取数据,游标不能做任何更新操作 。
  • SCROLL_LOCKS:是另一种极端,将读入游标的所有数据进行锁定,防止其他程序进行更改,以确保更新的绝对成功。
  • OPTIMISTIC:相对比较好的一个选择,OPTIMISTIC不锁定任何数据,当需要在游标中更新数据时,如果底层表数据更新,则游标内数据更新不成功,如果,底层表数据未更新,则游标内表数据可以更新。

5、For Update[of column_name ,....] :定义游标中可更新的列。

二、打开游标

当定义完游标后,游标需要打开后使用,只需一行代码便可打开游标:

OPEN test_Cursor

注意,当全局游标和局部游标变量重名时,默认会打开局部变量游标。

三、使用游标

1、利用游标提取数据

游标的使用分为两部分,一部分是操作游标在数据集内的指向,另一部分是将游标所指向的行的部分或全部内容进行操作。 
支持6种移动导航,分别为:

  • 第一行(FIRST)
  • 最后一行(LAST)
  • 下一行(NEXT)
  • 上一行(PRIOR)
  • 直接跳到某行(ABSOLUTE(n))
  • 相对于目前跳几行(RELATIVE(n))

例如:

declare test_Cursor cursor scroll for
    select name from Person;

open test_Cursor;

declare @c nvarchar(10);

--取下一行
fetch next from test_Cursor into @c;
print @c;

--取最后一行
fetch last from test_Cursor into @c;
print @c;

--取第一行
fetch first from test_Cursor into @c;
print @c;

--取上一行
fetch prior from test_Cursor into @c;
print @c;

--取第三行
fetch absolute 3 from test_Cursor into @c;
print @c;

--取相对目前来说上一行
fetch relative -1 from test_Cursor into @c;
print @c;

对于未指定SCROLL选项的游标来说(未指定,则是只进游标),只支持NEXT取值。

游标经常会和全局变量@@FETCH_STATUS与WHILE循环来共同使用,以达到遍历游标所在数据集的目的。

当执行一条Fetch语句之后,@@Fetch_Status可能出现3种值:

  • 0,Fetch语句成功。
  • -1:Fetch语句失败或行不在结果集中。
  • -2:提取的行不存在。

游标总记录数 @@CURSOR_ROWS

例如:

declare test_Cursor cursor fast_forward  for
    select id, name from Person;

open test_Cursor;

declare @id int;
declare @name nvarchar(10);

fetch next from test_Cursor into @id, @name;

while @@FETCH_STATUS = 0
    begin
        print @id;
        print @name;

        fetch next from test_Cursor into @id, @name;
    end;

close test_Cursor;
deallocate test_Cursor;

2、利用游标更新删除数据

游标修改当前行数据语法:

Update 基表名 Set 列名=值[,...] Where Current of 游标名

游标删除当前数行据语法:

Delete 基表名  Where Current of 游标名

举例:

---1.声明游标
declare orderNum_03_cursor cursor scroll for
    select OrderId, userId from bigorder where orderNum = 'ZEORD003402';

--2.打开游标
open orderNum_03_cursor;

--3.声明游标提取数据所要存放的变量
declare @OrderId int, @userId varchar(15);

--4.定位游标到哪一行
fetch first from orderNum_03_cursor  into @OrderId, @userId; -- into的变量数量必须与游标查询结果集的列数相同

while @@fetch_status = 0 --提取成功,进行下一条数据的提取操作 
    begin
        if @OrderId = 122182
            begin
                update bigorder set UserId = '123' where current of orderNum_03_cursor; --修改当前行
            end;

        if @OrderId = 154074
            begin
                delete bigorder where current of orderNum_03_cursor; --删除当前行
            end;

        fetch next from orderNum_03_cursor
        into @OrderId, @userId; --移动游标
    end;

close orderNum_03_cursor;
deallocate orderNum_03_cursor;

四、关闭游标

在游标使用完之后,一定要记得关闭,只需要一行代码:CLOSE+游标名称

close  test_Cursor

五、释放游标

当游标不再需要被使用后,释放游标,只需要一行代码:DEALLOCATE+游标名称

deallocate test_Cursor

六、对于游标一些优化建议

  • 如果能不用游标,尽量不要使用游标
  • 用完之后一定要关闭和释放
  • 尽量不要在大量数据上定义游标
  • 尽量不要使用游标上更新数据
  • 尽量不要使用insensitive, static和keyset这些参数定义游标
  • 如果可以,尽量使用FAST_FORWARD关键字定义游标
  • 如果只对数据进行读取,当读取时只用到FETCH NEXT选项,则最好使用FORWARD_ONLY参数

到此这篇关于SQL Server游标的文章就介绍到这了。


Tags in this post...

SQL Server 相关文章推荐
如何有效防止sql注入的方法
May 25 SQL Server
SQLServer 错误: 15404,无法获取有关 Windows NT 组/用户 WIN-8IVSNAQS8T7\Administrator 的信息
Jun 30 SQL Server
sql通过日期判断年龄函数的示例代码
Jul 16 SQL Server
SQL SERVER触发器详解
Feb 24 SQL Server
SQL Server查询某个字段在哪些表中存在
Mar 03 SQL Server
SQL Server远程连接的设置步骤(图文)
Mar 23 SQL Server
SQL Server Agent 服务无法启动
Apr 20 SQL Server
SQL Server中使用表变量和临时表
May 20 SQL Server
SQL Server中T-SQL标识符介绍与无排序生成序号的方法
May 25 SQL Server
SQL使用复合索引实现数据库查询的优化
May 25 SQL Server
SQL中的连接查询详解
Jun 21 SQL Server
SQL Server #{}可以防止SQL注入
May 11 #SQL Server
SQL Server 忘记密码以及重新添加新账号
使用 MybatisPlus 连接 SqlServer 数据库解决 OFFSET 分页问题
Apr 22 #SQL Server
使用MybatisPlus打印sql语句
Apr 22 #SQL Server
Sql Server 行数据的某列值想作为字段列显示的方法
SQL Server Agent 服务无法启动
Apr 20 #SQL Server
SQLServer权限之只开启创建表权限
You might like
PHP使用redis实现统计缓存mysql压力的方法
2015/11/14 PHP
yii实现model添加默认值的方法(2种方法)
2016/01/06 PHP
javascript判断用户浏览器插件安装情况的代码
2011/01/01 Javascript
jquery获得option的值和对option进行操作
2013/12/13 Javascript
node.js中实现同步操作的3种实现方法
2014/12/05 Javascript
JavaScript简单修改窗口大小的方法
2015/08/03 Javascript
javascript控制图片播放的实现代码
2020/07/29 Javascript
AngularJS  $modal弹出框实例代码
2016/08/24 Javascript
微信小程序 教程之wxapp 视图容器 view
2016/10/19 Javascript
bootstrap PrintThis打印插件使用详解
2017/02/20 Javascript
微信小程序 检查接口状态实例详解
2017/06/23 Javascript
JQuery 获取Dom元素的实例讲解
2017/07/08 jQuery
简单实现js上传文件功能
2017/08/21 Javascript
jQuery与vue实现拖动验证码功能
2018/01/30 jQuery
jquery应用实例分享_实现手风琴特效
2018/02/01 jQuery
基于vue,vue-router, vuex及addRoutes进行权限控制问题
2018/05/02 Javascript
jQuery+Datatables实现表格批量删除功能【推荐】
2018/10/24 jQuery
详解ES6中的 Set Map 数据结构学习总结
2018/11/06 Javascript
node爬取新型冠状病毒的疫情实时动态
2020/02/06 Javascript
Python cookbook(数据结构与算法)在字典中将键映射到多个值上的方法
2018/02/18 Python
Pytorch GPU显存充足却显示out of memory的解决方式
2020/01/13 Python
解决pycharm不能自动补全第三方库的函数和属性问题
2020/03/12 Python
pyqt5数据库使用详细教程(打包解决方案)
2020/03/25 Python
python 的topk算法实例
2020/04/02 Python
Jupyter Notebook输出矢量图实例
2020/04/14 Python
利用html5 canvas破解简单验证码及getImageData接口应用
2013/01/25 HTML / CSS
面向对象编程OOP的优点
2013/01/22 面试题
业务员岗位职责范本
2013/12/15 职场文书
文秘专业个人求职信
2013/12/22 职场文书
优秀演讲稿范文
2013/12/29 职场文书
航海技术专业毕业生求职信
2014/04/06 职场文书
乔布斯斯坦福大学演讲稿
2014/05/23 职场文书
社团活动总结模板
2014/06/30 职场文书
社区公民道德宣传日活动总结
2015/03/23 职场文书
2015年秋季运动会加油稿
2015/07/22 职场文书
排查并解决Oracle sysaux表空间异常增长
2022/04/20 Oracle