1. 程式人生 > >mysql中游標的使用案例詳解(學習筆記)

mysql中游標的使用案例詳解(學習筆記)

1.遊標是啥玩意?
簡單的說:遊標(cursor)就是遊動的標識,啥意思呢,通俗的這麼說,一條sql取出對應n條結果資源的介面/控制代碼,就是遊標,沿著遊標可以一次取出一行。我給大家準備一張圖:


2.怎麼使用遊標?
//1.宣告/定義一個遊標
declare 宣告;declare 遊標名 cursor for select_statement;
//2.開啟一個遊標
open 開啟;open 遊標名
//3.取值
fetch 取值;fetch 遊標名 into var1,var2[,...]
//4.關閉一個遊標
close 關閉;close 遊標名;

3.遊標實戰
未使用遊標:
create procedure p1()
begin
select * from category;
end$

call p1$
執行結果:


使用遊標:
/**
註釋
*/
create procedure p2()
begin
//一下定義的三個變數用於將fetch取值出來的值放到對應的變數中
declare row_cat_id int;
declare row_cat_name int;
declare row_parent_id int;
//定義遊標
declare getcategory cursor for select cat_id,cat_name,parent_id from category;
//開啟遊標
open getcategory;
//取值
fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
//關閉遊標
close getcategory;
end$
/**
未註釋
*/
create procedure p2()
begin
declare row_cat_id int;
declare row_cat_name varchar(90);
declare row_parent_id int;
declare getcategory cursor for select cat_id,cat_name,parent_id from category;
open getcategory;
fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
close getcategory;
end$
//執行的時候你會發現是0行,這時因為我們將查詢出的結果賦給了變數,我們有沒有對賦值後的變數進行查詢顯示。所以是0行。因此,我們要重新改進。
call p2()$
執行結果為:

//改進
//刪除遊標重新執行
drop procedure p2$
create procedure p2()
begin
declare row_cat_id int;
declare row_cat_name varchar(90);
declare row_parent_id int;
declare getcategory cursor for select cat_id,cat_name,parent_id from category;
open getcategory;
fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
select row_cat_id,row_cat_name,row_parent_id;
close getcategory;
end$
call p2()$
執行結果如下:

這時候你會發現我們只得到了一個查詢結果,這時為什麼呢?這時因為控制權在我們這裡,我願意取一行就一行,願意取兩行就兩行。因此,我在把剛才的動作變一下。
create procedure p3()
begin
declare row_cat_id int;
declare row_cat_name varchar(90);
declare row_parent_id int;
declare getcategory cursor for select cat_id,cat_name,parent_id from category;
open getcategory;
fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
select row_cat_id,row_cat_name,row_parent_id;

fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
select row_cat_id,row_cat_name,row_parent_id;

fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
select row_cat_id,row_cat_name,row_parent_id;

fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
select row_cat_id,row_cat_name,row_parent_id;

fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
select row_cat_id,row_cat_name,row_parent_id;

fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
select row_cat_id,row_cat_name,row_parent_id;
close getcategory;
end$
我fetch六次,查詢五次,這時候我們會得到什麼呢?試一下嘛!
call p4()$
執行結果如下:

提示:發現什麼了嗎?相同的語句,我們每取一次就往後遊一次,有幾次就遊幾次,直到你把游完所有標識,這時候系統就會報【02000】這個錯誤,告訴我們遊標已經走完了。我們這裡遊了六次,因此會列印前六條記錄。
所以啊,我們如何循環遊標來取出所有行?
思路:
1.計算所有行select count(*) 
create procedure p4()
begin
declare row_cat_id int;
declare row_cat_name varchar(90);
declare row_parent_id int;
declare cnt int default 0;//定義總行數
declare i int default 0;
declare getcategory cursor for select cat_id,cat_name,parent_id from category;
select count(*) into cnt from category;//計算得出的總行數查詢後賦給cnt變數
open getcategory;
repeat
set i:=i+1;
fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
select row_cat_id,row_cat_name,row_parent_id;
until i>=cnt end repeat;
close getcategory;
end$
call p4()$
執行結果為:


由此可見已經一條條得到表中結果,再次強調遊標在此處的意義在於它把取出每一行的權利交給了你,你可以在每取出這一行的repeat中再做其他判斷。
2.給遊標定義一個越界的標識
//在mysql遊標(cursor)中,可以定義continue handler來操作一個越界標識,使用語法:declare continue handler for NOT FOUND statemet(當沒資料的時候要執行的語句)

//這句話的意思是說,我要宣告一個控制代碼事件,你往後取,一旦發生NOT FOUND 事件就會出發set ergodic:=0這個語句
create procedure p5()
begin
declare row_cat_id int;
declare row_cat_name varchar(90);
declare row_parent_id int;
declare ergodic int default 1;//宣告一個變量表明還有資料可遍歷
declare getcategory cursor for select cat_id,cat_name,parent_id from category;
declare continue handler for NOT FOUND set ergodic:=0;
open getcategory;
repeat
fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
select row_cat_id,row_cat_name,row_parent_id;
until ergodic=0 end repeat;
close getcategory;
end$
call p5()$
執行結果為:

發現問題沒有?為啥第最後一個查了兩次?這是什麼原因?我們不妨來分析一下我們寫的語句:


既然問題已經分析出來後,我們如何處理這個問題呢?
解決方案:宣告處理的hanlder不再是continue,而是exit即可達到目的。即:declare exit handler for NOT FOUND set ergodic:=0;
//exit與continue的區別是:exit觸發後,後面的語句不再執行,而continue還需要繼續執行。
注意:除了這exit與continue兩種方式外,還有一種方式就是undo handler。
//採用undo handler方式觸發後,前面的語句直接撤銷。【但目前好像這種方式,mysql還不支援】
create procedure p6()
begin
declare row_cat_id int;
declare row_cat_name varchar(90);
declare row_parent_id int;
declare ergodic int default 1;
declare getcategory cursor for select cat_id,cat_name,parent_id from category;
declare exit handler for NOT FOUND set ergodic:=0;
open getcategory;
repeat
fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
select row_cat_id,row_cat_name,row_parent_id;
until ergodic=0 end repeat;
close getcategory;
end$
call p6()$
執行結果為:

由此,問題解決。
題外話:如果我們還是使用continue的方式去實現不重複的話,我們應該怎麼做呢?這時候我們可以在我們程式碼邏輯上處理這種問題,我們先來分析一下程式碼:
提示:
你有沒有考慮過,你第一次fetch取值的時候會不會存在沒有資料(值為空)的情況,因此我們可以先手動的fetch一行出來,緊接著repeat下面的資料。
create procedure p7()
begin
declare row_cat_id int;
declare row_cat_name varchar(90);
declare row_parent_id int;
declare ergodic int default 1;
declare getcategory cursor for select cat_id,cat_name,parent_id from category;
declare continue handler for NOT FOUND set ergodic:=0;
open getcategory;
fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
repeat
select row_cat_id,row_cat_name,row_parent_id;
fetch getcategory into row_cat_id,row_cat_name,row_parent_id;
until ergodic=0 end repeat;
close getcategory;
end$
call p7()$
執行結果為:


附件:
測試資料庫與資料表:
create table category (
cat_id smallint unsigned auto_increment primary key,
cat_name varchar(90) not null default '',
parent_id smallint unsigned
)engine myisam charset utf8;

INSERT INTO `category` VALUES 
(1,'手機型別',0),
(2,'CDMA手機',1),
(3,'GSM手機',1),
(4,'3G手機',1),
(5,'雙模手機',1),
(6,'手機配件',0),
(7,'充電器',6),
(8,'耳機',6),
(9,'電池',6),
(11,'讀卡器和記憶體卡',6),
(12,'充值卡',0),
(13,'小靈通/固話充值卡',12),
(14,'移動手機充值卡',12),
(15,'聯通手機充值卡',12);