数据库常见面试题 —— 8. 游标 (CURSOR) 的定义、分类和使用

数据库常见面试题 —— 8. 游标 (CURSOR) 的定义、分类和使用

文章目录

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

查询结果:

🎈 相关推荐

这18种植物具有强大的美容功能
bob365

这18种植物具有强大的美容功能

📅 07-27 👀 8512
qq飞车手游勋章多久出一期 每期勋章上线时间介绍
网彩365平台下载

qq飞车手游勋章多久出一期 每期勋章上线时间介绍

📅 09-12 👀 4701
财务预算怎么做才能靠谱?这5个实用方法快来收藏