1. 游标里按 EMPNO 给 SAL 赋值,为什么容易改错
1.1 原逻辑拆开看
这段 PL/SQL 我最早是在改 scott.emp 的薪资赋值逻辑时遇到的:MY_CUR 游标带着 FOR UPDATE OF SAL 锁住行,然后按 EMPNO 的 MOD 10/11 走不同分支,再用 DEPTNO 前两位做除数、TRUNC 后写回 SAL。表面看只有两个条件,实际改起来要同时盯住四件事:游标锁、分支顺序、除数为 0、END IF 配对。老实说,分支一多,END IF 和 NULLIF 很容易对不齐。后来我用 Codex 来改写,Codex 的模型通道走的是 TaoToken,也就是先到 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 创建 API Key,再把 Base URL 填进 Codex 配置。TaoToken 只负责把模型请求送出去,真正对 SAL 的赋值逻辑,仍然由 Codex 生成代码、由你在 SQL*Plus 里执行。
先看原始需求:scott.emp 表里,EMPNO 大于 7698 的员工,要根据 EMPNO 的不同性质,把 DEPTNO 前两位拿来做除数,最后把结果写回 SAL。代码用游标 MY_CUR 加 FOR UPDATE OF SAL 锁定目标行,保证在 UPDATE 之前没有其他会话改这一列的 SAL 值。这个锁不是表锁,是行级锁,锁的范围就是游标当前扫到的行。
原代码的关键结构整理后是这样:
FOR EMP_ROW IN MY_CUR LOOP NUM := TRUNC( TO_NUMBER(EMP_ROW.EMPNO) / NULLIF(TO_NUMBER(SUBSTR(EMP_ROW.DEPTNO, 1, 2)), 0) ); IF MOD(EMP_ROW.EMPNO, 10) = 0 THEN UPDATE scott.emp E SET E.SAL = TRUNC(TO_NUMBER(EMP_ROW.EMPNO) / NUM) WHERE CURRENT OF MY_CUR; ELSE IF MOD(EMP_ROW.EMPNO, 11) = 0 THEN UPDATE scott.emp E SET E.SAL = TRUNC(TO_NUMBER(EMP_ROW.EMPNO) / NUM) WHERE CURRENT OF MY_CUR; END IF; END IF; END LOOP;WHERE CURRENT OF MY_CUR的含义是:当前 UPDATE 只作用于游标刚取出来的这一行,不需要再写 WHERE EMPNO = 某某。它依赖FOR UPDATE OF SAL建立的游标行锁,两者是配套出现的。
1.2 分支一多,END IF 和 NULLIF 对不齐
原文最大的坑不是计算本身,而是结构:ELSE IF嵌套后有两个END IF,少写一个,Oracle 只报 PLS-00103,不会告诉你「这里少了一个 END IF」。另一个坑是除数为 0:如果 DEPTNO 前两位是 00,SUBSTR结果是 0,NULLIF把它变成 NULL,NUM 就成了 NULL;继续拿 NUM 去除 EMPNO,结果还是 NULL,最后 SAL 会被写成 NULL。原代码只在第一层除法用了NULLIF,第二层TRUNC(EMPNO / NUM)没有保护。
这些坑在代码短的时候看不出来,一旦分支从两个变成五个,人工核对 END IF 的体力活就容易出错。这也是后来我把这段丢给 Codex 改写的原因。
1.3 还有两个边界,Codex 改的时候也得注意
第一,MOD(EMP_ROW.EMPNO, 10) = 0和MOD(EMP_ROW.EMPNO, 11) = 0可能同时成立,比如 EMPNO = 7700 时,能同时被 10 和 11 整除。原代码先判断 10,所以走第一个分支;改成 ELSIF 后,也仍然是第一个分支优先。如果 Codex 把两个 IF 写成并列,后面的分支会覆盖前面的结果,SAL 的赋值行为就变了。
第二,DEPTNO 是 NUMBER 类型,SUBSTR(EMP_ROW.DEPTNO, 1, 2)依赖 Oracle 的隐式类型转换。练习表上问题不大,但改到生产环境,最好先TO_CHAR(EMP_ROW.DEPTNO)再截取前两位。这两条都可以写进给 Codex 的提示词里,避免它自由发挥。
2. 用 Codex 改写之前,先到 TaoToken 拿 Key 和 Base URL
2.1 Codex 只负责生成代码,不碰 SAL 字段
先澄清一点:Codex 不是直接连上 Oracle 去 UPDATE 数据的。它的任务是读懂这段 PL/SQL,把分支结构改清晰、补全 END IF、加好 NULLIF 保护,然后生成一段你可以直接贴进 SQLPlus 执行的脚本。TaoToken 在这里只充当 Codex 的模型通道,既不接触 scott.emp,也不碰 SAL 字段本身。真正对 SAL 的赋值动作,发生在你本地 SQLPlus 会话里。
所以步骤变成:先配通 Codex,再让 Codex 改写,最后你本机执行。配置 Codex 的第一步是到 TaoToken 注册并创建 API Key。
2.2 Codex 配置文件里填入 TaoToken 的 Base URL
创建 Key 的入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 控制台的 API Keys 页面。拿到 Key 后,编辑本机的~/.codex/config.toml:
model = "YOUR_MODEL_ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "OPENAI_API_KEY" wire_api = "chat"注意两个地址不是一回事:官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 是注册、创建 Key、看模型广场、看用量的地方;~/.codex/config.toml里的base_url必须填https://taotoken.net/api,末尾不要加/v1。模型 ID 以 TaoToken 模型广场当时列出来的为准,把YOUR_MODEL_ID替换成真实 ID。
保存 config.toml 后,在终端里把 Key 导出成环境变量:
export OPENAI_API_KEY=YOUR_API_KEYYOUR_API_KEY换成控制台创建出来的那串 Key。然后跑一句codex exec "1+1"之类的测试,能正常返回就说明通道通了。
3. 让 Codex 改写游标赋值分支
3.1 给 Codex 的提示词怎么写
把原代码连同需求一起贴给 Codex,重点说明:游标是 FOR UPDATE OF SAL;EMPNO 被 10 整除走一个分支,被 11 整除走另一个分支,其他情况 SAL 置 100;除数来自 DEPTNO 前两位,用 NULLIF 防止除数为 0;最后把结果 TRUNC 后写回。同时要求把嵌套的 ELSE IF 改成 ELSIF,减少 END IF 层级。
可以直接用下面这段提示词:
下面这段 Oracle PL/SQL 用游标 MY_CUR 扫描 scott.emp 中 EMPNO > '7698' 的行, 对满足 MOD(EMPNO,10)=0 或 MOD(EMPNO,11)=0 的行,用 DEPTNO 前两位作为除数, 计算 TRUNC(EMPNO / 除数) 并更新 SAL,其余行 SAL 置 100。 请改写为更稳的版本: 1. 保留 FOR UPDATE OF SAL 和 WHERE CURRENT OF MY_CUR; 2. 用 ELSIF 替代嵌套 ELSE IF,补全 END IF; 3. 对每一处除法都用 NULLIF 防止 ORA-01476; 4. 保持 MOD(EMPNO,10)=0 优先于 MOD(EMPNO,11)=0; 5. 输出完整 PL/SQL 块,不要解释。3.2 改写后的大致样子
Codex 给出的结果不会只有一种,但核心结构会长成这样:
DECLARE CURSOR MY_CUR IS SELECT E.EMPNO, E.DEPTNO, E.SAL FROM scott.emp E WHERE E.EMPNO > '7698' FOR UPDATE OF SAL; V_EMPNO NUMBER; V_DIV NUMBER; V_NUM NUMBER; BEGIN FOR EMP_ROW IN MY_CUR LOOP V_EMPNO := TO_NUMBER(EMP_ROW.EMPNO); V_DIV := TO_NUMBER(SUBSTR(TO_CHAR(EMP_ROW.DEPTNO), 1, 2)); V_NUM := TRUNC(V_EMPNO / NULLIF(V_DIV, 0)); IF MOD(EMP_ROW.EMPNO, 10) = 0 THEN UPDATE scott.emp E SET E.SAL = TRUNC(V_EMPNO / NULLIF(V_NUM, 0)) WHERE CURRENT OF MY_CUR; ELSIF MOD(EMP_ROW.EMPNO, 11) = 0 THEN UPDATE scott.emp E SET E.SAL = TRUNC(V_EMPNO / NULLIF(V_NUM, 0)) WHERE CURRENT OF MY_CUR; ELSE UPDATE scott.emp E SET E.SAL = 100 WHERE CURRENT OF MY_CUR; END IF; END LOOP; COMMIT; END; /这段相对原代码的改动是:把ELSE IF变成了ELSIF,整个块只剩一个END IF;DEPTNO先TO_CHAR再截取,避免隐式转换;两处除法都套了NULLIF。如果 Codex 坚持用嵌套ELSE IF,在提示词里追加一句「不要嵌套,全部改成 ELSIF」即可。
3.3 Codex 可能擅自改掉游标语义,要拦一下
Codex 这类模型有个特点:看到「按条件更新 SAL」,容易顺手优化成一条MERGE或者多条UPDATE合并,然后告诉你「这样更高效」。但在这个场景里不能接受,因为FOR UPDATE OF SAL和WHERE CURRENT OF MY_CUR是原文指定的行锁语义,合并成集合 UPDATE 后,锁的粒度、扫描顺序都可能变。所以提示词里要明确写「保留游标和 WHERE CURRENT OF MY_CUR」。如果改写结果里没有游标,就让它重写,不要将就。
4. 在 SQL*Plus 里验证改写结果
4.1 先备份再执行
Codex 生成的脚本不要直接对业务表跑。scott.emp 是练习表,但习惯要养好:执行前先建一张备份表:
CREATE TABLE emp_bak_202409 AS SELECT * FROM scott.emp;然后在 SQL*Plus 里执行整个 PL/SQL 块。执行完用下面的对照语句验证:
SELECT EMPNO, DEPTNO, SAL FROM scott.emp WHERE EMPNO > '7698' ORDER BY EMPNO;重点看两类行:EMPNO 以 0 结尾的,SAL 应该是 EMPNO 除以 NUM 再 TRUNC 的结果;EMPNO 是 11 的倍数的,走第二个分支;其他行 SAL 是 100。如果和预期不符,把结果贴回 Codex,让它对照调整。
4.2 报错对照:ORA-01476 和 PLS-00103
这段改写最容易踩的报错有两个。第一个是ORA-01476: divisor is equal to zero,说明某一行算出来的除数是 0,NULLIF没起作用或没套对位置。检查NULLIF是否同时出现在两处除法上。第二个是PLS-00103,通常伴随END IF缺失,说明 Codex 这次生成的块又嵌套回去了。解决办法是让 Codex 只输出一个平面的 IF-ELSIF-END IF,不要保留 ELSE IF 嵌套。
如果你在 Codex 配置阶段就报了 404,先回 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 的模型广场确认YOUR_MODEL_ID是不是当前列表里的 ID;如果是 401,检查OPENAI_API_KEY是否导出正确,或者 Key 是否在控制台被删了。
4.3 先打印再更新,确认中间值
如果你不想第一次就跑 UPDATE,可以让 Codex 生成一个调试版本:只计算不更新,用DBMS_OUTPUT.PUT_LINE把 EMPNO、DEPTNO、V_DIV、V_NUM 和新 SAL 都打出来。在 SQL*Plus 里先执行SET SERVEROUTPUT ON,再跑调试块,确认每行的计算结果都符合预期后,再执行真正的 UPDATE 版本。这种「先生成、后验证、再写回」的流程,正好发挥 Codex 改代码的用途,也不会让它在你的库里乱动数据。
5. 跑通之后去控制台对一下这次调用
5.1 在模型对话里试同一把 Key
配置保存后,先在 TaoToken 模型对话 里用同一把 Key 发一条测试消息,确认模型 ID 和 Base URL 没填错。如果对话正常,Codex 的请求也会正常,因为两者走的是同一个 API 通道,只是前端工具不同。
5.2 Coding Plan 与 Key 管理
如果你打算让 Codex 长期承担这类 Oracle 改写工作,可以打开 Coding Plan 看看套餐是否够用。Key 的创建和吊销都在 TaoToken 控制台 API Keys 页面,建议每次只开一把 Key,配一个工具,出问题好定位。这次 Codex 调用有没有正常记账,也能在官网用量页面确认。