注册
达梦数据库存储过程从入门到实战
培训园地/ 文章详情 /

达梦数据库存储过程从入门到实战

AK47 2026/06/30 446 0 0

一、存储过程的基本结构

    参数名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:让变量类型跟着表走,表结构变了也不怕。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服