☰
Oracle 存储过程实战:按部门号返回员工姓名、工资与佣金(TaoToken 配置骨架)
2026/9/29 4:48:24 网站建设 项目流程

1. 从部门号到员工清单:这个存储过程到底解决什么问题

你在 Oracle 里维护员工表,业务方隔三差五来一句「把 20 号部门的人、工资、佣金拉出来看看」。每次都手写一条select ename, sal, comm from emp where deptno = 20当然能跑,但问题是:调用方可能是个 Java 服务、可能是个报表脚本、也可能是个刚入门的同事,他们不想关心表结构,只想「给个部门号,拿回一列结果」。这时候把查询逻辑封进存储过程,就是最省心的做法。

这篇要做的,是定义一个接收部门号、返回该部门员工姓名、工资、佣金的 PL/SQL 存储过程。我会给你两套可复制的写法:一套用显式游标配合dbms_output直接打印,适合在 SQL*Plus 或 SQL Developer 里快速验证;另一套用sys_refcursor把结果集作为out参数抛出去,适合被外部程序消费。两套都会给出建表脚本、过程定义、调用脚本和结果校验动作。

同时,因为现在很多团队会用统一的模型/API 通道来辅助写 SQL、生成存储过程骨架、做代码审查,我也会把 TaoToken 的settings.json配置骨架和连通性验证动作一并给出。目标很明确:你照着走一遍,查询能跑通,结果能对上,配置能验证。

适合谁看:刚接触 PL/SQL 存储过程、被游标和%rowtype绕晕的开发者;需要把查询封装成接口给外部调用的后端同学;以及想用统一 Key 通道来辅助生成和检查 SQL 的人。

2. 前置准备:TaoToken 统一 Key 与 API 通道配置骨架

在写存储过程之前,先把工具链配好。TaoToken 提供统一的 Key 和 API 通道,官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api 。它的作用是让你用一个 Key 去访问多种模型能力,写 SQL、生成存储过程、排查报错时不用来回切换账号。

先拿到 Key:进入控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,在 API Keys 页面创建一个新 Key,复制保存。这个 Key 就是后面settings.json里的凭证。

然后在你的项目或工具目录下建一个settings.json,配置骨架如下:

{ "provider": "taotoken", "api_base": "https://taotoken.net/api", "api_key": "sk-你的TaoTokenKey", "model": "claude-sonnet", "timeout": 60, "max_tokens": 4096 }

几个字段说明一下。api_base固定填https://taotoken.net/api,不要带多余路径;api_key换成你刚创建的那串;model按你实际要用的模型名填;timeout给 60 秒,生成较长的存储过程时不容易断。

注意:settings.json里不要提交真实 Key 到 Git 仓库,建议用环境变量注入,或者把文件加进.gitignore。

配置完成后做一次连通性验证。用 curl 发一个最小请求:

curl -X POST https://taotoken.net/api/v1/messages \ -H "Content-Type: application/json" \ -H "x-api-key: sk-你的TaoTokenKey" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-sonnet", "max_tokens": 64, "messages": [{"role": "user", "content": "回复 ok"}] }'

如果返回里有正常的文本内容,说明 Key 和通道都通了。这一步别跳过,后面用模型辅助生成存储过程时,通道不通会浪费很多时间在排查上。

3. 可复制配置:建表脚本与两套存储过程定义

先把测试数据准备好。假设我们用经典的emp表结构,建表和插数据脚本如下:

-- 建表 create table emp ( empno number(4) primary key, ename varchar2(20), sal number(7,2), comm number(7,2), deptno number(2) ); -- 插入测试数据 insert into emp values (7369, 'SMITH', 800, null, 20); insert into emp values (7499, 'ALLEN', 1600, 300, 30); insert into emp values (7521, 'WARD', 1250, 500, 30); insert into emp values (7566, 'JONES', 2975, null, 20); insert into emp values (7788, 'SCOTT', 3000, null, 20); insert into emp values (7839, 'KING', 5000, null, 10); commit;

这样 20 号部门有 SMITH、JONES、SCOTT 三个人,佣金都是 null;30 号部门有 ALLEN、WARD,佣金分别是 300 和 500。后面校验结果时用得上。

3.1 方案一:显式游标 + dbms_output 打印

这套写法适合在数据库客户端里直接看输出。核心是用emp.deptno%type让入参类型跟随表字段,用mysor%rowtype承接整行数据。

create or replace procedure mydure(dno emp.deptno%type) is cursor mysor is select ename, sal, comm from emp where deptno = dno; message mysor%rowtype; begin open mysor; loop fetch mysor into message; exit when mysor%notfound; dbms_output.put_line( '员工姓名:' || message.ename || ' 工资:' || message.sal || ' 佣金:' || nvl(to_char(message.comm), '无') ); end loop; close mysor; end; /

这里有个细节值得说:comm是number类型,直接和字符串拼接时如果值是 null,整个拼接结果会变成 null,输出就空了。所以用nvl(to_char(message.comm), '无')兜一下,保证佣金为空时也能看到「无」而不是一片空白。

调用脚本:

set serveroutput on; declare d_dno emp.deptno%type := 20; begin mydure(d_dno); end; /

set serveroutput on必须加,否则dbms_output.put_line的内容不会显示。执行后预期输出三行,分别是 SMITH、JONES、SCOTT 的姓名、工资和佣金。

3.2 方案二:sys_refcursor 作为 out 参数返回结果集

如果调用方是 Java、Python 这类外部程序,方案一的打印方式就没法用了,得把结果集抛出去。sys_refcursor是系统预定义的动态游标,正好干这个。

create or replace procedure proc_2( pno emp.deptno%type, list_cur out sys_refcursor ) is begin open list_cur for select ename, sal, comm from emp where deptno = pno; end; /

调用时定义一个sys_refcursor变量,再定义一个记录类型来承接每一行:

set serveroutput on; declare pno emp.deptno%type := 20; mycur sys_refcursor; type emp_rec is record( pname emp.ename%type, psal emp.sal%type, pcomm emp.comm%type ); list_rec emp_rec; begin proc_2(pno, mycur); loop fetch mycur into list_rec; exit when mycur%notfound; dbms_output.put_line( list_rec.pname || ' ' || list_rec.psal || ' ' || nvl(to_char(list_rec.pcomm), '无') ); end loop; close mycur; end; /

方案二的好处是结果集结构清晰,外部程序拿到游标后可以按列名取值,不用解析字符串。实际项目里我更推荐这套,扩展性更好。

4. 验证请求与成功结果:跑通查询并核对数据

配置和过程都写好了,现在做完整验证。按顺序执行下面几步。

第一步,确认过程编译成功。查一下数据字典:

select object_name, status from user_objects where object_type = 'PROCEDURE' and object_name in ('MYDURE', 'PROC_2');

status应该是VALID。如果是INVALID,多半是表名或字段名写错了,用show errors procedure mydure;看具体报错行。

第二步,跑方案一的调用,入参给 20。预期输出:

员工姓名:SMITH 工资:800 佣金:无 员工姓名:JONES 工资:2975 佣金:无 员工姓名:SCOTT 工资:3000 佣金:无

第三步,把入参换成 30,再跑一次。预期输出:

员工姓名:ALLEN 工资:1600 佣金:300 员工姓名:WARD 工资:1250 佣金:500

第四步,跑方案二的调用,入参给 20,输出应该和方案一一致,只是格式变成空格分隔。

第五步,做一个边界校验:传一个不存在的部门号,比如 99。两个过程都应该不报错,只是没有任何输出行。这说明游标的%notfound退出逻辑是正常的。

如果你在验证过程中想用模型帮忙检查 SQL 语法或者生成更多测试用例,可以走模型对话入口 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,把过程定义贴进去让它帮你审一遍。长期做编码和 Agent 任务的,可以看 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

5. 本篇常见错排查:游标、类型与输出那些坑

实际写的时候,报错基本集中在几个地方。我按出现频率排一下。

ORA-00942 表或视图不存在。最常见的原因是当前登录用户和表所属用户不一致。用select table_name from user_tables where table_name = 'EMP';确认表在当前 schema 下可见。如果表在别的 schema,要么加 schema 前缀,要么建同义词。

ORA-06550 / PLS-00201 标识符必须声明。多半是过程名拼错,或者过程没编译成功。先查user_objects的 status,再show errors看具体行。

dbms_output 没有任何输出。九成是忘了set serveroutput on。在 SQL*Plus 里这是会话级设置,每次新开窗口都要重新执行。SQL Developer 里则要确认「DBMS Output」面板已经启用。

佣金列输出为空白。前面提过,number类型的 null 和字符串拼接会传染。解决办法就是nvl(to_char(comm), '无'),先转字符再兜底。

sys_refcursor 调用后报 ORA-01000 超出打开游标数。这是游标没关。方案二的调用脚本里close mycur;不能漏,外部程序消费完结果集后也要记得关闭。

入参类型不匹配。用emp.deptno%type的好处是类型自动跟随,但如果你传的是字符串'20'而不是数字20,Oracle 会尝试隐式转换,某些情况下会出问题。调用时保证类型一致。

提示:如果过程体改了但调用还是旧结果,先确认create or replace执行成功,再检查是不是连到了别的数据库实例。

6. 把查询封装好之后:接入与后续动作

到这里,两套存储过程都能跑通,结果也核对过了。回到实际工程里,方案二更适合作为对外接口,因为sys_refcursor能被 JDBC、cx_Oracle、python-oracledb 等直接消费。Java 侧大致是这样调的:

CallableStatement cs = conn.prepareCall("{call proc_2(?, ?)}"); cs.setInt(1, 20); cs.registerOutParameter(2, OracleTypes.CURSOR); cs.execute(); ResultSet rs = (ResultSet) cs.getObject(2); while (rs.next()) { System.out.println(rs.getString("ename") + " " + rs.getBigDecimal("sal") + " " + rs.getBigDecimal("comm")); }

如果你在写这类接入代码时需要生成骨架或者排查类型映射问题,用 TaoToken 的 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 管理你的 Key,接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里有各语言的调用示例。Claude Code 相关的接入可以看 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

最后留一个实用习惯:每次改完存储过程,别只看编译状态,一定用真实部门号跑一遍调用,再传一个不存在的部门号验证空结果分支。这两步花不了一分钟,但能挡掉大部分「编译过了、一调就错」的情况。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询