1. 为什么 SELECT INTO 一到多行就翻车如果你从 Oracle 或 MySQL 迁移到达梦写 DMSQL 存储过程时大概率踩过这个坑用SELECT ... INTO取数单行没问题一旦查询命中多行直接抛TOO_MANY_ROWS-7046一行都没查到又抛NO_DATA_FOUND-7065。这不是语法写错了而是SELECT ... INTO的设计边界——它天生只能接收一条记录。现实业务里逐行处理多行结果集是刚需遍历某部门全部员工、按行汇总报表、批量刷新状态字段。这时候就得请出游标Cursor。你可以把游标理解成一个指向结果集的指针程序通过它一行一行地拨动、读取数据而不是一次性把整个结果集塞进变量。达梦 DMSQL 的游标体系分两大类静态游标编译期就能确定查询含隐式游标和显式游标和动态游标运行期才指定查询。此外还有游标变量它本身不是真正的游标对象而是指向源游标对象的指针典型代表是SYS_REFCURSOR常用于在存储过程/函数之间传递结果集。本文按这四类逐一拆解语法差异、适用边界并给出可直接复制到 DM 环境验证的配置骨架。2. 隐式游标不用声明但属性有坑隐式游标无需定义。每当你在 DMSQL 程序中执行一条 DML 语句INSERT / UPDATE / DELETE / SELECT或SELECT ... INTO时数据库会自动声明并管理一个隐式游标统一名称叫SQL。它的四个属性含义如下属性含义SQL%FOUND未执行 DML 时返回 NULL执行了则判断是否影响/查到记录是 TRUE否 FALSESQL%NOTFOUND与 %FOUND 相反未执行 DML 时返回 NULLSQL%ISOPEN语句执行完自动关闭隐式游标永远为 FALSESQL%ROWCOUNTDML 影响的行数或 SELECT ... INTO 返回的行数实战修改某人电话用SQL%NOTFOUND判断是否真的改到了数据。BEGIN UPDATE PERSON.PERSON SET PHONE 13818882888 WHERE NAME 孙丽; IF SQL%NOTFOUND THEN PRINT 此人不存在; ELSE PRINT 已修改; END IF; END; /注意隐式游标的%ISOPEN永远是 FALSE别拿它判断游标状态这是迁移时最容易想当然写错的地方。3. 显式游标四步法与属性差异需要处理返回多条记录的查询时就要显式定义游标。标准流程是四步DECLARE定义→ OPEN打开→ FETCH拨动取数→ CLOSE关闭。3.1 定义与打开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; /cursor 选项支持四种形式最常用的是IS|FOR 查询表达式也可直接TABLE 表名指向整张表带参数游标写成(参数声明) IS 查询表达式指定返回类型则用RETURN DMSQL 数据类型 IS 查询表达式。打开游标执行关联查询、把结果装入游标工作区并把游标定位到结果集第一行之前OPEN c1;3.2 FETCH 取数与方向选项FETCH [fetch 选项 [FROM]] 游标名 [ [BULK COLLECT] INTO 主变量{,主变量} ] [LIMIT rows];fetch 选项指定游标移动方向NEXT下移一行默认、PRIOR前移一行、FIRST第一行、LAST最后一行、ABSOLUTE n第 n 行、RELATIVE n当前行之后第 n 行。完整遍历骨架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; /3.3 显式游标四属性显式游标也有%FOUND / %NOTFOUND / %ISOPEN / %ROWCOUNT但含义与隐式游标有区别未打开时访问%FOUND、%NOTFOUND、%ROWCOUNT会报异常打开后第一次拨动前%FOUND为 NULL、%ROWCOUNT为 0之后%ROWCOUNT为已取到的元组数。用%ROWCOUNT控制只输出前 5 行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; END LOOP; CLOSE c1; END; /3.4 FAST 快速游标与 BULK COLLECT定义时可用FAST声明快速游标缺省NO FAST它提前返回结果集速度提升明显但约束很多只支持显式游标语句块中不能修改 FAST 游标涉及的表不支持游标更新删除不支持 NEXT 以外的 FETCH 方向不支持作为函数返回值MPP 环境下不支持 FETCH不进行 SQL 剥离。一句话追求极致读性能、且明确只读顺序扫的场景才用。逐行 FETCH 在数据量大时性能较差FETCH ... BULK COLLECT INTO可一次性批量装进集合变量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 之后 INTO 的变量必须是集合类型针对FETCH ... BULK COLLECT INTOINTO 变量不支持索引类型为 VARCHAR 的索引表。4. 动态游标与游标变量运行期才定查询静态游标在定义时就绑定了查询动态游标在声明部分只声明游标类型变量、不指定查询到执行部分 OPEN 时才指定。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; /带参数的动态游标用?占位USING绑定参数个数和类型必须一一匹配DECLARE str VARCHAR(50); csr CURSOR; 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_REFCURSOR1才真正生效否则只是语法支持。游标变量是指向源游标对象的指针继承源游标全部属性——源游标已打开则游标变量也已打开指向位置完全一致。定义形式为游标变量名 CURSOR [[:] 源游标]或游标变量名 SYS_REFCURSOR [[:] 源游标]。赋值时机有两个定义时赋值或执行部分赋值。游标表达式会自动打开不需要再用 OPEN未赋值的游标变量在执行部分打开时必须同时动态关联查询。DECLARE CURSOR c1 IS SELECT TITLE FROM RESOURCES.EMPLOYEE WHERE MANAGERID 3; c2 CURSOR c1; BEGIN OPEN c2; CLOSE c2; END; /游标变量最大的价值在于把结果集在存储过程/函数之间传递或让一个变量在运行期指向不同查询——这是静态游标做不到的。5. 四类游标对比与常见报错排查维度隐式游标显式游标动态游标游标变量名称固定为 SQL用户自定义用户自定义指向源游标是否声明无需声明声明部分定义并绑定查询只声明类型声明为指针可赋源游标查询绑定时机执行时自动编译期OPEN 时赋值/OPEN 时支持带参查询—支持支持? USING支持典型用途单条 DML 结果判断遍历多行结果集查询运行期才确定过程/函数间传结果集排障清单按报错现象对号入座抛NO_DATA_FOUND-7065或TOO_MANY_ROWS-7046用了SELECT ... INTO但结果集不是恰好一行改用显式游标或加异常处理。访问%FOUND、%NOTFOUND、%ROWCOUNT报异常显式游标还没 OPEN 就访问属性先 OPEN 再判断。用SQL%ISOPEN判断隐式游标状态永远 FALSE这是设计如此隐式游标执行完自动关闭别用它做状态判断。FAST 游标报不支持更新/复杂 FETCHFAST 只适用于只读顺序扫需要更新或 PRIOR/ABSOLUTE 方向就换默认游标。BULK COLLECT 报类型不匹配INTO 变量必须是集合类型且不支持 VARCHAR 索引类型的索引表。重复 OPEN 后数据不对重复 OPEN 会重新初始化游标注意逻辑一致性。C/Java 语法中 OPEN、FETCH、CLOSE 报错这三个操作后必须加CURSOR关键字。游标用完及时 CLOSE 释放内存这是资源管理的基本功。6. 把游标验证跑通再谈迁移迁移达梦的过程中游标行为验证建议单独拉一个测试块跑一遍先跑隐式游标确认SQL%ROWCOUNT和%NOTFOUND的返回时机再跑显式游标四步法确认%ROWCOUNT从 0 开始累加最后跑动态游标和SYS_REFCURSOR确认运行期绑定和跨过程传递。三类都跑通再往业务存储过程里套。如果你在本地或测试环境反复调游标属性、想快速验证某段 DMSQL 的执行结果可以直接在模型对话里贴代码让模型帮你逐行核对属性返回时机比翻文档快https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_verify需要把游标逻辑接进现有工程、批量生成接入代码或做长期编码辅助的可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_coding申请 API Key 并查阅达梦接入文档https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_apikeyhttps://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_doc控制台入口https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_console官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcursor_homeAPI 地址不加 UTMhttps://taotoken.net/api