PL/pgSQL批量导CSV首次循环后cursor不存在错误的原因与解决方法
错误原因
你遇到的cursor "curs" does not exist错误核心是事务提交导致游标失效:
- PostgreSQL中显式声明的游标和当前事务强绑定,事务一旦执行
COMMIT提交,事务上下文内打开的所有游标都会被自动关闭。 - 你在循环内部写入了
COMMIT语句,第一个文件导入完成提交后,提前打开的curs游标就被销毁了,第二次循环执行FETCH curs时自然找不到对应游标。
另外你的代码还存在两处隐性语法问题会导致执行报错:
- 插入数据的
SELECT子句中,mytemp.dataB和mytemp.source_id之间缺少逗号分隔,会触发语法错误。 - 开头
DROP TABLE mytemp没有加IF EXISTS判断,第一次执行时临时表不存在,会直接抛出错误。
修复方案
根据你的需求分两种场景处理:
场景1:所有文件导入要么全部成功要么全部回滚
直接删除循环内部的COMMIT语句即可,DO匿名块默认会在所有逻辑执行完成后自动提交事务,游标会在整个事务周期内保持有效。
场景2:允许单个文件导入失败不影响其他文件,导入成功的文件直接标记为已加载
DO匿名块不支持内部事务控制,你需要改用**存储过程(PROCEDURE)**实现,同时抛弃显式游标写法,改用PostgreSQL自带的FOR ... IN SELECT隐式游标循环,避免游标和事务绑定的问题。同时建议优化以下细节:
- 用
DROP TABLE IF EXISTS避免临时表不存在的报错 FORMAT函数中用%L格式化文件路径,自动转义特殊字符,避免路径异常或SQL注入风险
修正后完整代码
全量事务版本(适合文件量小、不需要部分提交的场景)
DO $$ DECLARE file_record record; curs CURSOR FOR SELECT id, file_path FROM source_files WHERE added_to_db=FALSE ORDER BY id; BEGIN OPEN curs; LOOP FETCH curs INTO file_record; exit WHEN NOT found; BEGIN DROP TABLE IF EXISTS mytemp; CREATE TABLE mytemp ( dataA numeric, dataB numeric ); EXECUTE FORMAT('COPY mytemp FROM %L DELIMITER '','' CSV HEADER;', file_record.file_path); ALTER TABLE mytemp ADD COLUMN source_id int; UPDATE mytemp SET source_id=file_record.id; INSERT INTO data_table( dataA, dataB, source_id ) SELECT mytemp.dataA, mytemp.dataB, mytemp.source_id FROM mytemp; UPDATE source_files SET added_to_db=TRUE WHERE CURRENT OF curs; END; END LOOP; CLOSE curs; END $$;
支持部分提交的存储过程版本(适合文件量大、允许部分导入的场景)
-- 先创建存储过程 CREATE OR REPLACE PROCEDURE batch_import_csv() LANGUAGE plpgsql AS $$ DECLARE file_record record; BEGIN -- 用隐式游标循环,不需要手动管理游标生命周期 FOR file_record IN SELECT id, file_path FROM source_files WHERE added_to_db=FALSE ORDER BY id LOOP BEGIN DROP TABLE IF EXISTS mytemp; CREATE TABLE mytemp ( dataA numeric, dataB numeric ); EXECUTE FORMAT('COPY mytemp FROM %L DELIMITER '','' CSV HEADER;', file_record.file_path); ALTER TABLE mytemp ADD COLUMN source_id int; UPDATE mytemp SET source_id=file_record.id; INSERT INTO data_table( dataA, dataB, source_id ) SELECT mytemp.dataA, mytemp.dataB, mytemp.source_id FROM mytemp; UPDATE source_files SET added_to_db=TRUE WHERE id = file_record.id; -- 单个文件导入成功就提交 COMMIT; EXCEPTION -- 单个文件导入失败就回滚当前文件的操作,继续下一个文件 WHEN OTHERS THEN ROLLBACK; RAISE NOTICE '导入文件%失败,错误信息:%', file_record.file_path, SQLERRM; END; END LOOP; END $$; -- 调用存储过程执行导入 CALL batch_import_csv();
内容的提问来源于stack exchange,提问作者sev
相关产品推荐
相关产品推荐

