1. 游标 %NOTFOUND 到底在判什么:一次代码分析踩坑复盘
%NOTFOUND是 Oracle PL/SQL 里最容易被误读的属性之一。很多人第一反应是「它表示游标当前有没有指向一条有效记录」,但真正跑一遍就会发现,这个理解会直接导致最后一条记录被多输出一遍。我在做数据库迁移审查时,就遇到过一段逻辑看起来没问题、结果却多打一行的存储过程,排查半天才发现问题出在fetch和exit when的先后顺序上。
先把结论摆出来:%NOTFOUND的值取决于最近一次 fetch 的结果,而不是游标当前是否指向有效记录。对于显式游标,open之后、第一次fetch之前,%NOTFOUND的值是NULL,不是FALSE。这一点非常关键,因为 PL/SQL 里的布尔类型有三个值:TRUE、FALSE、NULL,而且默认值是NULL。如果你写if c%notfound then,在第一次循环时它既不是真也不是假,会直接走进else分支。
这段逻辑适合谁看?适合正在做 Oracle 存储过程代码分析、数据库迁移审查、或者把老 PL/SQL 逻辑往新平台搬的开发者。本文会给出可复制的 CC Switch 配置片段和 TaoToken 统一 Key 接入步骤,再附一段含%NOTFOUND的完整 PL/SQL 样例和预期输出,通过执行与日志比对完成验证。你不需要有很深的 Oracle 功底,只要能看懂基本的游标循环就能跟下来。
我试过把这段代码原样丢进分析流程里,结果第一次循环打印的是null,而不是false。这个细节在代码审查里特别容易被忽略,因为大多数人只关注exit when的位置,却没意识到%NOTFOUND在open后第一次fetch前是NULL。下面我会把整个复现路径拆开,从环境准备到配置片段,再到验证请求和排错,一步步走完。
2. TaoToken 统一 Key 前置准备:CC Switch 配置片段与接入步骤
在开始复现之前,需要先把调用链路搭好。这里用的是 TaoToken 统一 Key 的方式,配合 CC Switch 做模型切换。TaoToken 是一个面向开发者的模型接入服务,官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api 。它的作用是让你用一个统一的 Key 去调用不同的模型,省去每个模型单独配 Key 的麻烦。
CC Switch 是一个模型配置切换工具,你可以把它理解成一个「配置文件管理器」,它帮你把不同模型的 Base URL、Key、Model ID 组织成可切换的配置。对于代码分析场景,我通常会把 Oracle PL/SQL 相关的分析任务指向一个擅长代码理解的模型,这样在审查%NOTFOUND这类逻辑时,模型能更准确地指出 fetch 顺序问题。
先拿到统一 Key。进入控制台页面 https://taotoken.net/console ,在 API Keys 页面 https://taotoken.net/api-keys 创建一个新的 Key。创建时建议给它起一个能识别的名字,比如plsql-review,方便后续在 CC Switch 里对应。Key 创建后只显示一次,复制下来存好。
接下来是 CC Switch 的配置。CC Switch 的配置文件通常放在用户目录下的.cc-switch文件夹里,具体路径根据你的系统不同:
- macOS / Linux:
~/.cc-switch/config.json - Windows:
C:\Users\你的用户名\.cc-switch\config.json
如果你用的是 Claude Code 的配置方式,路径可能是~/.claude/settings.json。下面给出一段可复制的 JSON 配置片段,你可以直接改掉 Key 和 Model ID 后使用:
{ "providers": [ { "name": "taotoken-plsql", "baseUrl": "https://taotoken.net/api", "apiKey": "sk-你的统一Key", "models": [ { "id": "claude-sonnet-4-20250514", "name": "Claude Sonnet 4" } ], "defaultModel": "claude-sonnet-4-20250514" } ], "activeProvider": "taotoken-plsql" }这段配置里三个关键字段必须写全:baseUrl填https://taotoken.net/api,apiKey填你刚创建的统一 Key,models[].id填你要用的 Model ID。这三个就是所谓的「三件套」:Base URL、Key、Model ID。缺任何一个都会导致请求失败。
如果你用的是 Codex 的auth.json方式,配置结构会不太一样,通常在~/.codex/auth.json:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的统一Key", "model": "claude-sonnet-4-20250514" }注意这里的字段名是下划线风格,和 CC Switch 的驼峰风格不同,别混用。配置写完后保存,重启你的分析工具,让它重新读取配置。
配置完成后,你可以先用一个简单的请求验证链路是否通。打开终端,用 curl 发一个最小请求:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的统一Key" \ -d '{ "model": "claude-sonnet-4-20250514", "messages": [ {"role": "user", "content": "用一句话解释 Oracle PL/SQL 中 %NOTFOUND 的含义"} ] }'如果返回里能看到choices字段和模型输出,说明链路已经通了。这一步很重要,因为后面分析 PL/SQL 代码时,如果链路不通,你会分不清是代码问题还是配置问题。
3. 可复制配置:含 %NOTFOUND 的 PL/SQL 样例与 CC Switch 参数对照
配置链路通了之后,接下来把 PL/SQL 样例准备好。下面这段代码就是典型的「fetch 放在 exit when 后面」的错误写法,也是代码分析里最常见的坑:
declare cursor c is select ename from emp20; v_flag boolean; v_ename emp20.ename%type; begin open c; loop v_flag := c%notfound; if v_flag then dbms_output.put_line('true'); dbms_output.put_line(v_ename); elsif v_flag = false then dbms_output.put_line('false'); dbms_output.put_line(v_ename); else dbms_output.put_line('null'); dbms_output.put_line(v_ename); end if; exit when c%notfound; fetch c into v_ename; end loop; close c; end;这段代码的问题在于exit when c%notfound写在了fetch前面。第一次循环时,open刚执行完,还没fetch,所以c%notfound是NULL,v_flag也是NULL,会走进else分支打印null。然后exit when c%notfound判断为NULL,不退出,接着执行fetch,拿到第一条记录。第二次循环,v_flag是上一次fetch的结果,如果还有记录就是FALSE,打印false和当前v_ename。问题出在最后一次:当fetch拿到最后一条记录后,下一次循环v_flag还是FALSE,会再打印一次false和最后一条v_ename,然后exit when c%notfound才判断为TRUE退出。结果就是最后一条记录被多输出了一遍。
正确的写法是把fetch放在exit when前面:
declare cursor c is select ename from emp20; v_ename emp20.ename%type; begin open c; loop fetch c into v_ename; exit when c%notfound; dbms_output.put_line(v_ename); end loop; close c; end;这样每次循环先fetch,%NOTFOUND反映的是这次fetch的结果,拿到记录就打印,拿不到就退出,不会多打。
现在把这段代码交给模型分析。在 CC Switch 里,你需要确认当前激活的 provider 是taotoken-plsql,并且 Model ID 是claude-sonnet-4-20250514。下面是一个参数对照表,帮你确认配置项和实际值是否一致:
| 配置项 | CC Switch 字段 | 实际值 | 说明 |
|---|---|---|---|
| Base URL | baseUrl | https://taotoken.net/api | 统一接入地址 |
| API Key | apiKey | sk-你的统一Key | 控制台创建 |
| Model ID | models[].id | claude-sonnet-4-20250514 | 按需替换 |
| 默认模型 | defaultModel | claude-sonnet-4-20250514 | 与 Model ID 一致 |
| 激活项 | activeProvider | taotoken-plsql | 对应 provider 名称 |
如果你用的是 Cline MCP 的方式,配置会写在 MCP 的 settings 里,结构类似,但字段名可能不同。核心还是那三件套:Base URL、Key、Model ID。只要这三个对上了,模型就能正常接收你的 PL/SQL 代码并给出分析。
把上面那段错误代码和正确代码一起发给模型,让它对比分析%NOTFOUND的判定逻辑。你可以这样提问:
下面有两段 Oracle PL/SQL 代码,都用了游标 %NOTFOUND,但输出结果不同。 请分析 %NOTFOUND 在这两段代码中的判定时机,并解释为什么第一段会多输出最后一条记录。 代码一: [粘贴错误代码] 代码二: [粘贴正确代码]模型返回的分析里,应该会指出%NOTFOUND取决于最近一次 fetch 的结果,以及open后第一次 fetch 前它是NULL。如果模型没有提到NULL这个点,你可以追问一句「open 后第一次 fetch 前 %NOTFOUND 是什么值」,看它是否能准确回答。
4. 验证请求与成功结果:执行日志比对与 %NOTFOUND 输出确认
配置和分析都准备好后,最关键的一步是实际执行并比对日志。这一步不能省,因为模型分析得再对,最终还是要看真实数据库的输出。
先在 Oracle 里建一张测试表emp20,插入几条数据:
create table emp20 (ename varchar2(50)); insert into emp20 values ('SMITH'); insert into emp20 values ('ALLEN'); insert into emp20 values ('WARD'); commit;然后执行第一段错误代码,开启dbms_output:
set serveroutput on; declare cursor c is select ename from emp20; v_flag boolean; v_ename emp20.ename%type; begin open c; loop v_flag := c%notfound; if v_flag then dbms_output.put_line('true'); dbms_output.put_line(v_ename); elsif v_flag = false then dbms_output.put_line('false'); dbms_output.put_line(v_ename); else dbms_output.put_line('null'); dbms_output.put_line(v_ename); end if; exit when c%notfound; fetch c into v_ename; end loop; close c; end; /预期输出应该是这样的:
null false SMITH false ALLEN false WARD false WARD注意最后两行:false和WARD出现了两次。这就是多输出一遍的证据。第一次WARD是正常拿到第三条记录时打印的,第二次WARD是下一次循环v_flag还是FALSE时又打印了一遍,然后才退出。
再执行正确代码:
set serveroutput on; declare cursor c is select ename from emp20; v_ename emp20.ename%type; begin open c; loop fetch c into v_ename; exit when c%notfound; dbms_output.put_line(v_ename); end loop; close c; end; /预期输出:
SMITH ALLEN WARD只有三行,没有重复。把这两段输出日志保存下来,和模型的分析结果做比对。如果模型准确指出了「第一次循环%NOTFOUND为NULL」和「最后一条记录多输出」这两个点,说明分析链路是有效的。
你还可以进一步验证%NOTFOUND在open后第一次fetch前的值。单独跑一段:
declare cursor c is select ename from emp20; v_ename emp20.ename%type; begin open c; if c%notfound is null then dbms_output.put_line('open 后第一次 fetch 前:%NOTFOUND 是 NULL'); end if; fetch c into v_ename; if c%notfound = false then dbms_output.put_line('fetch 到记录后:%NOTFOUND 是 FALSE'); end if; close c; end; /预期输出:
open 后第一次 fetch 前:%NOTFOUND 是 NULL fetch 到记录后:%NOTFOUND 是 FALSE这段验证能直接证明%NOTFOUND的判定时机。把这段日志也保存下来,作为代码分析的证据。
如果你在验证过程中想换一个模型再分析一遍,可以在 CC Switch 里切换 provider,或者直接改defaultModel字段。TaoToken 的统一 Key 支持多个模型,你可以在模型对话页面 https://taotoken.net/models 查看可用模型列表,选一个适合代码分析的再跑一遍。对比不同模型对同一段 PL/SQL 的分析结果,也能帮你判断哪个模型在 Oracle 语法细节上更靠谱。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth 报错对照
配置和验证过程中,最容易卡住的不是 PL/SQL 本身,而是接入链路的各种报错。下面把几个高频错误和排查路径列出来,你可以对照自己的报错信息定位。
401 Unauthorized
这是最常见的错误,通常出现在 curl 请求或 CC Switch 配置后第一次调用时。报错信息类似:
{"error":{"message":"Invalid API key","type":"invalid_request_error"}}排查顺序:先确认apiKey字段填的是sk-开头的完整 Key,没有多余空格;再确认这个 Key 是在 https://taotoken.net/api-keys 创建的,并且没有过期或被删除;最后确认请求头里的Authorization格式是Bearer sk-xxx,不是Basic或其他。如果 Key 没问题,检查baseUrl是不是写成了https://taotoken.net/api/带了多余斜杠,有些工具对末尾斜杠敏感。
local proxy failed
这个报错通常出现在 CC Switch 或 Claude Code 启动时,信息类似:
local proxy failed: connection refused它表示本地代理层没有正常启动。排查顺序:先确认 CC Switch 进程是否在运行;再检查配置文件路径是否正确,比如~/.cc-switch/config.json是否存在且 JSON 格式合法;然后确认activeProvider指向的 provider 名称和providers[].name完全一致,大小写敏感。如果 JSON 里有语法错误,比如多了逗号或少了引号,CC Switch 会启动失败,但报错信息可能不直接指向 JSON 解析问题,这时候用jq检查一下:
jq . ~/.cc-switch/config.json如果输出报错,就说明 JSON 格式有问题,按提示修。
reading choices 报错
这个报错通常出现在模型返回阶段,信息类似:
error reading choices: unexpected end of JSON input它表示请求发出去了,但返回的响应体不完整或格式不对。排查顺序:先确认model字段填的 Model ID 是真实存在的,如果填了一个不存在的模型名,服务端可能返回非标准响应;再确认请求体是合法 JSON,特别是messages数组格式正确;最后检查网络是否稳定,如果响应被截断也会出现这个错误。你可以用 curl 加-v参数看完整响应:
curl -v -X POST https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的统一Key" \ -d '{"model":"claude-sonnet-4-20250514","messages":[{"role":"user","content":"test"}]}'看返回的 HTTP 状态码和响应体,能更快定位。
OAuth 相关报错
如果你用的是 Claude Code 的 OAuth 方式,可能会遇到:
OAuth token expired or invalid排查顺序:先确认你用的是 API Key 方式还是 OAuth 方式,两者不能混用;如果配置里同时有apiKey和 OAuth 相关字段,可能会冲突;再确认settings.json里的字段名和工具版本匹配,不同版本的 Claude Code 配置字段可能有差异。最稳妥的方式是只保留 API Key 方式,把 OAuth 相关字段清掉,用统一 Key 接入。
%NOTFOUND 分析结果不对
如果模型分析结果和实际执行日志对不上,先检查你发给模型的代码是否完整,特别是open、fetch、exit when的顺序有没有贴错;再确认模型是否真的理解了 Oracle 的布尔NULL语义,有些模型会把%NOTFOUND默认当成FALSE,这时候你需要追问「open 后第一次 fetch 前 %NOTFOUND 是什么值」来验证。如果模型还是答错,换一个模型再试,或者在提问里直接给出执行日志,让模型基于日志反推逻辑。
排查完这些,基本能覆盖从配置到分析的主要卡点。如果遇到本文没列出的报错,可以去接入文档页面 https://taotoken.net/doc 查对应说明,或者在控制台看请求日志,定位是配置问题还是代码问题。
6. 从复现到落地:把 %NOTFOUND 分析接入日常代码审查
走完上面几步,你应该已经能独立复现%NOTFOUND的判定逻辑,并且用 TaoToken 统一 Key 加 CC Switch 的方式让模型参与代码分析。这套流程的价值不只是解决一个%NOTFOUND问题,而是可以迁移到其他 PL/SQL 属性的审查上,比如%ROWCOUNT、%ISOPEN、%FOUND,它们的判定时机和%NOTFOUND类似,都依赖最近一次操作的结果。
日常代码审查里,我建议把这类分析做成固定动作:先把待审查的 PL/SQL 片段和对应的执行日志一起发给模型,让模型基于日志反推逻辑,而不是只给代码让它猜。这样能减少模型对 Oracle 语义的误判。如果审查的是迁移场景,比如从 Oracle 迁到其他数据库,%NOTFOUND的语义差异往往是重点,这时候可以让模型对比两边的游标行为,提前标出需要改写的地方。
对于长期做代码分析和 Agent 任务的场景,可以考虑用 Coding Plan 的方式,把模型调用和审查流程固化下来,减少每次手动配置的成本。具体可以在 https://taotoken.net/coding-plan 查看适合的套餐。如果只是偶尔验证模型对某段代码的理解,用模型对话页面 https://taotoken.net/models 直接试就行,不用配 CC Switch。
最后留一个实用技巧:在写 PL/SQL 游标循环时,养成「先 fetch 再判断」的习惯,把exit when c%notfound紧跟在fetch后面。这样%NOTFOUND永远反映的是刚执行的 fetch 结果,不会出现NULL干扰,也不会多输出最后一条记录。这个习惯能帮你避开大部分游标相关的坑,比事后分析更省事。