Cursor 游標 (SQL Cursor)
Cursor(游標)讓你可以逐筆處理查詢結果,而不是一次取得所有資料。當需要對每一筆資料執行不同的邏輯處理時,游標就派上用場了。
游標的使用步驟
- DECLARE:宣告游標並定義查詢
- OPEN:開啟游標,執行查詢
- FETCH:逐筆取得資料
- CLOSE:關閉游標
- DEALLOCATE:釋放游標資源(部分資料庫需要)
SQL Server 語法
-- 宣告變數存放每筆資料
DECLARE @id INT, @name VARCHAR(50), @price DECIMAL(10,2);
-- 宣告游標
DECLARE product_cursor CURSOR FOR
SELECT id, name, price FROM products WHERE price > 100;
-- 開啟游標
OPEN product_cursor;
-- 取得第一筆資料
FETCH NEXT FROM product_cursor INTO @id, @name, @price;
-- 迴圈處理每筆資料
WHILE @@FETCH_STATUS = 0
BEGIN
-- 處理邏輯
PRINT 'Product: ' + @name + ', Price: ' + CAST(@price AS VARCHAR);
-- 取得下一筆
FETCH NEXT FROM product_cursor INTO @id, @name, @price;
END
-- 關閉游標
CLOSE product_cursor;
-- 釋放游標
DEALLOCATE product_cursor;
@@FETCH_STATUS 的值:
- 0:成功取得資料
- -1:已無資料
- -2:資料已被刪除
MySQL 語法
MySQL 的游標只能在預存程序或函數中使用:
DELIMITER //
CREATE PROCEDURE process_products()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_id INT;
DECLARE v_name VARCHAR(50);
DECLARE v_price DECIMAL(10,2);
-- 宣告游標
DECLARE product_cursor CURSOR FOR
SELECT id, name, price FROM products WHERE price > 100;
-- 宣告 handler 處理游標結束
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- 開啟游標
OPEN product_cursor;
-- 迴圈讀取
read_loop: LOOP
FETCH product_cursor INTO v_id, v_name, v_price;
IF done THEN
LEAVE read_loop;
END IF;
-- 處理邏輯
SELECT CONCAT('Product: ', v_name, ', Price: ', v_price);
END LOOP;
-- 關閉游標
CLOSE product_cursor;
END//
DELIMITER ;
-- 執行
CALL process_products();
PostgreSQL 語法
DO $$
DECLARE
product_cursor CURSOR FOR SELECT id, name, price FROM products WHERE price > 100;
v_id INT;
v_name VARCHAR(50);
v_price DECIMAL(10,2);
BEGIN
OPEN product_cursor;
LOOP
FETCH product_cursor INTO v_id, v_name, v_price;
EXIT WHEN NOT FOUND;
-- 處理邏輯
RAISE NOTICE 'Product: %, Price: %', v_name, v_price;
END LOOP;
CLOSE product_cursor;
END $$;
Oracle 語法
DECLARE
CURSOR product_cursor IS
SELECT id, name, price FROM products WHERE price > 100;
v_id products.id%TYPE;
v_name products.name%TYPE;
v_price products.price%TYPE;
BEGIN
OPEN product_cursor;
LOOP
FETCH product_cursor INTO v_id, v_name, v_price;
EXIT WHEN product_cursor%NOTFOUND;
-- 處理邏輯
DBMS_OUTPUT.PUT_LINE('Product: ' || v_name || ', Price: ' || v_price);
END LOOP;
CLOSE product_cursor;
END;
Oracle 也支援更簡潔的 FOR 迴圈語法:
BEGIN
FOR rec IN (SELECT id, name, price FROM products WHERE price > 100)
LOOP
DBMS_OUTPUT.PUT_LINE('Product: ' || rec.name || ', Price: ' || rec.price);
END LOOP;
END;
游標的缺點與替代方案
游標有以下缺點:
- 效能較差:逐筆處理比集合操作慢很多
- 消耗資源:長時間開啟會占用記憶體和鎖定
- 程式碼複雜:相比單純的 SQL 語句更難維護
大多數情況下,應該優先考慮使用集合式操作(set-based operations),例如:
只有在邏輯複雜到無法用集合式操作完成時,才考慮使用游標。