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 相关文章推荐
SQLServer2019 数据库的基本使用之图形化界面操作的实现
Apr 08 SQL Server
sqlserver2017共享功能目录路径不可改的解决方法
Apr 16 SQL Server
mybatis调用sqlserver存储过程返回结果集的方法
May 08 SQL Server
SQLServer 错误: 15404,无法获取有关 Windows NT 组/用户 WIN-8IVSNAQS8T7\Administrator 的信息
Jun 30 SQL Server
SQL语句中JOIN的用法场景分析
Jul 25 SQL Server
SQL写法--行行比较
Aug 23 SQL Server
使用SQL实现车流量的计算的示例代码
Feb 28 SQL Server
详解在SQLPlus中实现上下键翻查历史命令的功能
Mar 18 SQL Server
SQL Server中的逻辑函数介绍
May 25 SQL Server
SQL Server2019安装的详细步骤实战记录(亲测可用)
Jun 10 SQL Server
一次SQL查询优化原理分析(900W+数据从17s到300ms)
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 向右侧拉菜单实现代码,测试使用中
2009/11/03 PHP
php 面向对象的一个例子
2011/04/12 PHP
用Simple Excel导出xls实现方法
2012/12/06 PHP
百度ping方法使用示例 自动ping百度
2014/01/26 PHP
利用PHP如何统计Nginx日志的User Agent数据
2019/03/06 PHP
php根据命令行参数生成配置文件详解
2019/03/15 PHP
JavaScript的Function详细
2006/11/14 Javascript
Add a Picture to a Microsoft Word Document
2007/06/15 Javascript
jQuery 操作XML入门
2008/12/25 Javascript
跨浏览器通用、可重用的选项卡tab切换js代码
2011/09/20 Javascript
Extjs优化(二)Form表单提交通用实现
2013/04/15 Javascript
jquery简单实现滚动条下拉DIV固定在头部不动
2013/11/25 Javascript
写出高效jquery代码的19条指南
2014/03/19 Javascript
JavaScript实现找出数组中最长的连续数字序列
2014/09/03 Javascript
node.js中的fs.utimesSync方法使用说明
2014/12/15 Javascript
浅谈vue中.vue文件解析流程
2018/04/24 Javascript
微信小程序自定义底部弹出框
2020/11/16 Javascript
微信小程序使用wxParse解析html的方法示例
2019/01/17 Javascript
原生javascript自定义input[type=radio]效果示例
2019/08/27 Javascript
原生js实现贪食蛇小游戏的思路详解
2019/11/26 Javascript
javascript canvas API内容整理
2020/02/16 Javascript
[34:08]2018DOTA2亚洲邀请赛3月29日 小组赛B组 VP VS EG
2018/03/30 DOTA
Python中的引用和拷贝浅析
2014/11/22 Python
Python中的上下文管理器和with语句的使用
2018/04/17 Python
Python 图像对比度增强的几种方法(小结)
2019/09/25 Python
Python实现快速大文件比较代码解析
2020/09/04 Python
python爬虫泛滥的解决方法详解
2020/11/25 Python
药剂学专业应届生自荐信
2013/09/29 职场文书
美术国培研修感言
2014/02/12 职场文书
酒店员工检讨书
2014/02/18 职场文书
踏青活动策划方案
2014/08/19 职场文书
《观察物体》教学反思
2016/02/17 职场文书
创业计划书介绍
2019/04/24 职场文书
bat批处理之字符串操作的实现
2022/03/16 Python
Python进程间的通信之语法学习
2022/04/11 Python
Python tensorflow卷积神经Inception V3网络结构
2022/05/06 Python