1. Oracle 存储过程到底能解决什么问题Oracle 存储过程Stored Procedure是一组预编译后存放在数据库里的 PL/SQL 代码块调用一次就能完成一整套逻辑。它最直接的价值是把「循环遍历数据、条件分支判断、游标逐行处理」这三件事从应用层搬到数据库层减少网络往返也避免把大批量数据拉到 Java/Python 里再处理。适合谁需要做批量数据清洗、跨表同步、定时任务、报表预聚合的数据库开发者尤其是手上有一堆tmp_临时表要往正式表搬的场景。我见过太多人写存储过程卡在三个地方循环写不对导致死循环、判断分支漏了ELSE导致空值、游标忘了关或者%NOTFOUND判断位置错了。这篇就把 LOOP/WHILE/FOR 三种循环、IF/CASE 两种判断、显式游标的完整写法串起来给一个能直接复制运行的骨架。同时补上 TaoToken 的统一 Key/API 通道配置让你在写 SQL 的同时把模型调用也收敛到一套配置里不用每个工具单独填 Key。核心检索词先摆出来Oracle 存储过程、循环语句、判断语句、游标。下面所有代码都在 Oracle 11g/19c 的 SQL*Plus 或 SQL Developer 里验证过你可以直接贴进去跑。2. TaoToken 前置统一 Key 与 API 通道准备写存储过程本身不需要联网但你在开发过程中大概率会用到 AI 辅助生成 SQL、解释报错、补全游标逻辑。TaoToken 在这里的作用是提供一个统一的 API 入口把模型对话、编码计划、控制台管理收敛到一套 Key 上省得在多个工具之间来回切换配置。你需要先拿到一个 API Key。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台里创建 Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。API 基础地址统一用 https://taotoken.net/api 注意这个地址不带 UTM 参数配置时直接写死。如果你只是想让 AI 帮你解释一段游标代码用模型对话页就够了https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。如果你要长期做数据库开发、写大量 PL/SQL建议看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它更适合持续性的编码任务。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Claude Code 相关配置参考 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。注意TaoToken 是合规的 API 聚合通道配置时只填官方给的地址不要自行拼接其他域名。3. 可复制配置settings.json 与 config.toml 骨架在动手写存储过程之前先把 AI 辅助工具的配置落地。这样你遇到ORA-06550之类的编译错误时可以直接把报错贴给模型让它结合你的游标代码给出修改建议。3.1 settings.json 配置适用于多数编辑器插件{ ai.provider: taotoken, ai.apiKey: sk-你的TaoTokenKey, ai.baseUrl: https://taotoken.net/api, ai.model: claude-sonnet, ai.timeout: 60000, ai.maxTokens: 4096 }把sk-你的TaoTokenKey替换成你在控制台创建的真实 Key。baseUrl必须写https://taotoken.net/api不要加斜杠结尾也不要带任何查询参数。3.2 config.toml 配置适用于命令行工具[ai] provider taotoken api_key sk-你的TaoTokenKey base_url https://taotoken.net/api model claude-sonnet timeout 60 max_tokens 4096 [ai.retry] max_attempts 3 backoff_ms 1000两个配置文件选一个用就行取决于你的工具链。配好之后你在写存储过程时可以让 AI 直接读取当前文件上下文解释EXIT WHEN和%NOTFOUND的执行顺序。3.3 存储过程完整骨架下面这个存储过程把循环、判断、游标三样东西全用上了。场景是从tmp_fetch_data临时表里取出所有非空的code逐个判断是否已存在于res_netnode不存在就插入。CREATE OR REPLACE PROCEDURE SYNC_NETNODE_DATA IS -- 游标定义取出所有非空 code CURSOR cur_netnode IS SELECT DISTINCT code FROM tmp_fetch_data t WHERE code IS NOT NULL; v_code_src tmp_fetch_data.code%TYPE; v_flag_prv NUMBER; v_type VARCHAR2(10); BEGIN -- 打开游标 OPEN cur_netnode; LOOP -- 抓取一行 FETCH cur_netnode INTO v_code_src; -- 抓不到就退出 EXIT WHEN cur_netnode%NOTFOUND; -- 判断语句根据 code 前缀决定 type IF v_code_src LIKE A% THEN v_type : 1; ELSIF v_code_src LIKE B% THEN v_type : 2; ELSE v_type : 9; END IF; -- 查重 SELECT COUNT(1) INTO v_flag_prv FROM res_netnode WHERE nodename v_code_src; -- 不存在则插入 IF v_flag_prv 0 THEN INSERT INTO res_netnode(nodeid, nodename, upnodeid, type, maxsub, status, memo) VALUES (seq_res_netnode_id.nextval, v_code_src, 0, v_type, 0, 1, NULL); END IF; END LOOP; -- 关闭游标 CLOSE cur_netnode; COMMIT; EXCEPTION WHEN OTHERS THEN IF cur_netnode%ISOPEN THEN CLOSE cur_netnode; END IF; ROLLBACK; RAISE; END SYNC_NETNODE_DATA; /这段代码里有几个关键点。%TYPE让变量类型自动跟随表字段改表结构时不用改存储过程。EXIT WHEN cur_netnode%NOTFOUND必须放在FETCH之后否则第一次循环就会误判。异常块里先判断游标是否还开着再关避免ORA-01001无效游标错误。4. 循环语句三种写法与验证4.1 LOOP 简单循环LOOP ... EXIT WHEN ... END LOOP是最基础的循环适合不知道循环次数、靠条件退出的场景。DECLARE int NUMBER(2) : 0; BEGIN LOOP int : int 1; DBMS_OUTPUT.PUT_LINE(int 的当前值为: || int); EXIT WHEN int 10; END LOOP; END; /执行后输出 1 到 10。注意EXIT WHEN放在自增之后如果放在之前输出会变成 0 到 9。4.2 WHILE 循环WHILE 布尔表达式 LOOP ... END LOOP在每次循环开始前判断条件条件为假直接不进循环。DECLARE x NUMBER : 1; BEGIN WHILE x 10 LOOP DBMS_OUTPUT.PUT_LINE(X的当前值为: || x); x : x 1; END LOOP; END; /WHILE 和 LOOP 的区别WHILE 可能一次都不执行LOOP 至少执行一次。如果你要遍历一个可能为空的集合用 WHILE 更安全。4.3 FOR 循环FOR 计数器 IN [REVERSE] 下限..上限 LOOP ... END LOOP最省心计数器自动声明、自动递增不用手动初始化。BEGIN FOR int IN 1..10 LOOP DBMS_OUTPUT.PUT_LINE(int 的当前值为: || int); END LOOP; END; /加REVERSE就从大到小BEGIN FOR int IN REVERSE 1..10 LOOP DBMS_OUTPUT.PUT_LINE(倒序 int: || int); END LOOP; END; /注意REVERSE后面的数字必须从小到大写REVERSE 10..1是错的会直接编译报错。上下限也不能是变量或表达式只能是整数常量。4.4 验证循环结果在 SQL*Plus 里执行前先开输出SET SERVEROUTPUT ON;然后执行上面的匿名块看到 1 到 10 依次打印就说明循环逻辑通了。如果什么都没输出检查SET SERVEROUTPUT ON有没有执行或者你的客户端是不是把 DBMS_OUTPUT 缓冲区关掉了。5. 判断语句与游标组合实战5.1 IF/ELSIF/ELSE 判断DECLARE x VARCHAR2(10) : 住宅; t NUMBER; BEGIN IF x 住宅 THEN t : 0; ELSIF x 商务 THEN t : 2; ELSIF x 商住 THEN t : 3; ELSE t : 9; END IF; DBMS_OUTPUT.PUT_LINE(t || t); END; /ELSIF不是ELSEIF少一个 E写错直接编译不过。每个分支的THEN不能省。ELSE分支建议永远写上处理意外值避免变量保持 NULL。5.2 CASE 判断CASE 在分支多的时候比 IF 清爽DECLARE x VARCHAR2(10) : 商住; t NUMBER; BEGIN t : CASE x WHEN 住宅 THEN 0 WHEN 商务 THEN 2 WHEN 商住 THEN 3 ELSE 9 END; DBMS_OUTPUT.PUT_LINE(CASE t || t); END; /CASE 是表达式可以直接赋值IF 是语句只能控制流程。需要返回值时优先用 CASE。5.3 显式游标 LOOP FETCH 写法DECLARE CURSOR cur_netnode IS SELECT DISTINCT code FROM tmp_fetch_data t WHERE code IS NOT NULL; v_code_src tmp_fetch_data.code%TYPE; v_flag_prv NUMBER; BEGIN OPEN cur_netnode; LOOP FETCH cur_netnode INTO v_code_src; EXIT WHEN cur_netnode%NOTFOUND; SELECT COUNT(1) INTO v_flag_prv FROM res_netnode WHERE nodename v_code_src; IF v_flag_prv 0 THEN INSERT INTO res_netnode(nodeid, nodename, upnodeid, type, maxsub, status, memo) VALUES (seq_res_netnode_id.nextval, v_code_src, 0, 1, 0, 1, NULL); END IF; END LOOP; CLOSE cur_netnode; COMMIT; END; /5.4 游标 FOR 循环写法FOR 循环处理游标更简洁不用手动 OPEN/FETCH/CLOSEDECLARE CURSOR c_list IS SELECT * FROM table_user i WHERE i.id 4; BEGIN FOR c IN c_list LOOP DBMS_OUTPUT.PUT_LINE(用户的id || c.id); END LOOP; END; /c是隐式声明的记录变量直接c.id就能取字段。循环结束自动关游标异常时也自动关。日常开发我优先用这种写法少写三行代码少三个出错点。5.5 编译与执行验证编译存储过程CREATE OR REPLACE PROCEDURE SYNC_NETNODE_DATA IS ... END SYNC_NETNODE_DATA; /如果编译报错查错误信息SHOW ERRORS PROCEDURE SYNC_NETNODE_DATA;执行BEGIN SYNC_NETNODE_DATA; END; /验证结果SELECT COUNT(1) FROM res_netnode WHERE nodename IN ( SELECT DISTINCT code FROM tmp_fetch_data WHERE code IS NOT NULL );这个数字应该等于tmp_fetch_data里非空 code 的去重数量。如果对不上检查游标 WHERE 条件是不是漏了或者插入时被唯一约束挡了。6. 本篇常见错误排查6.1 ORA-06550 / PLS-00103 编译错误最常见的原因是ELSIF写成ELSEIF或者END LOOP后面漏了分号。用SHOW ERRORS看具体行号PL/SQL 的报错行号有时会偏移一行往上看一行通常能找到问题。6.2 ORA-01001 无效游标在CLOSE之后又FETCH或者异常块里重复关闭。解决办法是在异常处理里加IF cur_netnode%ISOPEN THEN CLOSE cur_netnode; END IF;。6.3 游标循环少处理最后一行EXIT WHEN cur_netnode%NOTFOUND如果放在FETCH之前第一行还没抓就判断直接退出。必须FETCH在前EXIT WHEN在后。6.4 DBMS_OUTPUT 没有输出先执行SET SERVEROUTPUT ON并且确认缓冲区大小够用SET SERVEROUTPUT ON SIZE 1000000。在 SQL Developer 里还要确认「DBMS Output」面板已经打开。6.5 FOR 循环上下限用了变量FOR i IN v_start..v_end LOOP会报PLS-00382。FOR 循环的边界只能是数字字面量。如果边界是动态的改用 WHILE 循环。6.6 插入时主键冲突seq_res_netnode_id.nextval如果序列当前值落后于表里已有数据会报ORA-00001。查一下序列当前值SELECT seq_res_netnode_id.nextval FROM dual;和表里最大 nodeid 对比必要时重建序列或调整增量。7. 把 AI 辅助接进你的存储过程开发流写存储过程时AI 最有用的三个场景解释报错、补全游标逻辑、生成测试数据。把前面配好的 TaoToken Key 用起来遇到ORA-开头的错误直接把错误码和你的 PL/SQL 块贴进模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 让它结合上下文给修改建议。如果你要长期维护一套存储过程库建议走 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 把模型调用和代码仓库绑定改游标逻辑时让 AI 直接读文件。接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Key 在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 管理。API 地址固定 https://taotoken.net/api 配置一次到处能用。最后给一个实用技巧把SYNC_NETNODE_DATA里的COMMIT改成每 1000 行提交一次用IF MOD(v_count, 1000) 0 THEN COMMIT; END IF;控制大批量数据时能避免 undo 表空间爆掉。这个改动我试过在百万级临时表上跑比一次性提交稳得多。