如果要对 SELECT 语句返回的结果进行逐行处理,则需使用游标(集)

  • 游标集是系统为用户开设的一个数据缓冲区,用于存放 SQLSQL 语句的执行结果(即元组的集合)
  • 游标是指向游标集中某个元组记录的指针
    游标的使用需要经过以下五个步骤:
  1. 定义: DECLARE
  2. 打开: OPEN
  3. 逐行提取: FETCH
  4. 关闭: CLOSE
  5. 释放: DEALLOCATE

# 定义

SQL
1
2
3
4
5
6
7
8
9
DECLARE 游标名 CURSOR
FOR 游标要实现的功能程序
[
FOR {
READ ONLY -- 仅查询
| UPDATE -- 可更新
[OF 列名列表] -- 如果有这个, 表示只能针对指定列更新
}
]

# 打开

SQL
1
OPEN 游标名

# 获取当前游标值

SQL
1
FETCH 游标名 INTO @变量名

# 关闭与释放

SQL
1
2
3
4
5
-- 关闭游标
CLOSE 游标名

-- 释放游标
DEALLOCATE 游标名

# 修改与删除

SQL
1
2
3
4
5
6
7
8
-- 删除游标集中的当前行
DELETE FROM 表名
WHERE CURRENT OF 游标名

-- 修改游标集中的当前行
UPDATE 表名
SET 列名1 =1, ...
WHERE CURRENT OF 游标名

# 游标用法模板

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DECLARE @变量1, ...
DECLARE 游标名 CURSOR FOR
SELECT ....

OPEN 游标名
FETCH 游标名 INTO @变量名1, ...

WHILE (@@FETCH_STATUS=0)
BEGIN
一些操作
FETCH 游标名 INTO @变量名1, ...
END

CLOSE 游标名
DEALLOCATE 游标名

# 游标用法示例

SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
-- 创建示例表
CREATE TABLE Orders (
OrderID INT PRIMARY KEY,
CustomerID INT,
OrderDate DATETIME,
Status VARCHAR(20) DEFAULT 'Pending',
TotalAmount DECIMAL(10, 2) DEFAULT 0
);

CREATE TABLE OrderItems (
ItemID INT PRIMARY KEY,
OrderID INT,
ProductID INT,
Quantity INT,
UnitPrice DECIMAL(10, 2),
FOREIGN KEY (OrderID) REFERENCES Orders(OrderID)
);

CREATE TABLE OrderProcessingLog (
LogID INT IDENTITY(1,1) PRIMARY KEY,
OrderID INT,
ProcessDate DATETIME,
Action VARCHAR(100),
OldStatus VARCHAR(20),
NewStatus VARCHAR(20)
);

-- 插入示例数据
INSERT INTO Orders (OrderID, CustomerID, OrderDate) VALUES
(1, 101, '2023-01-01'),
(2, 102, '2023-01-02'),
(3, 103, '2023-01-03');

INSERT INTO OrderItems VALUES
(1, 1, 1001, 2, 25.50),
(2, 1, 1002, 1, 120.00),
(3, 2, 1003, 3, 15.75),
(4, 2, 1004, 1, 200.00),
(5, 3, 1005, 5, 10.25),
(6, 3, 1006, 2, 45.00);

-- 声明变量
DECLARE @OrderID INT;
DECLARE @CustomerID INT;
DECLARE @OrderDate DATETIME;
DECLARE @OldStatus VARCHAR(20);
DECLARE @TotalAmount DECIMAL(10, 2);
DECLARE @ProcessedCount INT = 0;
DECLARE @ErrorCount INT = 0;

-- 声明游标
DECLARE OrderCursor CURSOR FOR
SELECT OrderID, CustomerID, OrderDate, Status
FROM Orders
WHERE Status = 'Pending'
ORDER BY OrderDate;

-- 打开游标
OPEN OrderCursor;

-- 开始事务
BEGIN TRANSACTION;

-- 获取第一行数据
FETCH NEXT FROM OrderCursor INTO @OrderID, @CustomerID, @OrderDate, @OldStatus;

-- 循环处理每一行
WHILE @@FETCH_STATUS = 0
BEGIN
BEGIN TRY
-- 计算订单总金额
SELECT @TotalAmount = SUM(Quantity * UnitPrice)
FROM OrderItems
WHERE OrderID = @OrderID;

-- 更新订单状态和总金额
UPDATE Orders
SET Status = 'Processed',
TotalAmount = @TotalAmount
WHERE OrderID = @OrderID;

-- 记录处理日志
INSERT INTO OrderProcessingLog (OrderID, ProcessDate, Action, OldStatus, NewStatus)
VALUES (@OrderID, GETDATE(), 'Order processed', @OldStatus, 'Processed');

-- 增加处理计数
SET @ProcessedCount = @ProcessedCount + 1;

-- 模拟复杂逻辑:VIP客户特殊处理
IF @CustomerID % 10 = 0 -- 假设VIP客户的CustomerID能被10整除
BEGIN
-- 记录VIP处理日志
INSERT INTO OrderProcessingLog (OrderID, ProcessDate, Action, OldStatus, NewStatus)
VALUES (@OrderID, GETDATE(), 'VIP customer processed', 'Processed', 'Processed');

-- 这里可以添加VIP客户的特殊处理逻辑
PRINT 'VIP customer order processed: OrderID = ' + CAST(@OrderID AS VARCHAR);
END

PRINT 'Order processed successfully: OrderID = ' + CAST(@OrderID AS VARCHAR);
END TRY
BEGIN CATCH
-- 错误处理
PRINT 'Error processing order: ' + CAST(@OrderID AS VARCHAR) + '. Error: ' + ERROR_MESSAGE();

-- 记录错误日志
INSERT INTO OrderProcessingLog (OrderID, ProcessDate, Action, OldStatus, NewStatus)
VALUES (@OrderID, GETDATE(), 'Error: ' + ERROR_MESSAGE(), @OldStatus, 'Error');

-- 更新订单状态为错误
UPDATE Orders
SET Status = 'Error'
WHERE OrderID = @OrderID;

-- 增加错误计数
SET @ErrorCount = @ErrorCount + 1;
END CATCH

-- 获取下一行数据
FETCH NEXT FROM OrderCursor INTO @OrderID, @CustomerID, @OrderDate, @OldStatus;
END

-- 提交事务
COMMIT TRANSACTION;

-- 关闭游标
CLOSE OrderCursor;

-- 释放游标
DEALLOCATE OrderCursor;

-- 输出处理结果
PRINT 'Processing completed.';
PRINT 'Orders processed successfully: ' + CAST(@ProcessedCount AS VARCHAR);
PRINT 'Orders with errors: ' + CAST(@ErrorCount AS VARCHAR);

-- 查询处理后的结果
SELECT * FROM Orders;
SELECT * FROM OrderProcessingLog ORDER BY ProcessDate DESC;