在 MySQL 中,存儲過程或函數中的查詢有時會返回多條記錄,而使用簡單的 SELECT 語句,沒有辦法得到第一行、下一行或前十行的數據,這時可以使用游標來逐條讀取查詢結果集中的記錄。游標在部分資料中也被稱為光標。
關系數據庫管理系統實質是面向集合的,在 MySQL 中並沒有一種描述表中單一記錄的表達形式,除非使用 WHERE 子句來限制只有一條記錄被選中。所以有時我們必須借助於游標來進行單條記錄的數據處理。
一般通過游標定位到結果集的某一行進行數據修改。
結果集是符合 SQL 語句的所有記錄的集合。
其實游標可以看做是一個標識,用來標識數據取到了什么地方,就像是編程語言中數組中的下標。
MySQL 中的游標只能用於存儲過程和函數。
下面介紹游標的使用,主要包括游標的聲明、打開、使用和關閉。
1. 聲明游標
MySQL 中使用 DECLARE 關鍵字來聲明游標,並定義相應的 SELECT 語句,根據需要添加 WHERE 和其它子句。其語法的基本形式如下:
DECLARE cursor_name CURSOR FOR select_statement;
其中,cursor_name 表示游標的名稱;select_statement 表示 SELECT 語句,可以返回一行或多行數據。
例 1
下面聲明一個名為 nameCursor 的游標,代碼如下:
mysql> DELIMITER // mysql> CREATE PROCEDURE processnames() -> BEGIN -> DECLARE nameCursor CURSOR -> FOR -> SELECT name FROM tb_student_info; -> END// Query OK, 0 rows affected (0.07 sec)
以上語句定義了 nameCursor 游標,游標只局限於存儲過程中,存儲過程處理完成后,游標就消失了。
2. 打開游標
聲明游標之后,要想從游標中提取數據,必須首先打開游標。在 MySQL 中,打開游標通過 OPEN 關鍵字來實現,其語法格式如下:
OPEN cursor_name;
其中,cursor_name 表示所要打開游標的名稱。需要注意的是,打開一個游標時,游標並不指向第一條記錄,而是指向第一條記錄的前邊。
在程序中,一個游標可以打開多次。用戶打開游標后,其他用戶或程序可能正在更新數據表,所以有時會導致用戶每次打開游標后,顯示的結果都不同。
3. 使用游標
游標順利打開后,可以使用 FETCH...INTO 語句來讀取數據,其語法形式如下:
FETCH [[NEXT] FROM] cursor_name INTO var_name [, var_name] ...
上述語句中,將游標 cursor_name 中 SELECT 語句的執行結果保存到變量參數 var_name 中。變量參數 var_name 必須在游標使用之前定義。使用游標類似高級語言中的數組遍歷,當第一次使用游標時,此時游標指向結果集的第一條記錄。
MySQL 的游標是只讀的,也就是說,你只能順序地從開始往后讀取結果集,不能從后往前,也不能直接跳到中間的記錄。
4. 關閉游標
游標使用完畢后,要及時關閉,在 MySQL 中,使用 CLOSE 關鍵字關閉游標,其語法格式如下:
CLOSE cursor_name;
CLOSE 釋放游標使用的所有內部內存和資源,因此每個游標不再需要時都應該關閉。
在一個游標關閉后,如果沒有重新打開,則不能使用它。但是,使用聲明過的游標不需要再次聲明,用 OPEN 語句打開它就可以了。
如果你不明確關閉游標,MySQL 將會在到達 END 語句時自動關閉它。游標關閉之后,不能使用 FETCH 來使用該游標。
例 2
創建 users 數據表,並插入數據,SQL 語句和運行結果如下:
mysql> create table if not exists user -> ( -> id int unsigned primary key auto_increment,
-> username varchar(25), -> userpass varchar(25) -> ); Query OK, 0 rows affected (0.50 sec)
mysql> INSERT INTO users VALUES(null,'sheng','sheng123'),(null,'yu','yu123'),(null,'ling','ling123'); Query OK, 3 rows affected (0.12 sec)
創建存儲過程 test_cursor,並創建游標 cur_test,根據入參uid查詢users返回, 入參沒值默認查詢 users 數據表中的第 2 條記錄,SQL 語句和執行過程如下:
注意: 這里存儲過程中涉及到的控制語句, 在下一章節中有講解, 這里看不懂沒事, 可以看完下一節再回來細品一下!
mysql> \. E:\java\mysql\my.sql
這里我是將存儲過程寫在了本地的一個文件my.sql中, 使用命令\.或source就可以直接調用執行此文件, 即執行文件中的sql, my.sql文件內容如下
delimiter // create procedure test_cursor(in uid int(4), out result varchar(30)) begin declare name, pass varchar(10); declare done int(2); declare count int(2) default 0; DECLARE cur_test CURSOR FOR select username, userpass from users; declare continue handler for sqlstate '02000' set done=1; if uid then select concat_ws(',', result, username, userpass) into result from users where id = uid; // 如果uid有值直接查詢 else // 如果uid沒值 open cur_test; // 打開游標 jkd:loop // 循環 fetch cur_test into name, pass; set count = count + 1; // if count = 2 then // 如果count等於2, 就是第二條數據 select concat_ws(',', name, pass) into result; leave jkd; // 跳出循環 end if; end loop jkd; close cur_test; end if; end// delimiter ;
上面的sql中關於"DECLARE continue handler FOR SQLSTATE '02000' SET done = 1;"的使用, 看這里 -> 說明
創建 update_users() 存儲過程,定義 cursor_update游標,將表 users 中的 user_name 字段全部修改為 入參的值,SQL 語句和執行過程如下。
source E:\java\mysql\my.sql
my.sql內容如下:
delimiter // create procedure update_users(in newname varchar(20)) begin declare done int(2); declare uid int(4); declare cursor_update cursor for select id from users; declare continue handler for sqlstate '02000' set done=1; open cursor_update; jkd:loop fetch cursor_update into uid; update users set username=newname where id=uid; if done=1 then leave jkd; end if; end loop jkd; close cursor_update; end// delimiter ;
調用查看:
mysql> call update_users('test'); Query OK, 0 rows affected (0.26 sec) mysql> select * from users; +----+----------+----------+ | id | username | userpass | +----+----------+----------+ | 1 | test | sheng123 | | 2 | test | yu123 | | 3 | test | ling123 | +----+----------+----------+ 3 rows in set (0.00 sec) mysql> call update_users('test666'); Query OK, 0 rows affected (0.25 sec) mysql> select * from users; +----+----------+----------+ | id | username | userpass | +----+----------+----------+ | 1 | test666 | sheng123 | | 2 | test666 | yu123 | | 3 | test666 | ling123 | +----+----------+----------+ 3 rows in set (0.00 sec)
結果顯示,users 表中的 user_name 字段已經全部修改為 入參的值。