404 Not Found

404 Not Found


nginx

存储过程概念与SQL函数

存储过程概念与SQL函数

生活类比

想象你去银行办理业务:

核心概念

什么是存储过程

存储过程(Stored Procedure)是预编译并存储在数据库中的SQL语句集合,可以:

💡 SQLite 不支持存储过程,以下示例展示概念和 MySQL/PostgreSQL 语法。SQLite 用户可以使用自定义函数替代。

MySQL 存储过程语法

SQL
-- MySQL 存储过程基本结构
DELIMITER //
CREATE PROCEDURE procedure_name (参数列表)
BEGIN
    -- 变量声明
    -- 业务逻辑
    -- 返回结果
END //
DELIMITER ;

PostgreSQL 存储过程语法

SQL
-- PostgreSQL 存储过程基本结构
CREATE OR REPLACE FUNCTION procedure_name(参数列表)
RETURNS 返回类型 AS $$
DECLARE
    -- 变量声明
BEGIN
    -- 业务逻辑
    -- RETURN 语句
END;
$$ LANGUAGE plpgsql;

SQLite 自定义函数

SQLite 不支持存储过程,但支持通过编程语言创建用户自定义函数:

SQL
-- SQLite 本身不支持 CREATE FUNCTION
-- 需要通过 Python、C 等编程语言注册自定义函数

-- Python 示例(使用 sqlite3 模块)
import sqlite3

def calculate_bonus(salary, rate):
    return salary * rate

conn = sqlite3.connect('company.db')
conn.create_function('calculate_bonus', 2, calculate_bonus)

-- 注册后可以在 SQL 中使用
SELECT name, salary, calculate_bonus(salary, 0.1) AS bonus
FROM employees;

流程控制语句

IF/ELSE 条件判断

SQL
-- MySQL 示例
DELIMITER //
CREATE PROCEDURE check_salary(IN emp_id INT)
BEGIN
    DECLARE emp_salary DECIMAL(10,2);
    
    SELECT salary INTO emp_salary
    FROM employees WHERE id = emp_id;
    
    IF emp_salary > 10000 THEN
        SELECT '高薪员工' AS status;
    ELSEIF emp_salary > 5000 THEN
        SELECT '中等薪资' AS status;
    ELSE
        SELECT '低薪员工' AS status;
    END IF;
END //
DELIMITER ;

WHILE 循环

SQL
-- MySQL 示例:计算1到N的和
DELIMITER //
CREATE PROCEDURE calculate_sum(IN n INT, OUT total INT)
BEGIN
    DECLARE i INT DEFAULT 1;
    SET total = 0;
    
    WHILE i <= n DO
        SET total = total + i;
        SET i = i + 1;
    END WHILE;
END //
DELIMITER ;

LOOP 循环

SQL
-- MySQL 示例:LOOP 与 LEAVE
DELIMITER //
CREATE PROCEDURE process_employees()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE emp_name VARCHAR(100);
    
    DECLARE cur CURSOR FOR SELECT name FROM employees;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    OPEN cur;
    
    read_loop: LOOP
        FETCH cur INTO emp_name;
        IF done THEN
            LEAVE read_loop;
        END IF;
        -- 处理每条记录
        SELECT CONCAT('Processing: ', emp_name);
    END LOOP;
    
    CLOSE cur;
END //
DELIMITER ;

游标(CURSOR)概念

游标用于逐行处理查询结果集:

SQL
-- 游标的基本使用流程
-- 1. 声明游标
DECLARE cur CURSOR FOR SELECT column FROM table;

-- 2. 打开游标
OPEN cur;

-- 3. 获取数据
FETCH cur INTO variable;

-- 4. 处理数据(通常在循环中)

-- 5. 关闭游标
CLOSE cur;

存储函数(Stored Function)

存储函数与存储过程类似,但有重要区别:

SQL
-- MySQL 存储函数
DELIMITER //
CREATE FUNCTION get_annual_salary(monthly_salary DECIMAL(10,2))
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
    RETURN monthly_salary * 12;
END //
DELIMITER ;

-- 使用函数
SELECT name, get_annual_salary(salary) AS annual_salary
FROM employees;

存储过程 vs 函数的区别

特性 存储过程 存储函数
调用方式 CALL 语句 在 SQL 中直接调用
返回值 可返回多个结果集 必须返回单个值
使用场景 复杂业务逻辑 计算和转换
事务控制 可以管理事务 不能管理事务
SQL 语句中 不能在 SELECT 中使用 可以在 SELECT 中使用

基本语法

创建存储过程(MySQL)

SQL
-- 创建带参数的存储过程
DELIMITER //
CREATE PROCEDURE get_employees_by_dept(
    IN dept_name VARCHAR(50),
    IN min_salary DECIMAL(10,2)
)
BEGIN
    SELECT e.name, e.salary, d.department_name
    FROM employees e
    JOIN departments d ON e.department_id = d.id
    WHERE d.department_name = dept_name
    AND e.salary >= min_salary
    ORDER BY e.salary DESC;
END //
DELIMITER ;

-- 调用存储过程
CALL get_employees_by_dept('技术部', 8000);

创建存储函数(PostgreSQL)

SQL
-- 创建计算奖金的函数
CREATE OR REPLACE FUNCTION calculate_bonus(
    base_salary NUMERIC,
    performance_rating INTEGER
)
RETURNS NUMERIC AS $$
DECLARE
    bonus_rate NUMERIC;
BEGIN
    CASE performance_rating
        WHEN 1 THEN bonus_rate := 0.10;
        WHEN 2 THEN bonus_rate := 0.15;
        WHEN 3 THEN bonus_rate := 0.20;
        WHEN 4 THEN bonus_rate := 0.25;
        WHEN 5 THEN bonus_rate := 0.30;
        ELSE bonus_rate := 0.05;
    END CASE;
    
    RETURN base_salary * bonus_rate;
END;
$$ LANGUAGE plpgsql;

-- 使用函数
SELECT 
    name,
    salary,
    performance_rating,
    calculate_bonus(salary, performance_rating) AS bonus
FROM employees;
💡 提示:存储过程适合封装复杂的业务逻辑,而函数适合可复用的计算。在设计时应根据实际需求选择合适的方式。

示例

示例:创建员工入职处理存储过程

创建一个存储过程,处理新员工入职流程,包括分配工号、设置初始薪资、记录入职信息。

SQL
-- MySQL 版本
DELIMITER //
CREATE PROCEDURE hire_employee(
    IN p_name VARCHAR(100),
    IN p_department_id INT,
    IN p_position VARCHAR(50),
    IN p_salary DECIMAL(10,2),
    OUT p_employee_id INT,
    OUT p_message VARCHAR(200)
)
BEGIN
    DECLARE dept_exists INT DEFAULT 0;
    DECLARE max_id INT DEFAULT 0;
    
    -- 检查部门是否存在
    SELECT COUNT(*) INTO dept_exists
    FROM departments WHERE id = p_department_id;
    
    IF dept_exists = 0 THEN
        SET p_message = '错误:指定的部门不存在';
        SET p_employee_id = -1;
    ELSE
        -- 获取最大工号
        SELECT COALESCE(MAX(id), 0) INTO max_id FROM employees;
        SET p_employee_id = max_id + 1;
        
        -- 插入新员工记录
        INSERT INTO employees (id, name, department_id, position, salary, hire_date)
        VALUES (p_employee_id, p_name, p_department_id, p_position, p_salary, CURDATE());
        
        SET p_message = CONCAT('成功:员工 ', p_name, ' 已入职,工号为 ', p_employee_id);
    END IF;
END //
DELIMITER ;

-- 调用示例
CALL hire_employee('张三', 1, '初级工程师', 8000.00, @new_id, @msg);
SELECT @new_id AS employee_id, @msg AS message;
▶ 试一试

示例:PostgreSQL 版本的员工薪资调整函数

SQL
-- PostgreSQL 版本
CREATE OR REPLACE FUNCTION adjust_salary(
    p_employee_id INTEGER,
    p_adjustment_type VARCHAR(10),  -- 'RAISE' 或 'REDUCE'
    p_percentage NUMERIC
)
RETURNS TABLE(
    employee_name VARCHAR,
    old_salary NUMERIC,
    new_salary NUMERIC,
    adjustment_amount NUMERIC
) AS $$
DECLARE
    v_old_salary NUMERIC;
    v_new_salary NUMERIC;
    v_name VARCHAR;
BEGIN
    -- 获取当前薪资
    SELECT name, salary INTO v_name, v_old_salary
    FROM employees WHERE id = p_employee_id;
    
    IF NOT FOUND THEN
        RAISE EXCEPTION '员工ID % 不存在', p_employee_id;
    END IF;
    
    -- 计算新薪资
    IF p_adjustment_type = 'RAISE' THEN
        v_new_salary := v_old_salary * (1 + p_percentage / 100);
    ELSE
        v_new_salary := v_old_salary * (1 - p_percentage / 100);
    END IF;
    
    -- 更新数据库
    UPDATE employees SET salary = v_new_salary WHERE id = p_employee_id;
    
    -- 返回结果
    employee_name := v_name;
    old_salary := v_old_salary;
    new_salary := v_new_salary;
    adjustment_amount := v_new_salary - v_old_salary;
    
    RETURN NEXT;
END;
$$ LANGUAGE plpgsql;

-- 使用示例
SELECT * FROM adjust_salary(1, 'RAISE', 10);
▶ 试一试

应用场景

场景1:批量数据处理

SQL
-- MySQL:批量更新订单状态
DELIMITER //
CREATE PROCEDURE update_order_status()
BEGIN
    DECLARE affected_rows INT DEFAULT 0;
    
    -- 开始事务
    START TRANSACTION;
    
    -- 更新超过30天未付款的订单为已取消
    UPDATE orders 
    SET status = 'cancelled', 
        updated_at = NOW()
    WHERE status = 'pending' 
    AND created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
    
    SET affected_rows = ROW_COUNT();
    
    -- 记录处理日志
    INSERT INTO process_log (process_name, affected_rows, executed_at)
    VALUES ('update_order_status', affected_rows, NOW());
    
    -- 提交事务
    COMMIT;
    
    SELECT CONCAT('已取消 ', affected_rows, ' 个过期订单') AS result;
END //
DELIMITER ;

场景2:复杂报表生成

SQL
-- PostgreSQL:生成部门薪资统计报表
CREATE OR REPLACE FUNCTION generate_salary_report()
RETURNS TABLE(
    department_name VARCHAR,
    employee_count BIGINT,
    avg_salary NUMERIC,
    min_salary NUMERIC,
    max_salary NUMERIC,
    total_salary NUMERIC
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        d.department_name,
        COUNT(e.id) AS employee_count,
        ROUND(AVG(e.salary), 2) AS avg_salary,
        MIN(e.salary) AS min_salary,
        MAX(e.salary) AS max_salary,
        SUM(e.salary) AS total_salary
    FROM departments d
    LEFT JOIN employees e ON d.id = e.department_id
    GROUP BY d.department_name
    ORDER BY total_salary DESC;
END;
$$ LANGUAGE plpgsql;

-- 使用
SELECT * FROM generate_salary_report();

❓ 常见问题

Q:SQLite 完全不支持存储过程怎么办? A: SQLite 设计为轻量级嵌入式数据库,不支持存储过程。可以使用编程语言(Python、Java等)实现业务逻辑,或使用视图(VIEW)封装复杂查询。

Q:存储过程和视图有什么区别? A: 视图是虚拟表,主要用于简化查询;存储过程是程序,可以包含复杂的业务逻辑、流程控制和数据修改操作。

Q:什么时候应该使用存储过程? A: 当需要封装复杂的业务逻辑、减少网络传输、提高性能、统一数据访问接口时使用。简单的查询使用视图更合适。

Q:存储过程如何调试? A: MySQL 可以使用 SELECT 语句输出中间结果;PostgreSQL 可以使用 RAISE NOTICE 输出调试信息;也可以使用数据库管理工具的调试功能。

📖 小节

本课我们学习了:

📝 作业

  1. 基础练习:创建一个存储过程,接受部门ID作为参数,返回该部门薪资最高的前3名员工信息。

  2. 进阶练习:创建一个函数,根据员工的工作年限(hire_date到当前日期)计算工龄工资补贴:

    • 1-3年:基本工资的5%
    • 3-5年:基本工资的10%
    • 5年以上:基本工资的15%
  3. 思考题:比较在应用层代码和数据库存储过程中处理业务逻辑的优缺点。


下一课→23-triggers-events.md

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏