游標
在操作mysql的時候我們知道MySQL檢索操作返回一組稱為結果集的行。這組返回的行都是與 SQL語句相匹配的行(零行或多行)。使用簡單的 SELECT語句,例如,沒有辦法得到第一行、下一行或前 10行,也不存在每次一行地處理所有行的簡單方法(相對於成批地處理它們)。有時,需要在檢索出來的行中前進或后退一行或多行。這就是使用游標的原因。游標( cursor)是一個存儲在MySQL服務器上的數據庫查詢,它不是一條 SELECT語句,而是被該語句檢索出來的結果集。在存儲了游標之后,應用程序可以根據需要滾動或瀏覽其中的數據。游標主要用於交互式應用,其中用戶需要滾動屏幕上的數據,並對數據進行瀏覽或做出更改。
注意:只能用於存儲過程。不像多數 DBMS,MySQL 游標只能用於存儲過程(和函數)。
使用游標
使用游標涉及幾個明確的步驟。
1、在能夠使用游標前,必須聲明(定義)它。這個過程實際上沒有檢索數據,它只是定義要使用的 SELECT語句。
2、一旦聲明后,必須打開游標以供使用。這個過程用前面定義的SELECT語句把數據實際檢索出來。
3、對於填有數據的游標,根據需要取出(檢索)各行。
4、在結束游標使用時,必須關閉游標。
在聲明游標后,可根據需要頻繁地打開和關閉游標。在游標打開后,可根據需要頻繁地執行取操作。
創建游標
游標用 DECLARE語句創建。 DECLARE命名游標,並定義相應的 SELECT語句,根據需要帶WHERE和其他子句。例如,下面的語句定義了名為 ordernumbers的游標,使用了可以檢索所有訂單的 SELECT語句。
mysql> delimiter // mysql> create procedure duhuo() -> begin -> declare ordnums cursor -> for -> select order_num from orders; -> end -> //
這個存儲過程並沒有做很多事情, DECLARE語句用來定義和命名游標,這里為 ordnums。存儲過程處理完成后,游標就消失(因為它局限於存儲過程)。在我們定義了游標之后。可以打開它。
打開與關閉游標
游標使用OPEN CURSOR語句來打開。如下:
OPEN ordnums;
在處理 OPEN語句時執行查詢,存儲檢索出的數據以供瀏覽和滾動。
游標處理完成后,應當使用如下語句關閉游標:
close ordnums;
CLOSE釋放游標使用的所有內部內存和資源,因此在每個游標不再需要時都應該關閉。
在一個游標關閉后,如果沒有重新打開,則不能使用它。但是,使用聲明過的游標不需要再次聲明,用 OPEN語句打開它就可以了。此外mysql具有隱含關閉功能。如果你不明確關閉游標, MySQL將會在到達END語句時自動關閉它。
下面是前面例子的修改版本:
mysql> delimiter // mysql> create procedure duhuo1() -> begin -> declare dudu cursor -> for -> select order_num from orders; -> open dudu; -> close dudu; -> end -> //
這個存儲過程聲明、打開和關閉一個游標。但對檢索出的數據什么也沒做。
使用游標數據
在一個游標被打開后,可以使用 FETCH語句分別訪問它的每一行。FETCH指定檢索什么數據(所需的列),檢索出來的數據存儲在什么地方。它還向前移動游標中的內部行指針,使下一條FETCH語句檢索下一行(不重復讀取同一行)。
第一個例子從游標中檢索單個行(第一行):
mysql> delimiter // mysql> create procedure demo0() -> begin -> declare tx int; -> declare du1 cursor -> for -> select order_num from orders; -> open du1; -> fetch du1 into tx; -> close du1; -> end -> // Query OK, 0 rows affected (0.02 sec)
其中 FETCH用來檢索當前行的order_num列(將自動從第一行開始)到一個名為 tx的局部聲明的變量中。對檢索出的數據不做任何處理。
在下一個例子中,循環檢索數據,從第一行到最后一行:
mysql> create procedure demo0() -> begin -> delcare done boolean default 0; -> declare du int; -> declare ordernu cursor -> for -> select order_num from orders; -> declare continue handler for sqlstate '02000' set done = 1; -> open ordernu; -> repeat -> fetch ordernu into du; -> until done end repeat; -> close ordernu; -> end//
與前一個例子一樣,這個例子使用 FETCH檢索當前order_num到聲明的名為 o的變量中。但與前一個例子不一樣的是,這個例子中的 FETCH是在REPEAT 內,因此它反復執行直到 done為真(由UNTILdone END REPEAT;規定)。為使它起作用,用一個 DEFAULT 0(假,不結束)定義變量 done。那么,done 怎樣才能在結束時被設置為真呢?答案是用以下語句:
declare continue handler for sqlstate '02000' set done = 1;
這條語句定義了一個 CONTINUE HANDLER,它是在條件出現時被執行的代碼。這里,它指出當 SQLSTATE '02000'出現時,SET done=1 。SQLSTATE '02000'是一個未找到條件,當REPEAT由於沒有更多的行供循環而不能繼續時,出現這個條件。
關於MySQL 5使用的 MySQL錯誤代碼列表,請參閱 http://dev.mysql.com/doc/mysql/en/error-handling.html 。
注意:DECLARE語句的次序。DECLARE語句的發布存在特定的次序。用DECLARE語句定義的局部變量必須在定義任意游標或句柄之前定義,而句柄必須在游標之后定義。不遵守此順序將產生錯誤消息。
如果調用這個存儲過程,它將定義幾個變量和一個 CONTINUEHANDLER,定義並打開一個游標,重復讀取所有行,然后關閉游標。如果一切正常,你可以在循環內放入任意需要的處理(在 FETCH語句之后,循環結束之前)。
為了把這些內容組織起來,下面給出我們的游標存儲過程樣例的更進一步修改的版本,這次對取出的數據進行某種實際的處理:
mysql> delimiter // mysql> create procedure liwei() -> begin -> declare done boolean default 0; -> declare li int; -> declare wei decimal(8,2); -> declare numb cursor -> for -> select order_num from orders; -> declare continue handler for sqlstate '02000' set done=1; -> create table if not exists ordertotals -> (order_num int,total decimal(8,2)); -> open numb; -> repeat -> fetch ordernumbers into li; -> call ordertotals(tx,1,t); -> insert into ordertotals(order_num, total) -> values(li,wei); -> until done end repeat; -> close numd; -> end -> //
在這個例子中,我們增加了另一個名為 t的變量(存儲每個訂單的合計)。此存儲過程還在運行中創建了一個新表(如果它不存在的話),名為 ordertotals。這個表將保存存儲過程生成的結果。 FETCH像以前一樣取每個 order_num,然后用CALL 執行另一個存儲過程(我們在前一章中創建)來計算每個訂單的帶稅的合計(結果存儲到 t)。最后,用INSERT保存每個訂單的訂單號和合計。
調用存儲過程:
call liwei();
此存儲過程不返回數據,但它能夠創建和填充另一個表,可以用一條簡單的 SELECT語句查看該表:
select * from ordertotals;
輸出:
+----------------+--------------+ | order_num | total | +----------------+--------------+ | 20005 | 158.56 | | 20006 | 25.78 | | 20007 | 1068.00 | +----------------+--------------+
這樣,我們就得到了存儲過程、游標、逐行處理以及存儲過程調用其他存儲過程的一個完整的工作樣例。