存储过程是数据库中的一组预编译的SQL语句,可以用来完成特定的功能。

特征

  • 预编译:存储过程在创建时被编译,执行时效率较高。
  • 参数支持:可以接受输入参数和返回输出参数。
  • 控制结构:支持条件语句和循环。
  • 提高安全性:通过存储过程而不是直接访问表,提高了数据的安全性。

 各种数据库的CRUD存储过程

1.1 MySQL

增加员工(INSERT)

DELIMITER //

CREATE PROCEDURE AddEmployee(IN firstName VARCHAR(50), IN lastName VARCHAR(50), IN position VARCHAR(50), IN salary DECIMAL(10,2))
BEGIN
    INSERT INTO Employees (FirstName, LastName, Position, Salary) 
    VALUES (firstName, lastName, position, salary);
END //

DELIMITER ;

修改员工信息(UPDATE)

DELIMITER //

CREATE PROCEDURE UpdateEmployee(IN emp_id INT, IN salary DECIMAL(10,2))
BEGIN
    UPDATE Employees 
    SET Salary = salary 
    WHERE EmployeeID = emp_id;
END //

DELIMITER ;

删除员工(DELETE)

DELIMITER //

CREATE PROCEDURE DeleteEmployee(IN emp_id INT)
BEGIN
    DELETE FROM Employees 
    WHERE EmployeeID = emp_id;
END //

DELIMITER ;

查询员工(SELECT)

DELIMITER //

CREATE PROCEDURE GetEmployeeByID(IN emp_id INT)
BEGIN
    SELECT EmployeeID, FirstName, LastName, Position, Salary 
    FROM Employees 
    WHERE EmployeeID = emp_id;
END //

DELIMITER ;

分页查询员工

DELIMITER //

CREATE PROCEDURE GetEmployeesPaginated(IN pageNum INT, IN pageSize INT)
BEGIN
    SET @offset = (pageNum - 1) * pageSize;
    PREPARE stmt FROM 'SELECT * FROM Employees LIMIT ? OFFSET ?';
    SET @pageSize = pageSize;
    SET @offsetValue = @offset;
    EXECUTE stmt USING @pageSize, @offsetValue;
    DEALLOCATE PREPARE stmt;
END //

DELIMITER ;

1.2 PostgreSQL

增加员工(INSERT)

CREATE OR REPLACE FUNCTION AddEmployee(first_name TEXT, last_name TEXT, position TEXT, salary NUMERIC) 
RETURNS VOID AS $$
BEGIN
    INSERT INTO Employees (FirstName, LastName, Position, Salary) 
    VALUES (first_name, last_name, position, salary);
END;
$$ LANGUAGE plpgsql;

修改员工信息(UPDATE)

CREATE OR REPLACE FUNCTION UpdateEmployee(emp_id INT, salary NUMERIC) 
RETURNS VOID AS $$
BEGIN
    UPDATE Employees 
    SET Salary = salary 
    WHERE EmployeeID = emp_id;
END;
$$ LANGUAGE plpgsql;

删除员工(DELETE)

CREATE OR REPLACE FUNCTION DeleteEmployee(emp_id INT) 
RETURNS VOID AS $$
BEGIN
    DELETE FROM Employees 
    WHERE EmployeeID = emp_id;
END;
$$ LANGUAGE plpgsql;

查询员工(SELECT)

CREATE OR REPLACE FUNCTION GetEmployeeByID(emp_id INT) 
RETURNS TABLE(EmployeeID INT, FirstName TEXT, LastName TEXT, Position TEXT, Salary NUMERIC) AS $$
BEGIN
    RETURN QUERY
    SELECT EmployeeID, FirstName, LastName, Position, Salary 
    FROM Employees 
    WHERE EmployeeID = emp_id;
END;
$$ LANGUAGE plpgsql;

分页查询员工

CREATE OR REPLACE FUNCTION GetEmployeesPaginated(page_num INT, page_size INT) 
RETURNS TABLE(EmployeeID INT, FirstName TEXT, LastName TEXT, Position TEXT, Salary NUMERIC) AS $$
BEGIN
    RETURN QUERY 
    SELECT * FROM Employees 
    ORDER BY EmployeeID 
    LIMIT page_size OFFSET (page_num - 1) * page_size;
END;
$$ LANGUAGE plpgsql;

1.3 达梦

增加员工(INSERT)

CREATE PROCEDURE AddEmployee(IN firstName VARCHAR(50), IN lastName VARCHAR(50), IN position VARCHAR(50), IN salary DECIMAL(10,2))
BEGIN
    INSERT INTO Employees (FirstName, LastName, Position, Salary) 
    VALUES (firstName, lastName, position, salary);
END;

修改员工信息(UPDATE)

CREATE PROCEDURE UpdateEmployee(IN emp_id INT, IN salary DECIMAL(10,2))
BEGIN
    UPDATE Employees 
    SET Salary = salary 
    WHERE EmployeeID = emp_id;
END;

删除员工(DELETE)

CREATE PROCEDURE DeleteEmployee(IN emp_id INT)
BEGIN
    DELETE FROM Employees 
    WHERE EmployeeID = emp_id;
END;

查询员工(SELECT)

CREATE PROCEDURE GetEmployeeByID(IN emp_id INT)
BEGIN
    SELECT EmployeeID, FirstName, LastName, Position, Salary 
    FROM Employees 
    WHERE EmployeeID = emp_id;
END;

分页查询员工

CREATE PROCEDURE GetEmployeesPaginated(IN page_num INT, IN page_size INT)
BEGIN
    DECLARE offset INT;
    SET offset = (page_num - 1) * page_size;

    SELECT * FROM Employees 
    LIMIT page_size OFFSET offset;
END;

1.4 Oracle

增加员工(INSERT)

CREATE OR REPLACE PROCEDURE AddEmployee(first_name IN VARCHAR2, last_name IN VARCHAR2, position IN VARCHAR2, salary IN NUMBER) AS
BEGIN
    INSERT INTO Employees (FirstName, LastName, Position, Salary) 
    VALUES (first_name, last_name, position, salary);
END;

修改员工信息(UPDATE)

CREATE OR REPLACE PROCEDURE UpdateEmployee(emp_id IN NUMBER, salary IN NUMBER) AS
BEGIN
    UPDATE Employees 
    SET Salary = salary 
    WHERE EmployeeID = emp_id;
END;

删除员工(DELETE)

CREATE OR REPLACE PROCEDURE DeleteEmployee(emp_id IN NUMBER) AS
BEGIN
    DELETE FROM Employees 
    WHERE EmployeeID = emp_id;
END;

查询员工(SELECT)

CREATE OR REPLACE PROCEDURE GetEmployeeByID(emp_id IN NUMBER) AS
BEGIN
    FOR rec IN (SELECT EmployeeID, FirstName, LastName, Position, Salary 
                FROM Employees 
                WHERE EmployeeID = emp_id) LOOP
        DBMS_OUTPUT.PUT_LINE('ID: ' || rec.EmployeeID || ', Name: ' || rec.FirstName || ' ' || rec.LastName);
    END LOOP;
END;

分页查询员工

CREATE OR REPLACE PROCEDURE GetEmployeesPaginated(page_num IN NUMBER, page_size IN NUMBER) AS
BEGIN
    FOR rec IN (SELECT * FROM (
                    SELECT EmployeeID, FirstName, LastName, Position, Salary, 
                    ROW_NUMBER() OVER (ORDER BY EmployeeID) AS rn 
                    FROM Employees)
                 WHERE rn BETWEEN (page_num - 1) * page_size + 1 AND page_num * page_size) LOOP
        DBMS_OUTPUT.PUT_LINE('ID: ' || rec.EmployeeID || ', Name: ' || rec.FirstName || ' ' || rec.LastName);
    END LOOP;
END;

存储过程中的错误处理

MySQL 错误处理

DELIMITER //

CREATE PROCEDURE SafeGetEmployeeByID(IN emp_id INT)
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        -- 错误处理逻辑
        SELECT '发生错误';
    END;

    SELECT EmployeeID, FirstName, LastName 
    FROM Employees 
    WHERE EmployeeID = emp_id;
END //

DELIMITER ;

PostgreSQL 错误处理

CREATE OR REPLACE FUNCTION SafeGetEmployeeByID(emp_id INT) 
RETURNS VOID AS $$
BEGIN
    -- 进行查询
    PERFORM * FROM Employees WHERE EmployeeID = emp_id;
EXCEPTION
    WHEN OTHERS THEN
        RAISE NOTICE '发生错误: %', SQLERRM;
END;
$$ LANGUAGE plpgsql;

达梦和 Oracle 错误处理

在达梦和 Oracle 中可以使用 EXCEPTION 来处理错误。

达梦示例

CREATE PROCEDURE SafeGetEmployeeByID(IN emp_id INT)
BEGIN
    BEGIN
        SELECT FirstName INTO emp_FirstName FROM Employees WHERE EmployeeID = emp_id;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            SELECT '未找到员工';
    END;
END;

Oracle 示例

CREATE OR REPLACE PROCEDURE SafeGetEmployeeByID(emp_id IN NUMBER) AS
BEGIN
    -- 进行查询
    SELECT FirstName INTO emp_FirstName FROM Employees WHERE EmployeeID = emp_id;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('未找到员工');
END;

扩展存储过程调用后端接口示例:

假设我们有一个 Orders 表,记录订单信息。我们希望在更新订单状态时,触发一个存储过程,发送通知给用户。

创建触发器和存储过程

开启组件(2012以上版本支持)

sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
sp_configure 'Ole Automation Procedures', 1;
GO
RECONFIGURE;
GO
EXEC sp_configure 'Ole Automation Procedures';
GO

注意事项:

  • 安全性:开启 OLE Automation Procedures 可能会增加 SQL Server 的安全风险,因为恶意代码可能会利用这种能力,因此在生产环境中使用时需要谨慎,并确保相关权限的管理。
  • 性能影响:使用 OLE Automation Procedures 可能会影响数据库性能,特别是当它们被频繁调用时。

1. 创建 Orders 表

CREATE TABLE [dbo].[Orders] (
    OrderID BIGINT PRIMARY KEY,
    Status NVARCHAR(50),
    UserEmail NVARCHAR(255)
);

2. 创建存储过程

这是负责发送通知的存储过程:

USE [HyperFCOBS]
GO

ALTER PROCEDURE [dbo].[proc_SendNotification]
    @orderId BIGINT,
    @userEmail NVARCHAR(255)
AS
BEGIN
    DECLARE @apiUrl NVARCHAR(300);
    DECLARE @object INT;
    DECLARE @responseText NVARCHAR(4000);
    DECLARE @hr INT;
    DECLARE @status NVARCHAR(10);
    DECLARE @source VARCHAR(255), @description VARCHAR(255);

    -- 构建 API URL
    SET @apiUrl = CONCAT('http://example.com/api/sendNotification?orderId=', @orderId, '&email=', @userEmail);

    BEGIN TRY
        -- 创建 Server XMLHTTP 对象实例
        EXEC @hr = sp_OACreate 'MSXML2.ServerXMLHTTP', @object OUT;
        IF @hr <> 0
        BEGIN
            EXEC sp_OAGetErrorInfo @object, NULL, @source OUT, @description OUT;
            RAISERROR('创建对象时出错。 %s %s', 16, 1, @source, @description);
            RETURN;
        END

        -- 设置超时
        EXEC sp_OAMethod @object, 'setTimeouts', NULL, 5000, 5000, 5000, 100;

        -- 打开 HTTP 请求
        EXEC sp_OAMethod @object, 'open', NULL, 'GET', @apiUrl, 'false';

        -- 发送请求
        EXEC sp_OAMethod @object, 'send';

        -- 检查请求状态
        EXEC @hr = sp_OAGetProperty @object, 'status', @status OUTPUT;
        IF @hr <> 0 OR @status <> '200'
        BEGIN
            EXEC sp_OAGetErrorInfo @object, NULL, @source OUT, @description OUT;
            RAISERROR('请求失败。状态码: %s, 错误信息: %s %s', 16, 1, @status, @source, @description);
            RETURN;
        END

        -- 获取响应文本
        EXEC @hr = sp_OAMethod @object, 'responseText', @responseText OUTPUT;
        IF @hr <> 0
        BEGIN
            EXEC sp_OAGetErrorInfo @object, NULL, @source OUT, @description OUT;
            RAISERROR('获取响应时出错。 %s %s', 16, 1, @source, @description);
            RETURN;
        END

        -- 输出响应文本
        PRINT @responseText;

    END TRY
    BEGIN CATCH
        -- 处理错误
        DECLARE @errorMessage NVARCHAR(4000) = ERROR_MESSAGE();
        RAISERROR('存储过程执行期间发生错误: %s', 16, 1, @errorMessage);
    END CATCH
    FINALLY
        -- 清理并销毁对象
        IF @object IS NOT NULL
            EXEC sp_OADestroy @object;
END

3. 创建触发器

触发器将在订单状态更新时调用存储过程:

USE [HyperFCOBS]
GO

ALTER TRIGGER [dbo].[trg_OrderStatusUpdate]
ON [dbo].[Orders]
AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON; -- 避免多余的消息

    -- 检查是否有任何行的Status被更新为‘Shipped’
    IF EXISTS (
        SELECT 1
        FROM inserted i
        WHERE i.Status = 'Shipped'
    )
    BEGIN
        -- 调用存储过程
        DECLARE @orderId BIGINT;
        DECLARE @userEmail NVARCHAR(255);
        
        -- 使用临时表存储符合条件的结果
        DECLARE @OrdersToNotify TABLE (OrderID BIGINT, UserEmail NVARCHAR(255));

        INSERT INTO @OrdersToNotify (OrderID, UserEmail)
        SELECT i.OrderID, i.UserEmail
        FROM inserted i
        INNER JOIN deleted d ON i.OrderID = d.OrderID
        WHERE i.Status = 'Shipped' AND d.Status <> 'Shipped';

        -- 遍历临时表并调用存储过程
        DECLARE order_cursor CURSOR FOR
            SELECT OrderID, UserEmail FROM @OrdersToNotify;

        OPEN order_cursor;

        FETCH NEXT FROM order_cursor INTO @orderId, @userEmail;

        WHILE @@FETCH_STATUS = 0
        BEGIN
            -- 调用存储过程
            EXEC [dbo].[proc_SendNotification] @orderId, @userEmail;
            FETCH NEXT FROM order_cursor INTO @orderId, @userEmail;
        END

        CLOSE order_cursor;
        DEALLOCATE order_cursor;
    END
END

使用场景

  1. 更新订单状态:

假设你想要更改某个订单的状态,可以执行以下 SQL:

UPDATE dbo.Orders
SET Status = 'Shipped'
WHERE OrderID = 101; -- 替换为相关订单ID

网上也有一些存储过程调用接口的文章,可以进行参考,请自行百度