好的,我们来系统地讲解 SQL 游标(Cursor),包括概念、使用场景、声明方式以及内部循环使用的完整示例。这里以 SQL Server 为例,也会兼顾 MySQL 和 Oracle 的差异说明。


1️⃣ 游标概念

SQL 是 集合操作语言,擅长批量操作,但有些情况下需要 逐行处理数据
这时就需要 游标 (Cursor)

  • 游标 是一种指针,用于遍历查询结果集的每一行。
  • 适用场景:
    • 逐行处理复杂逻辑
    • 对结果集的每一行进行计算或条件判断
    • 在存储过程或触发器中按行操作数据

⚠️ 游标会占用资源,性能低于批量操作,所以非必要尽量用集合操作替代。


2️⃣ 游标的生命周期

游标的使用通常包括 四个步骤

  1. 声明 (DECLARE):定义游标和要遍历的查询。
  2. 打开 (OPEN):执行查询并准备游标。
  3. 取值 (FETCH):逐行获取游标中的数据。
  4. 关闭 (CLOSE) 并释放 (DEALLOCATE):释放资源。

3️⃣ SQL Server 游标示例

-- 示例表
CREATE TABLE Employees (
    EmployeeID INT PRIMARY KEY,
    Name NVARCHAR(50),
    Salary DECIMAL(10,2)
);

INSERT INTO Employees VALUES
(1, 'Alice', 5000),
(2, 'Bob', 6000),
(3, 'Charlie', 5500);

-- 1. 声明游标
DECLARE EmployeeCursor CURSOR FOR
SELECT EmployeeID, Name, Salary
FROM Employees
WHERE Salary < 6000;

-- 2. 打开游标
OPEN EmployeeCursor;

-- 3. 声明变量接收每行数据
DECLARE @EmployeeID INT;
DECLARE @Name NVARCHAR(50);
DECLARE @Salary DECIMAL(10,2);

-- 4. 取值循环
FETCH NEXT FROM EmployeeCursor INTO @EmployeeID, @Name, @Salary;

WHILE @@FETCH_STATUS = 0
BEGIN
    -- 示例处理:加薪 10%
    UPDATE Employees
    SET Salary = Salary * 1.1
    WHERE EmployeeID = @EmployeeID;

    PRINT 'Updated ' + @Name + ' salary to ' + CAST(@Salary * 1.1 AS NVARCHAR(20));

    -- 获取下一行
    FETCH NEXT FROM EmployeeCursor INTO @EmployeeID, @Name, @Salary;
END

-- 5. 关闭并释放游标
CLOSE EmployeeCursor;
DEALLOCATE EmployeeCursor;

-- 查看更新结果
SELECT * FROM Employees;


4️⃣ MySQL 游标示例

MySQL 游标通常在 存储过程中使用:

DELIMITER $$

CREATE PROCEDURE UpdateSalaries()
BEGIN
    DECLARE done INT DEFAULT FALSE;
    DECLARE emp_id INT;
    DECLARE emp_name VARCHAR(50);
    DECLARE emp_salary DECIMAL(10,2);
    
    DECLARE cur CURSOR FOR
        SELECT EmployeeID, Name, Salary FROM Employees WHERE Salary < 6000;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    OPEN cur;
    
    read_loop: LOOP
        FETCH cur INTO emp_id, emp_name, emp_salary;
        IF done THEN
            LEAVE read_loop;
        END IF;
        
        -- 示例处理:加薪 10%
        UPDATE Employees
        SET Salary = Salary * 1.1
        WHERE EmployeeID = emp_id;
    END LOOP;
    
    CLOSE cur;
END$$

DELIMITER ;

-- 调用存储过程
CALL UpdateSalaries();

-- 查看结果
SELECT * FROM Employees;


5️⃣ 小结

  • 游标适合逐行处理而不是批量操作。
  • SQL Server 和 MySQL 在语法上略有差异,主要区别:
    • SQL Server 使用 WHILE @@FETCH_STATUS = 0 进行循环
    • MySQL 使用 LOOP + CONTINUE HANDLER 控制循环结束
  • 使用完游标必须 关闭并释放,否则会占用资源。