Oracle 存储过程(Stored Procedure)是存储在数据库中的一组预编译 SQL 和 PL/SQL 代码块,用于封装复杂的业务逻辑。
以下是完整的编写指南,包含基本结构 、参数类型 、常用逻辑 以及完整示例。
1. 基本语法结构
一个标准的存储过程由三部分组成:声明部分 、执行部分 、异常处理部分。
sql
CREATE OR REPLACE PROCEDURE 过程名 (
参数1 IN 数据类型, -- 输入参数
参数2 OUT 数据类型, -- 输出参数
参数3 IN OUT 数据类型 -- 输入输出参数
) IS
-- 【声明部分】定义局部变量、游标、常量等
v_variable_name VARCHAR2(100);
v_count NUMBER := 0;
BEGIN
-- 【执行部分】编写具体的 SQL 和业务逻辑
SELECT count(*) INTO v_count FROM 表名 WHERE 条件;
IF v_count > 0 THEN
-- 业务逻辑...
参数2 := '成功'; -- 给输出参数赋值
ELSE
参数2 := '失败';
END IF;
EXCEPTION
-- 【异常处理部分】捕获并处理错误
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('未找到数据');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('发生错误: ' || SQLERRM);
END 过程名;
2. 三种参数模式
| 模式 | 关键字 | 说明 | 使用场景 |
|---|---|---|---|
| 输入 | IN |
默认模式。调用时传入值,过程内只读。 | 查询条件、配置值。 |
| 输出 | OUT |
调用时需传入变量接收返回值,过程内可赋值。 | 返回统计结果、状态码、生成的主键ID。 |
| 输入输出 | IN OUT |
传入初始值,过程处理后修改该值并返回。 | 累加计算、字符串拼接处理。 |
3. 实战示例:用户信息处理
假设有一个表 USERS (ID, NAME, AGE, CITY)。我们要写一个存储过程:
- 根据 ID 查询用户。
- 如果用户年龄大于 18 岁且城市包含"市",则去掉城市后缀(类似你之前的 JS 逻辑)。
- 返回处理后的城市和状态消息。
sql
CREATE OR REPLACE PROCEDURE PROC_UPDATE_USER_CITY (
p_user_id IN NUMBER, -- 输入:用户ID
p_city_out OUT VARCHAR2, -- 输出:处理后的城市
p_msg OUT VARCHAR2 -- 输出:执行消息
) IS
v_age NUMBER;
v_city VARCHAR2(100);
v_name VARCHAR2(100);
BEGIN
-- 1. 查询数据
SELECT age, city, name
INTO v_age, v_city, v_name
FROM USERS
WHERE id = p_user_id;
-- 2. 业务逻辑判断
IF v_age > 18 THEN
-- 模拟去除城市后缀逻辑
IF INSTR(v_city, '市') > 0 AND v_city NOT LIKE '%自治州%' THEN
v_city := REPLACE(v_city, '市', '');
END IF;
p_msg := '用户 ' || v_name || ' 处理成功,新城市为:' || v_city;
ELSE
p_msg := '用户未成年,不处理城市信息。';
END IF;
-- 3. 赋值给输出参数
p_city_out := v_city;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_msg := '错误:未找到 ID 为 ' || p_user_id || ' 的用户';
p_city_out := NULL;
WHEN OTHERS THEN
p_msg := '系统异常:' || SQLERRM;
p_city_out := NULL;
-- 可选:记录日志到日志表
-- INSERT INTO error_log VALUES (SYSDATE, SQLERRM);
END PROC_UPDATE_USER_CITY;
4. 如何调用存储过程
方法 A:在 SQL Developer / PLSQL Developer 中测试
sql
DECLARE
v_result_city VARCHAR2(100);
v_message VARCHAR2(200);
BEGIN
-- 调用过程
PROC_UPDATE_USER_CITY(
p_user_id => 1001, -- 传入具体值
p_city_out => v_result_city, -- 传入变量接收
p_msg => v_message
);
-- 打印结果
DBMS_OUTPUT.PUT_LINE(v_message);
DBMS_OUTPUT.PUT_LINE('处理后城市:' || v_result_city);
END;
注意:使用前需开启输出显示 SET SERVEROUTPUT ON;
方法 B:在 Java / Python / Kettle 中调用
- Java (JDBC): 使用
CallableStatement。 - Kettle: 使用 "Call DB Procedure" 组件,选择刚才创建的过程名,映射输入输出参数即可。
5. 常用技巧与注意事项
-
CREATE OR REPLACE:- 始终加上
OR REPLACE,这样如果过程已存在,会直接覆盖更新,而不会报错。
- 始终加上
-
变量赋值 :
- 使用
:=进行赋值(如v_count := 1;)。 - 使用
SELECT ... INTO ...将查询结果赋给变量。
- 使用
-
动态 SQL :
- 如果表名或列名需要动态变化,使用
EXECUTE IMMEDIATE。
sqlEXECUTE IMMEDIATE 'UPDATE ' || v_table_name || ' SET status=1 WHERE id=:1' USING p_id; - 如果表名或列名需要动态变化,使用
-
事务控制 :
- 存储过程内部通常不写
COMMIT或ROLLBACK,除非明确设计为独立事务。最好由调用者(外部程序)决定何时提交事务,以保持灵活性。
- 存储过程内部通常不写
-
调试 :
- 使用
DBMS_OUTPUT.PUT_LINE('变量值:' || v_var);打印调试信息。
- 使用
6. 常见错误排查
- ORA-06550 / PLS-00201: 标识符必须声明(检查变量名拼写,或是否有权限访问该表)。
- ORA-01403 : 未找到数据(
SELECT INTO没查到数据,需在EXCEPTION中捕获NO_DATA_FOUND)。 - ORA-01422 : 返回多于请求的行数(
SELECT INTO查出了多行,但变量只能存一行,需改用游标CURSOR)。
如果你需要将之前 Kettle 中的 JavaScript 清洗逻辑迁移到 Oracle 存储过程中,我可以帮你直接转换那段代码。