达梦 DMSQL 游标全解析:从隐式游标、显式游标到动态游标与游标变量
引言:为什么需要游标
在 DMSQL 程序(达梦数据库的过程化 SQL 语言)中,最直观的取数方式是 SELECT ... INTO 把查询结果装进变量。但它有一个硬伤:只能接收一条记录。
- 查不到数据 → 抛出预定义异常
NO_DATA_FOUND(-7065); - 查到多行 → 抛出预定义异常
TOO_MANY_ROWS(-7046)。
DECLARE
p_name VARCHAR(50);
BEGIN
-- 作者"曹雪芹,高鹗"只对应一行,OK
SELECT NAME INTO p_name FROM PRODUCTION.PRODUCT
WHERE AUTHOR LIKE '曹雪芹,高鹗';
PRINT p_name;
EXCEPTION
WHEN NO_DATA_FOUND OR TOO_MANY_ROWS THEN
PRINT 'NO_DATA_FOUND OR TOO_MANY_ROWS';
END;
/
现实业务里,我们经常要"逐行"处理一个多行结果集——比如遍历某个部门所有员工、批量汇总报表。这时就需要 游标(Cursor):它像一个指向结果集的"指针",允许程序一行一行地拨动、读取数据。
一、游标是什么
游标是 DMSQL 中用来对多行结果集进行逐条处理的机制。理解它,先记住三组概念:
- 游标(Cursor):指向一个查询结果集的指针,程序通过它逐行访问数据。
- 游标变量(Cursor Variable):不是真正的游标对象,而是指向源游标对象的指针。游标和游标变量的关系,就是"常量与变量"的关系——游标在定义时就绑定了查询,而游标变量可以在运行期指向不同的游标。
- 引用游标(Ref Cursor):一种特殊类型的游标变量,典型代表就是
SYS_REFCURSOR,常用于在存储过程 / 函数之间传递结果集。
DM 把游标分为两大类:静态游标(编译时就能确定查询)和动态游标(运行期才指定查询)。静态游标又细分为隐式游标和显式游标。
二、静态游标
静态游标是只读游标,它总是按照打开游标时的原样显示结果集,查询在编译期即可确定。
2.1 隐式游标(SQL)
隐式游标无需定义。每当你在 DMSQL 程序中执行一条 DML 语句(INSERT / UPDATE / DELETE / SELECT)或 SELECT ... INTO 时,数据库都会自动声明并管理一个隐式游标,它的统一名称叫 SQL。
通过 SQL 可以拿到上一条语句的执行信息。每个游标都有四个属性,隐式游标的四属性含义如下:
| 属性 | 含义 |
|---|---|
SQL%FOUND |
未执行 DML 时返回 NULL;执行了则判断是否影响/查到记录,是返回 TRUE,否返回 FALSE |
SQL%NOTFOUND |
与 %FOUND 相反;未执行 DML 时返回 NULL |
SQL%ISOPEN |
是否打开。由于语句执行完会自动关闭,隐式游标的 %ISOPEN 永远为 FALSE |
SQL%ROWCOUNT |
DML 语句影响的行数,或 SELECT ... INTO 返回的行数 |
实战示例:修改某人的电话,用 SQL%NOTFOUND 判断是否真的改到了数据。
BEGIN
UPDATE PERSON.PERSON SET PHONE = 13818882888 WHERE NAME = '孙丽';
IF SQL%NOTFOUND THEN
PRINT '此人不存在';
ELSE
PRINT '已修改';
END IF;
END;
/
2.2 显式游标
当需要处理返回多条记录的查询时,就要显式地定义游标,逐行处理结果集的每一行。
2.2.1 使用显式游标的四步
DECLARE(定义游标) → OPEN(打开) → FETCH(拨动取数) → CLOSE(关闭)
① 定义游标(在声明部分):
CURSOR <游标名> [FAST | NO FAST] <cursor 选项>;
其中 <cursor 选项> 支持四种形式:
<选项1> := <IS|FOR> {<查询表达式>|<连接表>} -- 最常用
<选项2> := <IS|FOR> TABLE <表名> -- 直接指向整张表
<选项3> := (<参数声明>{,<参数声明>}) IS <查询表达式> -- 带参数游标
<选项4> := [(<参数声明>)] RETURN <DMSQL 数据类型> IS <查询表达式> -- 指定返回类型
DECLARE
CURSOR c1 IS SELECT TITLE FROM RESOURCES.EMPLOYEE WHERE MANAGERID = 3;
CURSOR c2 RETURN RESOURCES.EMPLOYEE%ROWTYPE IS SELECT * FROM RESOURCES.EMPLOYEE;
c3 CURSOR IS TABLE RESOURCES.EMPLOYEE;
BEGIN
NULL;
END;
/
② 打开游标:执行关联查询、把结果装入游标工作区,并把游标定位到结果集第一行之前。
OPEN <游标名>;
③ 拨动游标(FETCH 取数):
FETCH [<fetch 选项> [FROM]] <游标名> [ [BULK COLLECT] INTO <主变量>{,<主变量>} ]
[LIMIT <rows>];
fetch 选项 指定游标移动方向:
| 选项 | 含义 |
|---|---|
NEXT |
下移一行(默认) |
PRIOR |
前移一行 |
FIRST |
移动到第一行 |
LAST |
移动到最后一行 |
ABSOLUTE n |
移动到第 n 行 |
RELATIVE n |
移动到当前行之后的第 n 行 |
④ 关闭游标:释放占用的内存。关闭后不能再取数据(否则报错),需要时可再次打开。
完整实战:用 LOOP + %NOTFOUND 遍历结果集。
DECLARE
v_name VARCHAR(50);
v_phone VARCHAR(50);
c1 CURSOR FOR SELECT NAME, PHONE
FROM PERSON.PERSON A, RESOURCES.EMPLOYEE B
WHERE A.PERSONID = B.PERSONID;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO v_name, v_phone;
EXIT WHEN c1%NOTFOUND; -- 取不到数据时退出循环
PRINT v_name || v_phone;
END LOOP;
CLOSE c1;
END;
/
2.2.2 FAST 快速游标
定义游标时可用 FAST 声明快速游标(缺省 NO FAST)。FAST 游标提前返回结果集,速度提升明显,但约束较多:
- 只支持在显式游标中使用;
- 语句块中不能修改 FAST 游标所涉及的表(需用户自行保证);
- 不支持游标更新和删除;
- 不支持
NEXT以外的 FETCH 方向; - 不支持作为函数返回值;
- MPP 环境下不支持对 FAST 游标 FETCH;
- FAST 游标不进行 SQL 剥离。
一句话:追求极致读性能、且明确"只读、顺序扫"的场景才用 FAST,否则用默认游标更稳妥。
2.2.3 显式游标的四属性
显式游标也有 %FOUND / %NOTFOUND / %ISOPEN / %ROWCOUNT,但含义与隐式游标有区别:
| 属性 | 含义 |
|---|---|
%FOUND |
游标未打开时报异常;打开后第一次拨动前为 NULL;最近一次拨动取到数据为 TRUE,否则 FALSE |
%NOTFOUND |
未打开报异常;第一次拨动前为 NULL;最近一次拨动取到数据为 FALSE,否则 TRUE |
%ISOPEN |
打开为 TRUE,否则 FALSE |
%ROWCOUNT |
未打开报异常;打开后第一次拨动前为 0,之后为已取到的元组数 |
实战:输出前 5 行,不足 5 行则输出全部——用 %ROWCOUNT 控制。
DECLARE
CURSOR c1 FOR SELECT * FROM OTHER.EMPSALARY;
my_ename CHAR(10);
my_empno NUMERIC(4);
my_sal NUMERIC(7,2);
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO my_ename, my_empno, my_sal;
EXIT WHEN c1%NOTFOUND; -- 取不到数据跳出
PRINT my_ename || ' ' || my_empno || ' ' || my_sal;
EXIT WHEN c1%ROWCOUNT = 5; -- 已输出 5 行,跳出
END LOOP;
CLOSE c1;
END;
/
2.2.4 BULK COLLECT 批量取数
逐行 FETCH 在数据量大时性能较差。FETCH ... BULK COLLECT INTO 可以把结果一次性、批量地装进集合变量,配合 LIMIT rows 可限制每次取数行数。
DECLARE
TYPE V_rd IS RECORD(V_NAME VARCHAR(50), V_PHONE VARCHAR(50));
TYPE V_type IS TABLE OF V_rd INDEX BY INT; -- 索引表集合
v_info V_type;
c1 CURSOR IS SELECT NAME, PHONE
FROM PERSON.PERSON A, RESOURCES.EMPLOYEE B
WHERE A.PERSONID = B.PERSONID;
BEGIN
OPEN c1;
FETCH c1 BULK COLLECT INTO v_info; -- 一次取全部
CLOSE c1;
FOR I IN 1 .. v_info.COUNT LOOP
PRINT v_info(I).V_NAME || v_info(I).V_PHONE;
END LOOP;
END;
/
注意:
BULK COLLECT可以和SELECT INTO、FETCH INTO、RETURNING INTO一起使用,BULK COLLECT之后INTO的变量必须是集合类型;- 针对
FETCH ... BULK COLLECT INTO,INTO的变量不支持索引类型为 VARCHAR 的索引表。
三、动态游标
静态游标在定义时就绑定了查询;动态游标在声明部分只声明一个游标类型变量、不指定查询,到执行部分 OPEN 时才指定。
-- 定义动态游标(不指定查询)
CURSOR <游标名>;
-- 打开时指定查询
OPEN [WITH FAST] <游标名> FOR <查询表达式>; -- 形式一
OPEN [WITH FAST] <游标名> FOR <表达式> [USING <绑定参数>{,<绑定参数>}]; -- 形式二(带 ? 占位)
实战 1:普通动态游标。
DECLARE
my_ename CHAR(10);
my_empno NUMERIC(4);
my_sal NUMERIC(7,2);
CURSOR c1; -- 只声明,不指定查询
BEGIN
OPEN c1 FOR SELECT * FROM OTHER.EMPSALARY; -- 打开时才指定查询
LOOP
FETCH c1 INTO my_ename, my_empno, my_sal;
EXIT WHEN c1%NOTFOUND;
PRINT '姓名' || my_ename || '工号' || my_empno || '薪水' || my_sal;
END LOOP;
CLOSE c1;
END;
/
实战 2:带参数的动态游标——查询里的 ? 占位,用 USING 绑定参数(个数、类型必须一一匹配)。
DECLARE
str VARCHAR;
CURSOR csr;
BEGIN
OPEN csr FOR 'SELECT LOGINID FROM RESOURCES.EMPLOYEE
WHERE TITLE = ? OR TITLE = ?'
USING '销售经理', '总经理';
LOOP
FETCH csr INTO str;
EXIT WHEN csr%NOTFOUND;
PRINT str;
END LOOP;
CLOSE csr;
END;
/
WITH FAST需要DM.INI参数ENABLE_FAST_REFCURSOR=1才会真正生效,否则只是语法支持。
四、游标变量(SYS_REFCURSOR)
游标变量是指向源游标对象的指针,它继承源游标的全部属性——源游标已打开,则游标变量也已打开,且指向位置与源游标完全一致。
4.1 定义游标变量
<游标变量名> CURSOR [[:] = <源游标>];
<游标变量名> SYS_REFCURSOR [[:] = <源游标>];
<源游标> ::= <源游标名> | <游标表达式>
<游标表达式> ::= CURSOR [FAST] (<查询表达式>)
4.2 赋值与打开
- 赋值时机有两个:定义时赋值;或在执行部分赋值(
<游标变量名> = <源游标>;)。 - 游标表达式会自动打开,不需要再用
OPEN。 - 如果游标变量未赋值,在执行部分打开时,必须同时动态关联一条查询。
-- 已赋值的游标变量,直接 OPEN
OPEN <游标变量名>;
-- 未赋值的游标变量,OPEN 时动态关联查询
OPEN [WITH FAST] <游标变量名> FOR <查询表达式>;
实战:定义游标变量 c2 的同时赋值为源游标 c1。
DECLARE
CURSOR c1 IS SELECT TITLE FROM RESOURCES.EMPLOYEE WHERE MANAGERID = 3;
c2 CURSOR = c1; -- c2 指向 c1,是 c1 的"引用"
BEGIN
OPEN c2;
CLOSE c2;
END;
/
4.3 游标变量的典型用途
因为游标变量是"指针",所以它最大的价值在于:把结果集在存储过程 / 函数之间传递,或让一个变量在运行期指向不同的查询。这是普通静态游标做不到的——静态游标在定义那一刻就"焊死"了查询。
五、总结
本文系统梳理了达梦 DMSQL 中游标的完整知识体系,从基础概念到高级用法,为开发者提供了全面的游标使用指南。
5.1 核心要点回顾
- 游标的作用:解决
SELECT ... INTO只能处理单行数据的局限性,实现对多行结果集的逐行处理。 - 游标分类:
- 静态游标:编译时确定查询,包括隐式游标(
SQL)和显式游标。 - 动态游标:运行时通过
OPEN ... FOR指定查询语句。 - 游标变量:指向源游标对象的指针,典型代表是
SYS_REFCURSOR。
- 静态游标:编译时确定查询,包括隐式游标(
- 使用流程:显式游标遵循"定义(DECLARE)→打开(OPEN)→拨动(FETCH)→关闭(CLOSE)"四步法。
- 性能优化:
- FAST 快速游标:提升读取性能,但限制较多(只读、顺序扫等)。
- BULK COLLECT:批量取数,减少 FETCH 次数,提升大数据量处理效率。
5.2 四类游标对比总结
| 维度 | 隐式游标 | 显式游标 | 动态游标 | 游标变量 |
|---|---|---|---|---|
| 名称 | 固定为 SQL |
用户自定义 | 用户自定义 | 用户自定义(指向源游标) |
| 是否声明 | 无需声明,自动管理 | 声明部分定义并绑定查询 | 声明部分只声明类型 | 声明为指针,可赋源游标 |
| 查询绑定时机 | 执行时自动 | 编译期 | OPEN 时(OPEN ... FOR) |
赋值 / OPEN 时 |
| 支持带参查询 | — | 支持 | 支持(? + USING) |
支持 |
| 典型用途 | 单条 DML 的结果判断 | 遍历多行结果集 | 查询语句运行期才确定 | 在过程/函数间传结果集 |
5.3 常见陷阱与最佳实践
- 异常处理:使用
SELECT ... INTO时必须处理NO_DATA_FOUND和TOO_MANY_ROWS异常。 - 游标状态检查:访问显式游标属性(
%FOUND、%NOTFOUND、%ROWCOUNT)前务必先OPEN,否则会报异常。 - 隐式游标特性:隐式游标的
%ISOPEN永远为 FALSE,不要用它判断隐式游标状态。 - FAST 游标限制:只适用于只读、顺序扫描场景,不支持更新操作和复杂 FETCH 方向。
- BULK COLLECT 约束:
INTO变量必须是集合类型,且不支持 VARCHAR 索引类型的索引表。 - 资源管理:游标使用完毕后及时
CLOSE,释放内存资源。 - 重复打开:重复
OPEN会重新初始化游标,注意逻辑一致性。 - C/Java 语法:在 C 或 Java 语法中操作游标时,
OPEN、FETCH、CLOSE后必须加CURSOR关键字。
更多推荐




所有评论(0)