PostgreSQL存储过程报错‘cursor does not exist’求助
问题:REF游标修改后出现“cursor does not exist”错误
我有一个存储过程,最终目的是在表的指定列中写入标记,标记位置由当前行与前一行的值对比确定。原本采用两个静态游标遍历表的方案可正常运行,静态游标声明如下:
cursor_evento_actual cursor for select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc for update;
为了让游标声明能接受从存储过程参数传入的不同表名和列名,我将游标改为refcursor类型,在DECLARE部分声明:
cursor_evento_actual refcursor;
并在BEGIN部分打开游标:
open cursor_evento_actual for select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc for update; move cursor_evento_actual; fetch cursor_evento_actual into vector_evento_actual;
仅做此修改后就出现了‘cursor does not exist’错误。以下是可正常运行的完整存储过程代码,以及报错的修改后代码:
可正常运行的代码
<<bloque_1>> declare vector_evento_actual record; vector_evento_precedente record; cursor_evento_actual cursor for select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc for update; cursor_evento_precedente cursor for select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc; begin update public.camion_estado set aux_texto_1 = DEFAULT; open cursor_evento_actual; open cursor_evento_precedente; move cursor_evento_actual; fetch cursor_evento_actual into vector_evento_actual; fetch cursor_evento_precedente into vector_evento_precedente; while (found) loop if vector_evento_actual.calc_dt2 = vector_evento_precedente.calc_dt2 and vector_evento_actual.original_cam_id = vector_evento_precedente.original_cam_id then update public.camion_estado set aux_texto_1 = 'repite' where current of cursor_evento_actual; end if; fetch cursor_evento_actual into vector_evento_actual; fetch cursor_evento_precedente into vector_evento_precedente; end loop; close cursor_evento_actual; close cursor_evento_precedente; end bloque_1 $$;
修改后报错的代码
declare vector_evento_actual record; vector_evento_precedente record; cursor_evento_actual refcursor; cursor_evento_precedente refcursor; begin update public.camion_estado set aux_texto_1 = DEFAULT; -- asegurar que no está escrita de antes open cursor_evento_actual for select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc for update; move cursor_evento_actual; fetch cursor_evento_actual into vector_evento_actual; open cursor_evento_precedente for select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc; fetch cursor_evento_precedente into vector_evento_precedente; raise notice 'inicio Bloque 1b - tabla actual: %, columna; % ', tabla_objeto, columna_objeto; while (found) loop if vector_evento_actual.calc_dt2 = vector_evento_precedente.calc_dt2 and vector_evento_actual.original_cam_id = vector_evento_precedente.original_cam_id then raise notice 'este es repetido %', vector_evento_actual.original_cam_id; execute 'update public.'||tabla_objeto||' set aux_texto_1 = '||quote_literal('repite')||' where current of cursor_evento_actual'; /* update public.camion_estado set aux_texto_1 = 'repite' where current of cursor_evento_actual; */ end if; fetch cursor_evento_actual into vector_evento_actual; fetch cursor_evento_precedente into vector_evento_precedente; end loop; close cursor_evento_actual; close cursor_evento_precedente;
错误原因分析
动态SQL与本地游标上下文隔离
报错的核心原因是动态SQL语句中引用了存储过程本地声明的ref游标cursor_evento_actual:execute 'update public.'||tabla_objeto||' set aux_texto_1 = '||quote_literal('repite')||' where current of cursor_evento_actual';动态SQL的执行环境是独立的会话上下文,无法识别存储过程内部定义的本地游标变量,因此抛出“cursor does not exist”错误。
REF游标使用的小隐患
move cursor_evento_actual;语句未指定移动方向,默认执行move next。如果游标刚打开还未执行过fetch,这个操作会让游标指向第一行之后的位置,后续第一次fetch可能获取不到数据,不过这不是本次报错的直接诱因。
解决方案
方案1:用主键/唯一标识定位行(推荐)
避免在动态SQL中使用where current of,改为通过表的主键或唯一键来定位目标行:
- 修改游标查询,包含表的主键字段(假设表主键为
id):open cursor_evento_actual for select *, id from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc for update; - 动态update语句改为:
execute 'update public.'||tabla_objeto||' set aux_texto_1 = '||quote_literal('repite')||' where id = '||vector_evento_actual.id;
方案2:给REF游标指定显式名称
如果一定要使用where current of,需要给ref游标指定一个全局可见的名称:
- 打开游标时显式指定名称:
open 'my_actual_cursor' for select * from public.camion_estado order by original_cam_id asc, calc_dt2 asc, original_num_post asc for update; - 动态SQL中引用这个显式名称:
注意:这种方式需要确保游标名称在会话中唯一,避免冲突,可靠性不如主键定位。execute 'update public.'||tabla_objeto||' set aux_texto_1 = '||quote_literal('repite')||' where current of my_actual_cursor';
内容的提问来源于stack exchange,提问作者Brad
相关产品推荐
相关产品推荐

