引言:为什么需要游标

在 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 中用来对多行结果集进行逐条处理的机制。理解它,先记住三组概念:

  1. 游标(Cursor):指向一个查询结果集的指针,程序通过它逐行访问数据。
  2. 游标变量(Cursor Variable):不是真正的游标对象,而是指向源游标对象的指针。游标和游标变量的关系,就是"常量与变量"的关系——游标在定义时就绑定了查询,而游标变量可以在运行期指向不同的游标。
  3. 引用游标(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 INTOFETCH INTORETURNING INTO 一起使用,BULK COLLECT 之后 INTO 的变量必须是集合类型
  • 针对 FETCH ... BULK COLLECT INTOINTO 的变量不支持索引类型为 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 核心要点回顾

  1. 游标的作用:解决 SELECT ... INTO 只能处理单行数据的局限性,实现对多行结果集的逐行处理。
  2. 游标分类
    • 静态游标:编译时确定查询,包括隐式游标(SQL)和显式游标。
    • 动态游标:运行时通过 OPEN ... FOR 指定查询语句。
    • 游标变量:指向源游标对象的指针,典型代表是 SYS_REFCURSOR
  3. 使用流程:显式游标遵循"定义(DECLARE)→打开(OPEN)→拨动(FETCH)→关闭(CLOSE)"四步法。
  4. 性能优化
    • FAST 快速游标:提升读取性能,但限制较多(只读、顺序扫等)。
    • BULK COLLECT:批量取数,减少 FETCH 次数,提升大数据量处理效率。

5.2 四类游标对比总结

维度 隐式游标 显式游标 动态游标 游标变量
名称 固定为 SQL 用户自定义 用户自定义 用户自定义(指向源游标)
是否声明 无需声明,自动管理 声明部分定义并绑定查询 声明部分只声明类型 声明为指针,可赋源游标
查询绑定时机 执行时自动 编译期 OPEN 时(OPEN ... FOR 赋值 / OPEN 时
支持带参查询 支持 支持(? + USING 支持
典型用途 单条 DML 的结果判断 遍历多行结果集 查询语句运行期才确定 在过程/函数间传结果集

5.3 常见陷阱与最佳实践

  1. 异常处理:使用 SELECT ... INTO 时必须处理 NO_DATA_FOUNDTOO_MANY_ROWS 异常。
  2. 游标状态检查:访问显式游标属性(%FOUND%NOTFOUND%ROWCOUNT)前务必先 OPEN,否则会报异常。
  3. 隐式游标特性:隐式游标的 %ISOPEN 永远为 FALSE,不要用它判断隐式游标状态。
  4. FAST 游标限制:只适用于只读、顺序扫描场景,不支持更新操作和复杂 FETCH 方向。
  5. BULK COLLECT 约束INTO 变量必须是集合类型,且不支持 VARCHAR 索引类型的索引表。
  6. 资源管理:游标使用完毕后及时 CLOSE,释放内存资源。
  7. 重复打开:重复 OPEN 会重新初始化游标,注意逻辑一致性。
  8. C/Java 语法:在 C 或 Java 语法中操作游标时,OPENFETCHCLOSE 后必须加 CURSOR 关键字。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐