文章目录
1. 游标的定义2. 游标的特性3. 游标的分类4. 使用游标5. 创建游标6. 游标的使用模式6.1 声明游标6.2 打开游标6.3 检索数据6.4 处理数据6.5 循环处理6.6 关闭和释放游标7. 实际应用示例1 (使用游标进行数据的更新或插入)示例2 (使用游标实现分页)
1. 游标的定义
游标(Cursor)是一个存储在DBMS服务器上的数据库查询,它不是一条SELECT语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。
2. 游标的特性
① 能够标记游标为只读,使数据能读取,但不能更新和删除 ② 能控制可以执行的定向操作(向前、向后、第一、最后、绝对位置、相对位置等) ③能标记某些列是可编辑的,某些列为不可编辑的 ④ 规定范围,使游标对创建它的特定请求(如存储过程)或对所有请求可访问 ⑤ 指示DBMS对检索出的数据(而不是指表中活动数据)进行复制,使数据在游标打开和访问期间不变化
3. 游标的分类
(1) 显式游标 显示游标一次从数据库中提取多行数据 (2) 隐式游标 隐式游标一般只从数据库中提取一行数据
4. 使用游标
① 在使用游标前,必须声明(定义)它。这个过程实际上没有检索数据它只是定义要使用的SELECT 语句和游标选项 ② 一旦声明就必须打开游标以供使用 ③ 对于填写数据的游标,根据需要取出各行 ④ 在结束游标使用时,必须关闭游标,可能的话,释放游标
5. 创建游标
MySQL 和 SQL Server
declare CustCusor CURSOR
FOR
SELECT * FROM Customers
where cust_email is null
Oracle
declare cursor custcursor
is
select * from customers
where cust_email is null
6. 游标的使用模式
6.1 声明游标
首先,需要使用 DECLARE 语句声明游标,并定义游标的名称、数据类型和查询语句等属性。 例如:
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 ] ] ]
[;]
DECLARE my_cursor CURSOR FOR SELECT col1, col2 FROM my_table;
① 游标的作用域 LOCAL 说明所声明的游标为局部的,其作用域为创建它的批处理、存储过程或触发器,即在批处理、调用它的存储过程或触发器执行完成后,该游标被系统隐式释放。但,若游标作为存储过程OUTPUT 的输出参数,在存储过程终止后给游标变量分配参数可以继续引用游标,如果 OUTPUT 参数将游标传递回来,则游标在最后引用它的变量释放或离开作用域时释放。
GLOBAL 指定该游标的作用域对来说连接是全局的。在由连接执行的任何存储过程或批处理中,都可以引用该游标名称。该游标仅在断开连接时隐式释放。
注意:如果 GLOBAL 和 LOCAL 参数都未指定,则默认值由 default to local cursor 数据库选项的设置控制。在 SQL Server 7.0 版中,该选项默认为 FALSE,以便与 SQL Server 的早期版本匹配,在早期版本中,所有游标都是全局的。
② 游标方向 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。
③ 游标类型 STATIC 定义一个游标,以创建将由该游标使用的数据的临时复本。对游标的所有请求都从 tempdb 中的这一临时表中得到应答;因此,在对该游标进行提取操作时返回的数据中不反映对基表所做的修改,并且该游标不允许修改。及该游标是只读的。
DYNAMIC 定义一个游标,以反映在滚动游标时对结果集内的各行所做的所有数据更改。行的数据值、顺序和成员身份在每次提取时都会更改。
KEYSET 指定当游标打开时,游标中行的成员身份和顺序已经固定。对行进行唯一标识的键集内置在 tempdb 内一个称为 keyset 的表中。
FAST_FORWARD 指定启用了性能优化的 FORWARD_ONLY、READ_ONLY 游标。如果指定了 SCROLL 或 FOR_UPDATE,则不能也指定 FAST_FORWARD。
6.2 打开游标
使用 OPEN 语句打开游标,准备开始处理数据。 例如:
OPEN my_cursor;
在打开游标之后,可以使用全局变量@@CURSOR_ROWS来显示游标内的记录条数,使用全局变量@@FETCH_STATUS来返回上一条游标FETCH语句的状态。
@@FETCH_STATUS 是一个系统变量,返回一个整数,用于指示最后一次 FETCH 语句的执行结果,并根据该结果执行某些操作。
如果 FETCH 语句成功检索到了一行或多行数据,则 @@FETCH_STATUS 的值为 0。如果 FETCH 语句未检索到任何数据,则 @@FETCH_STATUS 的值为 -2。如果 FETCH 语句发生错误,则 @@FETCH_STATUS 的值为 -1。
用户可以利用 @@FETCH_STATUS 的返回结果,来判断是否还有更多的数据需要获取(@@FETCH_STATUS 的值为 0 意味着还有更多数据,否则表示获取完毕),并决定是否继续执行 FETCH 语句或退出循环。
6.3 检索数据
使用 FETCH 语句从游标中检索数据,可以一次检索一行或多行数据,并将数据存储在变量中进行处理。 例如:
FETCH NEXT FROM my_cursor INTO @col1, @col2;
--读取当前行的下一行,并使其置为当前行(刚开始时游标置于表头的前一行,即若表是从0开始的,游标最初置于-1处,所以第一次读取的是头一行)
fetch next from my_cursor
--读取当前行的前一行,并使其置为当前行
fetch prior from my_cursor
--读取游标的第一行,并使其置为当前行(不能用于只进游标)
fetch first from my_cursor
--读取游标的最后一行,并使其置为当前行(不能用于只进游标)
fetch last from my_cursor
--读取从游标头开始向后的第2行,并将读取的行作为新的行
fetch absolute 2 from my_cursor
--读取从当前行开始向后的第3行,并将读取的行作为新的行
fetch relative 3 from my_cursor
--读取当前行的上两行,并将读取的行作为新的行
fetch relative-2 from my_cursor
◆ NEXT:当前记录的下一条记录。 ◆ PRIOR:当前记录的上一条记录。 ◆ FIRST:游标中第一条记录。 ◆ LAST:游标中最后一条记录。 ◆ ABSOLUTE:游标中指定位置的记录,即绝对位置。 ◆ RELATIVE:相对于当前位置的记录,即相对位置。
6.4 处理数据
在每次检索数据之后,使用变量中的数据执行所需的操作,例如,对数据进行计算、逻辑判断、修改等。
6.5 循环处理
通常使用循环结构(例如 WHILE、LOOP)来遍历游标中的所有数据,并在每次循环迭代中执行相应的操作。
6.6 关闭和释放游标
在处理完所有数据之后,使用 CLOSE 和 DEALLOCATE 语句关闭和释放游标。 例如:
CLOSE my_cursor;
DEALLOCATE my_cursor;
7. 实际应用
示例1 (使用游标进行数据的更新或插入)
公交车和乘客到达 LeetCode 站。如果一辆公交车在 tbus 时间点到达车站,乘客在 tpassenger 到达车站,其中 tpassenger <= tbus,而该乘客没有赶上任何公交车,则该乘客将搭乘该公交车。此外,每辆公交车都有一个容量。如果在公交车到站的那一刻,等待的乘客超过了它的载客量 capacity,只有 capacity 个乘客才会搭乘该公交车。 编写解决方案,报告使用每条总线的用户数量。 返回按 bus_id 升序排序 的结果表。 结果格式如下所示。
--建表
if object_id('Buses','u') is not null drop table Buses
go
create table Buses(
bus_id int
, arrival_time int
, capacity int
)
go
insert into Buses
values
( 1 ,2 ,1 )
,( 2 ,4 ,10 )
,( 3 ,7 ,2 )
go
if object_id('Passengers','u') is not null drop table Passengers
go
create table Passengers(
passenger_id int
, arrival_time int
)
go
insert into Passengers
values
( 11 ,1 )
,( 12 ,1 )
,( 13 ,5 )
,( 14 ,6 )
,( 15 ,7 )
go
--查询
alter table Passengers add Flag int not null default 0
,bus_id int
--select * from Passengers
declare @bus_id int,@arrival_time int ,@capacity int,@i int
--声明游标
declare C cursor for
select bus_id, arrival_time,capacity from Buses order by arrival_time
--打开游标
open C
--从游标中检索数据,可以一次检索一行或多行数据,并将数据存储在变量中进行处理。
fetch next from C into @bus_id,@arrival_time,@capacity
--如果 FETCH 语句成功检索到了一行或多行数据,则 @@FETCH_STATUS 的值为 0
while @@FETCH_STATUS=0
begin
update a
set bus_id = @bus_id,flag = 1
from Passengers a
inner join (select * from (select passenger_id,arrival_time,ROW_NUMBER()over(order by arrival_time)Rnk
from Passengers
where arrival_time <= @arrival_time and flag = 0)a
where Rnk<=@capacity)b
on a.passenger_id = b.passenger_id
fetch next from C into @bus_id,@arrival_time,@capacity
end
close C
deallocate C
select a.bus_id,count(b.passenger_id) as passengers_cnt from Buses a
left join Passengers b
on a.bus_id = b.bus_id
group by a.bus_id
示例2 (使用游标实现分页)
--建表
if object_id ('Student','U') is not null drop table Student
GO
create table Student (
Student_id int identity (1,1)
,Student_Name varchar(20)
)
go
insert into Student (Student_Name)
values
('Lily' )
,('Andrew' )
,('Sophia' )
,('Benjamin' )
,('Ava' )
,('Richard' )
,('Abigail' )
,('Charles' )
,('Emma' )
,('Alexander' )
,('Isabella' )
,('Daniel' )
,('Madison' )
,('Joseph' )
,('Mia' )
,('Samuel' )
,('Charlotte' )
,('George' )
,('Ella' )
,('Edward' )
,('Amelia' )
,('Henry' )
,('Aria' )
,('Thomas' )
,('Layla' )
,('Katherine' )
,('Scarlett' )
,('Christopher' )
,('Riley' )
,('Timothy' )
,('Zoey' )
,('Steven' )
,('Penelope' )
,('Marcus' )
,('Avery' )
go
select * from Student
--Output
alter proc splitPage
@pageIndex int --分页后的第几页
,@pageSize int --分页后每页有多少条数据
as
begin
declare @table table (
Student_ID INT
,Student_Name varchar(20) )
declare cur cursor scroll for select Student_Id,Student_Name from Student
declare @count int , @name varchar(20) , @no int
set @count=(@pageIndex-1) * @pageSize + 1
open cur
fetch absolute @count from cur into @no,@name
while @count <= @pageSize * @pageIndex and @@FETCH_STATUS=0
begin
insert into @table
values
(@no ,@name)
set @count = @count+1
fetch absolute @count from cur into @no,@name end
end
close cur
deallocate cur
select * from @table
end
--Run
exec splitPage 2,10
查询结果: