ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Oracle基础积累:for循环里嵌子查询,TaoToken帮你把配置骨架先搭好

Oracle基础积累:for循环里嵌子查询,TaoToken帮你把配置骨架先搭好 1. 先搞清楚for 循环里嵌子查询到底在解决什么问题刚接触 Oracle 的朋友看到FOR TMP IN (SELECT ...) LOOP这种写法第一反应往往是「这不就是把查询结果当数组遍历吗」。这个直觉基本对但细节里藏着不少坑。Oracle PL/SQL 里的FOR ... IN (子查询) LOOP本质是一个隐式游标循环数据库先执行括号里的子查询把结果集挂在一个游标上然后逐行取出来赋给循环变量TMP每取一行执行一次循环体。它适合的场景很明确——你需要把一张表或多张表关联后的数据按行做二次加工、写进另一张表、或者做逐行校验。典型场景就是传感器数据这种「宽表拆窄表」或「窄表拼宽表」的活儿。比如原始表SENSOR_COLLECT_DATA里温度和湿度是两行记录靠DATA_TYPE区分1 是温度、2 是湿度但目标表SENSOR_COLLECT_DATA_DETAIL要求一行里同时有温度三个值和湿度三个值。这时候就得在子查询里把温度子集和湿度子集做LEFT JOIN拼成一行再在循环里插进目标表。为什么不用一条INSERT INTO ... SELECT直接搞定很多时候确实可以而且性能更好。但实际项目里循环体里往往不止一句 INSERT可能还要调存储过程、写日志、做条件分支、累加统计。这时候FOR循环的可读性和可维护性就体现出来了。所以这个知识点不是「炫技」而是你迟早要碰的基础功。这篇就按「能直接抄去用」的标准来写先给可复制的循环加子查询骨架再讲几个新手最容易踩的坑最后用一次查询验证循环结果对不对。中间会顺带把 TaoToken 的统一 Key 和 API 通道配置示例放进来方便你在本地或团队环境里统一管理模型调用配置。2. TaoToken 前置把 Key 和通道先备好在写 PL/SQL 之前先把工具链的配置骨架搭好。TaoToken 的作用是提供统一的 API 通道和 Key 管理让你在本地脚本、IDE 插件、命令行工具里用同一套凭证不用每个工具单独配一遍。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 根地址是 https://taotoken.net/api 。你需要先拿到一个 API Key。进控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 然后在 API Keys 页面生成https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。Key 生成后只显示一次复制到安全的地方。拿到 Key 之后不同工具的配置方式不一样。下面给两个最常见的配置文件示例你可以直接改 Key 和模型名。2.1 settings.json 配置示例很多 CLI 工具和编辑器插件读settings.json。典型结构如下{ apiKey: sk-你的TaoTokenKey, baseUrl: https://taotoken.net/api, model: claude-sonnet-4-20250514, maxTokens: 4096, temperature: 0.2 }注意baseUrl不要带末尾斜杠也不要自己拼/v1具体路径由工具内部处理。model字段填你实际要用的模型标识不同工具支持的模型名可能不同以文档为准https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。2.2 config.toml 配置示例有些工具用 TOML 格式比如某些 coding agent 或命令行助手[provider] name taotoken api_key sk-你的TaoTokenKey base_url https://taotoken.net/api [model] default claude-sonnet-4-20250514 max_tokens 4096 temperature 0.2如果你用的是 Claude Code 这类工具接入方式略有不同参考https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-code-anthropicutm_campaignrewrite 。长期做编码或 Agent 任务的话可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。配置好之后建议先用模型对话页面发一条测试消息确认 Key 和通道是通的https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 。这一步别跳过很多「代码跑不通」其实是 Key 没配对。3. 可复制配置for 循环嵌子查询的完整骨架现在进入正题。下面这个存储过程就是「for 循环里嵌子查询」的标准骨架你可以直接复制到 PL/SQL Developer 或 SQL Developer 里改表名和字段名。3.1 建表语句先跑一遍先把两张表建好不然存储过程编译不过。原始采集表CREATE TABLE SENSOR_COLLECT_DATA ( sensor_id NUMBER(16) NOT NULL, data_date DATE NOT NULL, data_type NUMBER(2) NOT NULL, region VARCHAR2(16) NOT NULL, s1 NUMBER(6,3), s2 NUMBER(6,3), s3 NUMBER(6,3) ); COMMENT ON TABLE SENSOR_COLLECT_DATA IS 传感器采集数据; COMMENT ON COLUMN SENSOR_COLLECT_DATA.sensor_id IS 数据id实体唯一标识; COMMENT ON COLUMN SENSOR_COLLECT_DATA.data_date IS 数据日期; COMMENT ON COLUMN SENSOR_COLLECT_DATA.data_type IS 数据类型(1:温度、2:湿度); COMMENT ON COLUMN SENSOR_COLLECT_DATA.region IS 传感器安装区域; COMMENT ON COLUMN SENSOR_COLLECT_DATA.s1 IS 传感器采集的值1; COMMENT ON COLUMN SENSOR_COLLECT_DATA.s2 IS 传感器采集的值2; COMMENT ON COLUMN SENSOR_COLLECT_DATA.s3 IS 传感器采集的值3;目标明细表CREATE TABLE SENSOR_COLLECT_DATA_DETAIL ( sensor_id NUMBER(16) NOT NULL, data_date DATE NOT NULL, region VARCHAR2(16) NOT NULL, wendu1 NUMBER(6,3), wendu2 NUMBER(6,3), wendu3 NUMBER(6,3), shidu1 NUMBER(6,3), shidu2 NUMBER(6,3), shidu3 NUMBER(6,3) ); COMMENT ON TABLE SENSOR_COLLECT_DATA_DETAIL IS 传感器采集数据明细; COMMENT ON COLUMN SENSOR_COLLECT_DATA_DETAIL.sensor_id IS 数据id实体唯一标识; COMMENT ON COLUMN SENSOR_COLLECT_DATA_DETAIL.data_date IS 数据日期; COMMENT ON COLUMN SENSOR_COLLECT_DATA_DETAIL.region IS 传感器安装区域; COMMENT ON COLUMN SENSOR_COLLECT_DATA_DETAIL.wendu1 IS 传感器采集的温度值1; COMMENT ON COLUMN SENSOR_COLLECT_DATA_DETAIL.wendu2 IS 传感器采集的温度值2; COMMENT ON COLUMN SENSOR_COLLECT_DATA_DETAIL.wendu3 IS 传感器采集的温度值3; COMMENT ON COLUMN SENSOR_COLLECT_DATA_DETAIL.shidu1 IS 传感器采集的湿度值1; COMMENT ON COLUMN SENSOR_COLLECT_DATA_DETAIL.shidu2 IS 传感器采集的湿度值2; COMMENT ON COLUMN SENSOR_COLLECT_DATA_DETAIL.shidu3 IS 传感器采集的湿度值3;3.2 存储过程骨架核心逻辑子查询里用两个内联视图分别筛出温度和湿度再按sensor_id和data_date做LEFT JOIN循环体里逐行插入明细表。CREATE OR REPLACE PROCEDURE PRO_TEST_CURSOR_FOR2( ERRORMSG OUT VARCHAR2 ) IS BEGIN ERRORMSG : ; FOR TMP IN ( SELECT WENDU.SENSOR_ID, WENDU.DATA_DATE, WENDU.REGION, DECODE(WENDU.S1, NULL, 0, WENDU.S1) AS WENDU1, DECODE(WENDU.S2, NULL, 0, WENDU.S2) AS WENDU2, DECODE(WENDU.S3, NULL, 0, WENDU.S3) AS WENDU3, DECODE(SHIDU.S1, NULL, 0, SHIDU.S1) AS SHIDU1, DECODE(SHIDU.S2, NULL, 0, SHIDU.S2) AS SHIDU2, DECODE(SHIDU.S3, NULL, 0, SHIDU.S3) AS SHIDU3 FROM (SELECT * FROM SENSOR_COLLECT_DATA WHERE DATA_TYPE 1) WENDU LEFT JOIN (SELECT * FROM SENSOR_COLLECT_DATA WHERE DATA_TYPE 2) SHIDU ON WENDU.SENSOR_ID SHIDU.SENSOR_ID AND WENDU.DATA_DATE SHIDU.DATA_DATE ) LOOP INSERT INTO SENSOR_COLLECT_DATA_DETAIL ( SENSOR_ID, DATA_DATE, REGION, WENDU1, WENDU2, WENDU3, SHIDU1, SHIDU2, SHIDU3 ) VALUES ( TMP.SENSOR_ID, TMP.DATA_DATE, TMP.REGION, TMP.WENDU1, TMP.WENDU2, TMP.WENDU3, TMP.SHIDU1, TMP.SHIDU2, TMP.SHIDU3 ); END LOOP; COMMIT; EXCEPTION WHEN OTHERS THEN ERRORMSG : PRO_TEST_CURSOR_FOR2抛出异常: || SQLERRM; ROLLBACK; END PRO_TEST_CURSOR_FOR2;几个关键点先划出来。第一FOR TMP IN (子查询)里的TMP是隐式声明的记录类型字段名就是子查询的列别名所以子查询里必须用AS WENDU1这种别名否则循环体里引用不到。第二DECODE用来把 NULL 转成 0因为LEFT JOIN在湿度缺失时会产生 NULL直接插入目标表虽然不报错但后续计算容易出问题。第三异常处理里加了ROLLBACK避免部分插入后出错留下脏数据。3.3 造点测试数据建完表跑几条数据进去方便验证INSERT INTO SENSOR_COLLECT_DATA VALUES (1001, DATE 2024-06-01, 1, A区, 25.1, 25.3, 25.5); INSERT INTO SENSOR_COLLECT_DATA VALUES (1001, DATE 2024-06-01, 2, A区, 60.1, 60.2, 60.3); INSERT INTO SENSOR_COLLECT_DATA VALUES (1002, DATE 2024-06-01, 1, B区, 26.0, 26.2, 26.4); INSERT INTO SENSOR_COLLECT_DATA VALUES (1002, DATE 2024-06-01, 2, B区, 55.0, 55.1, 55.2); COMMIT;注意这里sensor_id和data_date组合起来才是唯一键单看sensor_id会重复因为温度和湿度各一行。4. 验证请求跑一遍看结果对不对存储过程编译通过后用一段匿名块调用它把ERRORMSG打出来DECLARE V_MSG VARCHAR2(4000); BEGIN PRO_TEST_CURSOR_FOR2(V_MSG); DBMS_OUTPUT.PUT_LINE(返回信息: || V_MSG); END; /如果ERRORMSG是空字符串说明执行成功。然后查目标表SELECT * FROM SENSOR_COLLECT_DATA_DETAIL ORDER BY SENSOR_ID, DATA_DATE;预期结果应该是两行每行同时有温度和湿度的值。比如 1001 那行WENDU1到WENDU3是 25.1/25.3/25.5SHIDU1到SHIDU3是 60.1/60.2/60.3。如果湿度那几列是 0 而不是 NULL说明DECODE生效了如果整行湿度都是 NULL那可能是LEFT JOIN的条件没匹配上回去检查DATA_TYPE和关联字段。再补一个校验查询确认没有重复插入SELECT SENSOR_ID, DATA_DATE, COUNT(*) AS CNT FROM SENSOR_COLLECT_DATA_DETAIL GROUP BY SENSOR_ID, DATA_DATE HAVING COUNT(*) 1;这条查出来是空说明每个传感器每天只有一条明细符合预期。如果跑出重复行多半是存储过程被调了两次或者子查询里 JOIN 产生了笛卡尔积。5. 本篇常见错排查5.1 ORA-00904: 标识符无效最常见的原因是子查询里的列别名和循环体里引用的名字对不上。比如子查询写DECODE(WENDU.S1, NULL, 0, WENDU.S1) AS WENDU1循环体里却写TMP.WENDU_1Oracle 找不到这个字段就报 ORA-00904。解决办法是别名统一用短横线或下划线别混用改完重新编译。5.2 ORA-01422: 实际返回行数超过请求行数这个错通常出现在你把FOR循环换成了SELECT INTO的写法。FOR循环本身不会报这个因为它就是设计来遍历多行的。如果你在循环体里又写了一个SELECT ... INTO但没加WHERE条件就可能撞上。检查循环体里的单行查询确保WHERE能唯一定位。5.3 循环跑完但目标表没数据先看COMMIT有没有执行。如果存储过程里COMMIT写在EXCEPTION之前但异常处理里又ROLLBACK了那数据会被回滚掉。另一个可能是子查询本身返回空集循环体一次都没进。单独把子查询拎出来跑一下SELECT COUNT(*) FROM ( SELECT WENDU.SENSOR_ID, WENDU.DATA_DATE FROM (SELECT * FROM SENSOR_COLLECT_DATA WHERE DATA_TYPE 1) WENDU LEFT JOIN (SELECT * FROM SENSOR_COLLECT_DATA WHERE DATA_TYPE 2) SHIDU ON WENDU.SENSOR_ID SHIDU.SENSOR_ID AND WENDU.DATA_DATE SHIDU.DATA_DATE );如果返回 0说明源表里没有DATA_TYPE 1的数据或者关联条件写错了。5.4 性能问题循环里逐行插入太慢FOR循环逐行 INSERT 在数据量大时确实慢因为每行都是一次上下文切换。如果目标表数据量上万建议改成INSERT INTO ... SELECT批量插入或者用BULK COLLECT加FORALL。但如果是学习阶段先把逐行循环写对再考虑优化。另外循环体里避免再嵌子查询能提前算好的值尽量在子查询里算完。5.5 配置类问题Key 对了但请求 401如果你在用 TaoToken 的 API 通道调试脚本遇到 401 先检查三件事Key 有没有复制完整前后不能有空格、baseUrl是不是https://taotoken.net/api、请求头里的认证字段名对不对。不同工具对认证头的写法要求不同有的用Authorization: Bearer有的用x-api-key以文档为准。接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。6. 把配置和代码串起来用回到实际工作流。你写 PL/SQL 的时候可能同时开着编辑器插件做代码补全、开着命令行工具跑脚本、开着对话窗口查语法。如果每个工具都单独配 Key改一次要改好几处。用 TaoToken 的统一通道settings.json和config.toml里填同一个baseUrl和 Key换模型时只改model字段就行。具体操作顺序建议这样先去 API Keys 页面生成 Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 然后把上面第 2 节的配置文件复制到对应工具目录改掉sk-你的TaoTokenKey这一行。配完用模型对话页面发一条「你好」确认通道通https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 通了再回去跑 PL/SQL。存储过程这边我自己的习惯是先在 SQL Developer 里把子查询单独跑通确认返回的列和行数符合预期再包进FOR循环。这样出问题时能快速定位是子查询写错了还是循环体写错了。另外ERRORMSG这个出参别省它能在你调用存储过程时把异常信息带出来比只看「编译通过」有用得多。最后留一个实用技巧如果你要反复调试同一个存储过程可以在循环体里加一句DBMS_OUTPUT.PUT_LINE(处理: || TMP.SENSOR_ID || || TMP.DATA_DATE);跑的时候打开输出窗口能直观看到每一行有没有进循环。调完记得删掉不然数据量大时输出会拖慢速度。
返回列表