一、存储过程的基本结构
参数名1 IN 数据类型 DEFAULT 默认值,
参数名2 OUT 数据类型,
参数名3 IN OUT 数据类型
)
AS
-- 声明部分:定义变量、常量、游标等
BEGIN
-- 执行部分:SQL语句、流程控制等
EXCEPTION
-- 异常处理部分(可选)
END;
/
二、第一个存储过程:给员工涨薪
来看一个最经典的例子——给指定员工涨薪:
in_emp_id IN NUMBER, -- 输入参数:员工ID
in_raise_pct IN NUMBER DEFAULT 0.05 -- 输入参数:涨薪比例,默认5%
)
AS
v_current_salary NUMBER; -- 声明一个变量,存当前薪水
BEGIN
-- 1. 查询当前薪水
SELECT salary INTO v_current_salary
FROM employees
WHERE employee_id = in_emp_id;
-- 2. 更新薪水
UPDATE employees
SET salary = salary * (1 + in_raise_pct)
WHERE employee_id = in_emp_id;
-- 3. 打印结果
DBMS_OUTPUT.PUT_LINE('员工' || in_emp_id ||
' 薪水从 ' || v_current_salary ||
' 涨到 ' || (v_current_salary * (1 + in_raise_pct)));
COMMIT; -- 提交事务
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('员工ID ' || in_emp_id || ' 不存在');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('出错了:' || SQLERRM);
ROLLBACK;
END raise_salary;
/
怎么调用呢?
-- 给员工ID为1001的涨薪10%
EXECUTE raise_salary(1001, 0.10);
-- 或者用CALL也可以
CALL raise_salary(1002);
-- 不传第二个参数,就按默认的5%涨
EXECUTE raise_salary(1003);
三、参数类型:IN、OUT、IN OUT
存储过程的参数有三种模式:
模式 含义
IN 输入参数,只能读不能改
OUT 输出参数,只能写不能读
IN OUT 既能读又能写
来看一个带输出参数的例子:
p_user_id IN INT, -- 输入:用户ID
p_user_name OUT VARCHAR2, -- 输出:用户姓名
p_user_age OUT INT -- 输出:用户年龄
)
AS
BEGIN
SELECT name, age
INTO p_user_name, p_user_age
FROM users
WHERE id = p_user_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
p_user_name := '用户不存在';
p_user_age := 0;
END;
/
调用的时候,需要先声明变量来接收输出值:
v_name VARCHAR2(50);
v_age INT;
BEGIN
get_user_info(1, v_name, v_age);
DBMS_OUTPUT.PUT_LINE('姓名:' || v_name || ',年龄:' || v_age);
END;
/
四、变量声明与使用
在存储过程的AS和BEGIN之间,可以声明各种变量:
AS
-- 普通变量
v_count INT;
v_name VARCHAR2(50);
v_today DATE;
-- 常量
v_rate CONSTANT NUMBER := 0.05;
-- 直接引用表字段的类型(推荐!表结构变了也不会报错)
v_id employees.employee_id%TYPE;
-- 引用整行记录的类型(常用于游标)
v_row employees%ROWTYPE;
BEGIN
v_today := SYSDATE;
v_count := 100;
-- ... 后续逻辑
END;
/
小窍门:用表名.字段名%TYPE来声明变量,这样就算表的字段类型变了,存储过程也不会因为类型不匹配而报错。
五、条件判断:IF语句
存储过程里可以做各种逻辑判断:
in_score IN NUMBER
)
AS
BEGIN
IF in_score >= 90 THEN
DBMS_OUTPUT.PUT_LINE('优秀');
ELSIF in_score >= 60 THEN
DBMS_OUTPUT.PUT_LINE('及格');
ELSE
DBMS_OUTPUT.PUT_LINE('不及格');
END IF;
END;
/
六、循环:批量处理数据
循环是存储过程最强大的功能之一。来看一个批量插入数据的例子:
in_count IN INT
)
AS
i INT;
BEGIN
i := 1;
WHILE i <= in_count LOOP
INSERT INTO test_tab VALUES(i, '用户_' || i);
i := i + 1;
END LOOP;
COMMIT;
DBMS_OUTPUT.PUT_LINE('成功插入 ' || in_count || ' 条数据');
END;
/
达梦还支持FOR循环,写法更简洁:
sql
CREATE OR REPLACE PROCEDURE batch_insert_for (
in_count IN INT
)
AS
BEGIN
FOR i IN 1..in_count LOOP
INSERT INTO test_tab VALUES(i, '用户_' || i);
END LOOP;
COMMIT;
END;
/
七、游标:逐行处理查询结果
当需要逐行处理查询结果时,就要用到游标(Cursor) 了。
AS
-- 声明游标
CURSOR emp_cursor IS
SELECT employee_id, salary FROM employees WHERE status = 'ACTIVE';
-- 声明变量接收游标数据
v_id employees.employee_id%TYPE;
v_sal employees.salary%TYPE;
BEGIN
-- 打开游标
OPEN emp_cursor;
-- 循环取数据
LOOP
FETCH emp_cursor INTO v_id, v_sal;
EXIT WHEN emp_cursor%NOTFOUND; -- 没有数据了就退出
-- 对每一行做处理
IF v_sal < 5000 THEN
UPDATE employees SET salary = salary * 1.1
WHERE employee_id = v_id;
END IF;
END LOOP;
-- 关闭游标
CLOSE emp_cursor;
COMMIT;
END;
/
小窍门:游标用完后一定要CLOSE,否则会占用数据库资源。
八、异常处理:让程序更健壮
存储过程里可能会遇到各种错误——查不到数据、主键冲突、除零错误等等。用EXCEPTION块可以优雅地处理这些情况:
in_id IN INT
)
AS
BEGIN
DELETE FROM employees WHERE employee_id = in_id;
IF SQL%ROWCOUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE('没有找到要删除的员工');
ELSE
DBMS_OUTPUT.PUT_LINE('删除成功');
END IF;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('出错了:' || SQLERRM);
ROLLBACK;
END;
/
九、实战:一个完整的综合示例
最后,我们来写一个稍微完整点的存储过程——批量处理员工调薪:
in_dept_id IN VARCHAR2, -- 部门ID
in_raise_pct IN NUMBER, -- 涨薪比例
out_success_count OUT INT, -- 成功人数
out_error_msg OUT VARCHAR2 -- 错误信息
)
AS
-- 声明游标:找出该部门所有在职员工
CURSOR emp_cursor IS
SELECT employee_id, salary
FROM employees
WHERE dept_id = in_dept_id AND status = 'ACTIVE';
v_id employees.employee_id%TYPE;
v_sal employees.salary%TYPE;
v_count INT := 0;
BEGIN
out_success_count := 0;
out_error_msg := '';
OPEN emp_cursor;
LOOP
FETCH emp_cursor INTO v_id, v_sal;
EXIT WHEN emp_cursor%NOTFOUND;
-- 只给薪水低于平均水平的员工涨薪
IF v_sal < 10000 THEN
UPDATE employees
SET salary = salary * (1 + in_raise_pct)
WHERE employee_id = v_id;
v_count := v_count + 1;
END IF;
END LOOP;
CLOSE emp_cursor;
COMMIT;
out_success_count := v_count;
DBMS_OUTPUT.PUT_LINE('部门 ' || in_dept_id ||
' 共有 ' || v_count || ' 名员工涨薪');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
out_error_msg := SQLERRM;
DBMS_OUTPUT.PUT_LINE('处理失败:' || SQLERRM);
END;
/
十、几点建议
1.先在测试环境练手:存储过程里如果有DELETE或UPDATE,不小心写错了可能酿成大祸。
2.善用COMMIT和ROLLBACK:批量操作时,要么全部成功,要么全部回滚。
3.加异常处理:新手最容易忽略的就是EXCEPTION块,但这是让程序健壮的关键。
4.变量名加前缀:比如输入参数用in_,输出参数用out_,局部变量用v_,这样一眼就能看出变量的作用。
5.多用%TYPE和%ROWTYPE:让变量类型跟着表走,表结构变了也不怕。
文章
阅读量
获赞
