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 Server数据定义——模式与基本表操作
Apr 05 SQL Server
SqlServer 垂直分表(减少程序改动)
Apr 16 SQL Server
sql查询结果列拼接成逗号分隔的字符串方法
May 25 SQL Server
在 SQL 语句中处理 NULL 值的方法
Jun 07 SQL Server
SQL 尚未定义空闲 CPU 条件 - OnIdle 作业计划将不起任何作用
Jun 30 SQL Server
SQL中的三种去重方法小结
Nov 01 SQL Server
MySQL 中如何归档数据的实现方法
Mar 16 SQL Server
SQL Server表分区降低运维和维护成本
Apr 08 SQL Server
sql server偶发出现死锁的解决方法
Apr 10 SQL Server
SQL Server携程核心系统无感迁移到MySQL实战
Jun 01 SQL Server
SQL Server2019安装的详细步骤实战记录(亲测可用)
Jun 10 SQL Server
SQL bool盲注和时间盲注详解
Jul 23 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缓存类分享     php缓存机制
2014/01/22 PHP
PHP屏蔽过滤指定关键字的方法
2014/11/03 PHP
JavaScript中的几个关键概念的理解-原型链的构建
2011/05/12 Javascript
JavaScript高级程序设计(第3版)学习笔记13 ECMAScript5新特性
2012/10/11 Javascript
js Map List 遍历使用示例
2013/07/10 Javascript
js获取日期:昨天今天和明天、后天
2014/06/11 Javascript
jquery delay()介绍及使用指南
2014/09/02 Javascript
JavaScript实现生成GUID(全局统一标识符)
2014/09/05 Javascript
javascipt:filter过滤介绍及使用
2014/09/10 Javascript
使用JavaScript链式编程实现模拟Jquery函数
2014/12/21 Javascript
javascript实现Table排序的方法
2015/05/15 Javascript
JavaScript中调用函数的4种方式代码实例
2015/07/08 Javascript
javascript去掉代码里面的注释
2015/07/24 Javascript
node.js插件nodeclipse安装图文教程
2020/10/19 Javascript
AngularJS基础 ng-open 指令简单实例
2016/08/02 Javascript
jQuery+正则+文本框只能输入数字的实现方法
2016/10/07 Javascript
JS取模、取商及取整运算方法示例
2016/10/13 Javascript
基于JavaScript实现验证码功能
2017/04/01 Javascript
jQuery EasyUI tree增加搜索功能的实现方法
2017/04/27 jQuery
学习使用Bootstrap输入框、导航、分页等常用组件
2017/05/11 Javascript
vue Render中slots的使用的实例代码
2017/07/19 Javascript
百度地图去掉marker覆盖物或者去掉maker的label文字方法
2018/01/26 Javascript
微信小程序实现收藏与取消收藏切换图片功能
2018/08/03 Javascript
vue watch普通监听和深度监听实例详解(数组和对象)
2018/08/16 Javascript
vue.js实现三级菜单效果
2019/10/19 Javascript
解决VUE项目使用Element-ui 下拉组件的验证失效问题
2020/11/07 Javascript
python登录pop3邮件服务器接收邮件的方法
2015/04/30 Python
python微信跳一跳系列之棋子定位像素遍历
2018/02/26 Python
Python使用装饰器模拟用户登陆验证功能示例
2018/08/24 Python
Jupyter notebook设置背景主题,字体大小及自动补全代码的操作
2020/04/13 Python
Discard Protocol抛弃协议的作用是什么
2015/10/10 面试题
甜点店创业计划书
2014/01/27 职场文书
房地产开发项目建议书
2014/05/16 职场文书
维稳工作承诺书
2015/01/20 职场文书
财务工作失误检讨书
2015/02/19 职场文书
小学副班长竞选稿
2015/11/21 职场文书