1. 程式人生 > >mysql儲存過程procedure 觸發器trigger 遊標cusor 控制語句(條件,迴圈)

mysql儲存過程procedure 觸發器trigger 遊標cusor 控制語句(條件,迴圈)

什麼是mysql儲存例程?
儲存例程是儲存在資料庫伺服器中的一組sql語句,通過在查詢中呼叫一個指定的名稱來執行這些sql語句命令.

為什麼要使用mysql儲存過程?
我們都知道應用程式分為兩種,一種是基於web,一種是基於桌面,他們都和資料庫進行互動來完成資料的存取工作。假設現在有一種應用程式包含了這兩 種,現在要修改其中的一個查詢sql語句,那麼我們可能要同時修改他們中對應的查詢sql語句,當我們的應用程式很龐大很複雜的時候問題就出現這,不易維 護!另外把sql查詢語句放在我們的web程式或桌面中很容易遭到sql注入的破壞。而儲存例程正好可以幫我們解決這些問題。
儲存過程(stored procedure)、儲存例程(store routine)、儲存函式區別
Mysql儲存例程實際包含了儲存過程和儲存函式,它們被統稱為儲存例程。
其中儲存過程主要完成在獲取記錄或插入記錄或更新記錄或刪除記錄,即完成select insert delete update等的工作。而儲存函式只完成查詢的工作,可接受輸入引數並返回一個結果。

建立mysql儲存過程、儲存函式
create procedure 儲存過程名(引數)
儲存過程體
create function 儲存函式名(引數)

下面是儲存過程的例子:

mysql> DELIMITER // 
mysql> CREATE PROCEDURE proc1(OUT s int) 
   -> BEGIN
   -> SELECT COUNT(*) INTO s FROM user; 
   -> END
   -> // 
mysql> DELIMITER ;
1)這裡需要注意的是DELIMITER//和DELIMITER;兩句,DELIMITER是分割符的意思,因為MySQL預設以”;”為分隔 符,如果我們沒有宣告分割符,那麼編譯器會把儲存過程當成SQL語句進行處理,則儲存過程的編譯過程會報錯,所以要事先用DELIMITER關鍵字申明當 前段分隔符,這樣MySQL才會將”;”當做儲存過程中的程式碼,不會執行這些程式碼,用完了之後要把分隔符還原。
(2)儲存過程根據需要可能會有輸入、輸出、輸入輸出引數,這裡有一個輸出引數s,型別是int型,如果有多個引數用”,”分割開。

(3)過程體的開始與結束使用BEGIN與END進行標識。

引數

MySQL儲存過程的引數用在儲存過程的定義,共有三種引數型別,IN,OUT,INOUT,形式如:
CREATEPROCEDURE 儲存過程名([[IN |OUT |INOUT ] 引數名 資料類形…])
IN 輸入引數:表示該引數的值必須在呼叫儲存過程時指定,在儲存過程中修改該引數的值不能被返回,為預設值
OUT 輸出引數:該值可在儲存過程內部被改變,並可返回
INOUT 輸入輸出引數:呼叫時指定,並且可被改變和返回

. IN引數例子

建立:

1.  mysql > DELIMITER //  
2.  mysql > CREATE PROCEDURE demo_in_parameter
(IN p_in int) 3. -> BEGIN 4. -> SELECT p_in;
5. -> SET p_in=2; 6. -> SELECT p_in; 7. -> END; 8. -> // 9. mysql > DELIMITER ;

執行結果:

1.  mysql > SET @p_in=1;  
2.  mysql > CALL demo_in_parameter(@p_in);  
3.  +------+  
4.  | p_in |  
5.  +------+  
6.  |   1  |   
7.  +------+  
8.   
9.  +------+  
10.| p_in |  
11.+------+  
12.|   2  |   
13.+------+  
14. 
15.mysql> SELECT @p_in;  
16.+-------+  
17.| @p_in |  
18.+-------+  
19.|  1    |  
20.+-------+  

以上可以看出,p_in雖然在儲存過程中被修改,但並不影響@p_id的值

.OUT引數例子

建立:

1.  mysql > DELIMITER //  
2.  mysql > CREATE PROCEDURE demo_out_parameter(OUT p_out int)  
3.       -> BEGIN 
4.       -> SELECT p_out;  
5.       -> SET p_out=2;  
6.       -> SELECT p_out;  
7.       -> END;  
8.       -> //  
9.  mysql > DELIMITER ; 

執行結果:

1.  mysql > SET @p_out=1;  
2.  mysql > CALL sp_demo_out_parameter(@p_out);  
3.  +-------+  
4.  | p_out |   
5.  +-------+  
6.  | NULL  |   
7.  +-------+  
8.   
9.  +-------+  
10.| p_out |  
11.+-------+  
12.|   2   |   
13.+-------+  
14. 
15.mysql> SELECT @p_out;  
16.+-------+  
17.| p_out |  
18.+-------+  
19.|   2   |  
20.+-------+  

INOUT引數例子

建立:

1.  mysql > DELIMITER //   
2.  mysql > CREATE PROCEDURE demo_inout_parameter(INOUT p_inout int)   
3.       -> BEGIN 
4.       -> SELECT p_inout;  
5.       -> SET p_inout=2;  
6.       -> SELECT p_inout;   
7.       -> END;  
8.       -> //   
9.  mysql > DELIMITER ; 

執行結果:

1.  mysql > SET @p_inout=1;  
2.  mysql > CALL demo_inout_parameter(@p_inout) ;  
3.  +---------+  
4.  | p_inout |  
5.  +---------+  
6.  |    1    |  
7.  +---------+  
8.   
9.  +---------+  
10.| p_inout |   
11.+---------+  
12.|    2    |  
13.+---------+  
14. 
15.mysql > SELECT @p_inout;  
16.+----------+  
17.| @p_inout |   
18.+----------+  
19.|    2     |  
20.+----------+ 

在儲存過程中可以定義區域性變數,還可以定義使用者變數(相當於全域性變數)。區域性變數在儲存過程中直接使用declare宣告,作用域在begin和end之間,全域性變數的話用@宣告,在整個會話中可用。

觸發器

 MySQL5版本後支援觸發器
    只有表支援觸發器,檢視不支援觸發器
    MySQL語句在需要的時被執行,儲存過程也是如此,但是如果你想要某條語句(或某些語句)在事件發生時自動執行,那該怎麼辦呢:例如:
        1 每增加一個顧客到某個資料庫表時,都檢查其電話號碼格式是否正確,區的縮寫是否為大寫
        2 每當訂購一個產品時,都從庫存數量中減少訂購的數量
        3 無論何時刪除一行,都在某個存檔中保留一個副本
    這寫例子的共同之處是他們都需要在某個表發生更改時自動處理。這就是觸發器。觸發器是MySQL響應一下任意語句而自動執行的一條MySQL語句
(或位於BEGIN和END語句之間的一組語句)
    1 DELETE
    2 INSERT
    3 UPDATE
    其他的MySQL語句不支援觸發器
    建立觸發器
        建立觸發器需要給出4條資訊
        1 唯一的觸發器名;  //儲存每個資料庫中的觸發器名唯一
        2 觸發器關聯的表;
        3 觸發器應該響應的活動(DELETE、INSERT或UPDATE)
        4 觸發器何時執行(處理前還是後,前是BEFORE 後是AFTER)
        建立觸發器用CREATE TRIGGER
        CREATE TRIGGER newproduct AFTER INSERT ON products
        FOR EACH ROW SELECT'Product added'
       建立新觸發器newproduct ,它將在INSERT語句成功執行後執行。這個觸發器還鎮定FOR EACH ROW,因此程式碼對每個插入的行執行。
這個例子作用是文字對每個插入的行顯示一次product added
        FOR EACH ROW 針對每個行都有作用,避免了INSERT一次插入多條語句
    觸發器定義規則
        觸發器按每個表每個事件每次地定義,每個表每個事件每次只允許定義一個觸發器,因此,每個表最多定義6個觸發器
(每條INSERT UPDATE 和DELETE的之前和之後)。單個觸發器不能與多個事件或多個表關聯,所以,如果你需要一個對INSERT 和UPDATE儲存執行的觸發器,
則應該定義兩個觸發器
    觸發器失敗  如果BEFORE(之前)觸發器失敗,則MySQL將不執行SQL語句的請求操作,此外,如果BEFORE觸發器或語句本身失敗,
MySQL將不執行AFTER(之後)觸發器
    刪除觸發器
        DROP TRIGGER newproduct;
        觸發器不能更新或覆蓋,所以修改觸發器只能先刪除再建立
    使用觸發器
        我們來看看每種觸發器以及它們的差別
    INSERT 觸發器
        INSERT觸發器在INSERT語句執行之前或之後執行。需要知道以下幾點:
    1 在INSERT觸發器程式碼內,可引用一個名為NEW的虛擬表,訪問被插入的行
    2 在BEFORE INSERT觸發器中,NEW中的值也可以被更新(允許更改插入的值)
    3 對於AUTO_INCREMENT列,NEW在INSERT執行之前包含0,在INSERT執行之後包含新的自動生成值
        提示:通常BEFORE用於資料驗證和淨化(目的是保證插入表中的資料確實是需要的資料)。本提示也適用於UPDATE觸發器
    DELETE 觸發器
        DELETE觸發器在語句執行之前還是之後執行,需要知道以下幾點:
    1 在DELETE觸發器程式碼內,你可以引用一個名為OLD的虛擬表,訪問被刪除的行;
    2 OLD中的值全部是隻讀的,不能更新
        例子演示適用OLD儲存將要除的行到一個存檔表中
        CREATE TRIGGERdeleteorder BEFORE DELETE ON orders
        FOR EACH ROW
        BEGIN  
        INSERT INTO archive_orders(order_num , order_date , cust_id)
        VALUES(OLD.order_num , OLD.order_date , OLD.cust_id);
        END;
        //此處的BEGIN  END塊是非必需的,可以沒有
    在任何訂單刪除之前執行這個觸發器,它適用一條INSERT語句將OLD中的值(將要刪除的值)儲存到一個名為archive_orders的存檔表中
    BEFORE DELETE觸發器的優點是(相對於AFTER DELETE觸發器),如果由於某種原因,訂單不能被存檔,DELETE本身將被放棄執行。
    多語言觸發器  正如上面所見,觸發器deleteorder 使用了BEGIN和END語句標記觸發器體。這在此例中並不是必需的,不過也沒有害處。
使用BEGIN  END塊的好處是觸發器能容納多條SQL語句。
    UPDATE觸發器
        UPDATE觸發器在語句執行之前還是之後執行,需要知道以下幾點:
        1 在UPDATE觸發器程式碼中,你可以引用一個名為OLD的虛擬表訪問(UPDATE語句前)的值,引用一名為NEW的虛擬表訪問新更新的值
        2 在BEFORE UPDATE觸發器中,NEW中的值可能被更新,(允許更改將要用於UPDATE語句中的值)
        3 OLD中的值全都是隻讀的,不能更新
            例子:保證州名的縮寫總是大寫(不管UPDATE語句給出的是大寫還是小寫)
            CREATE TRIGGER updatevendor BEFORE UPDATE ON vendores FOR EACH ROW SET NEW.vend_state = Upper(NEW.vend_state)

遊標

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

遊標缺點

遊標的缺點是針對有點而言的,也就是隻能一行一行操作,在資料量大的情況下,是不適用的,速度過慢。這裡有個比喻就是:當你去ATM存錢是希望一次性存完呢,還是100一張一張的存,這裡的100一張一張存就是遊標針對行的操作。 資料庫大部分是面對集合的,業務會比較複雜,而遊標使用會有死鎖,影響其他的業務操作,不可取。 當資料量大時,使用遊標會造成記憶體不足現象。


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
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()$
執行結果如下:

這時候你會發現我們只得到了一個查詢結果,想要都顯示,我們需要自己控制

給遊標定義一個越界的標識
//在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()$
執行結果為:

由此,問題解決。

控制語句

條件語句

if-then -else語句

1.  mysql > DELIMITER //  
2.  mysql > CREATE PROCEDURE proc2(IN parameter int)  
3.       -> begin 
4.       -> declare var int;  
5.       -> set var=parameter+1;  
6.       -> if var=0 then 
7.       -> insert into t values(17);  
8.       -> end if;  
9.       -> if parameter=0 then 
10.     -> update t set s1=s1+1;  
11.     -> else 
12.     -> update t set s1=s1+2;  
13.     -> end if;  
14.     -> end;  
15.     -> //  
16.mysql > DELIMITER ;  

case語句:

1.  mysql > DELIMITER //  
2.  mysql > CREATE PROCEDURE proc3 (in parameter int)  
3.       -> begin 
4.       -> declare var int;  
5.       -> set var=parameter+1;  
6.       -> case var  
7.       -> when 0 then   
8.       -> insert into t values(17);  
9.       -> when 1 then   
10.     -> insert into t values(18);  
11.     -> else   
12.     -> insert into t values(19);  
13.     -> end case;  
14.     -> end;  
15.     -> //  
16.mysql > DELIMITER ; 
case
        when var=0 then
               insert into t values(30);
        when var>0 then
        when var<0 then
        else

end case

迴圈語句

while ···· end while:

1.  mysql > DELIMITER //  
2.  mysql > CREATE PROCEDURE proc4()  
3.       -> begin 
4.       -> declare var int;  
5.       -> set var=0;  
6.       -> while var<6 do  
7.       -> insert into t values(var);  
8.       -> set var=var+1;  
9.       -> end while;  
10.     -> end;  
11.     -> //  
12.mysql > DELIMITER ; 
 while條件 do
--迴圈體
endwhile

repeat···· end repeat:

它在執行操作後檢查結果,而while則是執行前進行檢查。

1.  mysql > DELIMITER //  
2.  mysql > CREATE PROCEDURE proc5 ()  
3.       -> begin   
4.       -> declare v int;  
5.       -> set v=0;  
6.       -> repeat  
7.       -> insert into t values(v);  
8.       -> set v=v+1;  
9.       -> until v>=5  
10.     -> end repeat;  
11.     -> end;  
12.     -> //  
13.mysql > DELIMITER ;  
 repeat
--迴圈體
until迴圈條件     
endrepeat;

loop ·····endloop:

loop迴圈不需要初始條件,這點和while 迴圈相似,同時和repeat迴圈一樣不需要結束條件, leave語句的意義是離開迴圈。

1.  mysql > DELIMITER //  
2.  mysql > CREATE PROCEDURE proc6 ()  
3.       -> begin 
4.       -> declare v int;  
5.       -> set v=0;  
6.       -> LOOP_LABLE:loop  
7.       -> insert into t values(v);  
8.       -> set v=v+1;  
9.       -> if v >=5 then 
10.     -> leave LOOP_LABLE;  
11.     -> end if;  
12.     -> end loop;  
13.     -> end;  
14.     -> //  
15.mysql > DELIMITER ;  
f這個loop很不喜歡,不喜歡用。