MySQL入門(三)

本學習筆記參考《MySQL必知必會》和官方手冊MySQL 5.6 Reference Manual

MySQL入門(一)
MySQL入門(二)

本文內容:
- MySQL存儲過程
- MySQL遊標
- MySQL觸發器

六、MySQL存儲過程

6.1 什麼是存儲過程

簡單來說,就是爲以後的使用而保存的一條或多條MySQL語句的集合。

6.2 使用存儲過程

(1) 創建存儲過程

mysql> DELIMITER  //
mysql> CREATE PROCEDURE productpricing( )
    -> BEGIN
    -> SELECT Avg(prod_price) AS priceaverage
    -> FROM products;
    -> END  //
Query OK, 0 rows affected (0.09 sec)

mysql> DELIMITER  ;

上面的語句創建了一個名爲 productpricing 的存儲過程,productpricing( ) 的括號裏可以加入參數列表,BEGIN 和 END 之間爲過程體。由於 MySQL 語句的分隔符爲 ; ,而 mysql 命令行實用程序的分隔符也爲 ; ,爲了避免存儲過程體裏的 ; 不被 mysql 實用程序解釋,解決辦法是臨時更改命令行實用程序的語句分隔符。DELIMITER // 語句重新定義分隔符爲 // ,在創建完存儲過程後再用 DELIMITER ; 把分隔符改回來。

(2) 使用存儲過程

mysql> CALL productpricing( );

+————–+
| priceaverage |
+————–+
| 16.133571 |
+————–+

(3) 刪除存儲過程

存儲過程在創建之後,就被保存在服務器上以供使用,直至被刪除,刪除命令如下:

mysql> DROP PROCEDURE productpricing;

注意:存儲過程名後面沒有括號。
如果指定要刪除的存儲過程存在則刪除,如果不存在就會出錯。爲了使在不存在時也不至於出錯可使用這樣的語句:

mysql> DROP PROCEDURE IF EXISTS productpricing;

(4) 使用參數

mysql> DELIMITER  //
mysql> CREATE PROCEDURE productpricing(
    -> OUT pl DECIMAL(8, 2),
    -> OUT ph DECIMAL(8, 2),
    -> OUT pa DECIMAL(8, 2)
    -> )
    -> BEGIN
    -> SELECT Min(prod_price)
    -> INTO pl
    -> FROM products;
    -> SELECT Max(prod_price)
    -> INTO ph
    -> FROM products;
    -> SELECT Avg(prod_price)
    -> INTO pa
    -> FROM products;
    -> END  //
Query OK, 0 rows affected (0.07 sec)

mysql> DELIMITER ;

此存儲過程接收3個參數:pl 存儲產品的最低價格,ph 存儲產品的最高價格,pa 存儲產品的平均價格。每個參數必須有指定的類型,這裏使用十進制。關鍵字 OUT 指出相應的參數用來從存儲過程傳出一個值(返回給調用者)。MySQL 支持 IN (傳遞給存儲過程)、OUT (從存儲過程傳出)和 INOUT (對存儲過程傳入和傳出)類型的參數。
調用這個存儲過程:

mysql> CALL productpricing(@pricelow, @pricehigh, @priceaverage);
Query OK, 1 row affected, 1 warning (0.04 sec)

mysql> SELECT @pricelow, @pricehigh, @priceaverage;

+———–+————+——————–+
| @pricelow | @pricehigh | @priceaverage |
+———–+————+——————–+
| 2.50 | 55.00 | 16.13 |
+———–+————+——————–+
所有 MySQL 變量都必須以 @ 開始。

另外一個例子:

mysql> DELIMITER //
mysql> CREATE PROCEDURE ordertotal(
         -> IN onumber INT,
         -> OUT ototal DECIMAL(8, 2)
         -> )
         -> BEGIN
         -> SELECT Sum(item_price * quantity)
         -> FROM orderitems
         -> WHERE order_num = onumber
         -> INTO ototal;
         -> END //

onumber 定義爲 IN ,因爲訂單號被傳入存儲過程。ototal 定義爲 OUT ,因爲要從存儲過程返回合計。

mysql> CALL ordertotal(20005, @total);
mysql> SELECT @total;

+——–+
| @total |
+——–+
| 149.87 |
+——–+

(5) 建立智能存儲過程

-- create_procedure.sql
-- Name: ordertotal
-- Parameters: onumber = order number
--             taxable = 0 if not taxable, 1 if taxable
--             ototal  = order total variable
DELIMITER  //
CREATE PROCEDURE ordertotal(
IN onumber INT,
IN taxable BOOLEAN,
OUT ototal DECIMAL(8, 2))
COMMENT 'Obtain order total, optionally adding tax'
BEGIN
    -- Declare variable for total
    DECLARE total DECIMAL(8, 2);
    -- Declare tax percentage
    DECLARE taxrate INT DEFAULT 6;

    -- Get the order total
    SELECT  Sum(item_price * quantity)  FROM  orderitems  where  order_num = onumber
    INTO total;

    -- Is this taxable?
    IF taxable THEN
        -- Yes, so add taxrate to the total
        SELECT total + (total / 100 * taxrate) INTO total;
    END IF;
    -- And finally, save to out variable
    SELECT total INTO ototal;
END//
DELIMITER  ;

此存儲過程有很大的變動。首先,增加了註釋(前面放置 –)。添加了另外一個參數 taxable,它是一個布爾值(如果要增加稅則爲真,否則爲假)。在存儲過程體中,用 DECLARE 語句定義了兩個局部變量。DECLARE 要求指定變量名和數據類型,它也支持可選的默認值。
COMMENT 關鍵字是可選的,如果給出,將在 SHOW PROCEDURE STATUS 的結果中顯示。

mysql> CALL ordertotal(20005, 0, @total);
mysql> SELECT @total;

+——–+
| @total |
+——–+
| 149.87 |
+——–+

mysql> CALL ordertotal(20005, 0, @total);
mysql> SELECT @total;

+——–+
| @total |
+——–+
| 158.86 |
+——–+

(6) 檢查存儲過程

檢查創建存儲過程的SQL語句:
SHOW CREATE PROCEDURE ordertotal;

如果想獲得詳細信息使用:
SHOW PROCEDURE STATUS; // 列出所有的存儲過程的詳細信息

可以使用 LIKE 起到過濾的作用:
SHOW PROCEDURE STATUS LIKE 'ordertotal';

七、MySQL遊標

7.1 什麼是遊標

    有時,需要在檢索出來的行中前進或後退一行或多行。遊標(cursor)是一個存儲在MySQL服務器上的數據庫查詢,它不是一條 SELECT 語句,而是被該語句檢索出來的結果集。在存儲了遊標之後,應用程序可以根據需要滾動或瀏覽器中的數據。

7.2 使用遊標

步驟:
1) 在能夠使用遊標前,必須聲明(定義)它,這個過程實際上沒有檢索數據,它只是定義要使用的 SELECT 語句。
2) 一旦聲明後,必須打開遊標以供使用。這個過程用前面定義的 SELECT 語句把數據實際檢索出來。
3) 對於填有數據的遊標,根據需要取出各行。
4)在結束遊標使用時,必須關閉遊標。

(1) 創建遊標

-- create_cursor.sql
DELIMITER  //
CREATE  PROCEDURE  processorder( )
BEGIN
    -- Declare the cursor
    DECLARE  ordernumbers  CURSOR
    FOR
    SELECT  order_num  FROM  orders;

    -- Open the cursor
    OPEN  ordernumbers;

    -- Close the cursor
    CLOSE  ordernumbers;
END//
DELIMITER  ;

    MySQL中的遊標只能用於存儲過程,DECLARE 語句用來定義和命名遊標,這裏爲 ordernumbers,在存儲過程處理完成後,遊標就會消失,因爲它侷限於存儲過程。該存儲過程只是打開和關閉了遊標,並沒有使用裏面的數據。CLOSE 釋放遊標使用的內存資源,因此在每個遊標不再需要時都應該關閉。如果你不明確關閉遊標,MySQL 將會在到達END語句時自動關閉它。

(2) 使用遊標

-- use_cursor.sql
DELIMITER  //
CREATE  PROCEDURE  processorder( )
BEGIN
    -- Declare local variables
    DECLARE  done  BOOLEAN  DEFAULT  0;
    DECLARE  onumber INT ;
    DECLARE  t  DECIMAL(8, 2);

    -- Declare the cursor
    DECLARE  ordernumbers  CURSOR
    FOR
    SELECT  order_num  FROM  orders;
    -- Declare continue handler
    DECLARE  CONTINUE  HANDLER  FOR  SQLSTATE  '02000'  SET  done = 1;

    -- Create a table to store the results
    CREATE  TABLE  IF  NOT  EXISTS  ordertotals
        (order_num  INT, total  DECIMAL(8, 2));

    -- Open the cursor
    OPEN  ordernumbers;

    -- Loop through all rows
    REPEAT
        -- Get order number
        FETCH  ordernumbers  INTO  onumber;

        -- Get the total for this order
        CALL  ordertotal(onumber, 1, t);

        -- Insert order and total into ordertotals
        INSERT  INTO ordertotals(order_num, total)  VALUES(onumber, t);

    -- End of loop
    UNTIL  done  END  REPEAT;

    -- Close the sursor
    CLOSE  ordernumbers;

END//
DELIMITER  ;

    在一個遊標被打開後,可以使用 FETCH 語句分別訪問它的每一行。FETCH 取出檢索的數據同時它還向前移動遊標中的內部行指針。該例子中的 FETCH 是在 REPEAT 內,因此它反覆執行直到 done 爲真(由 UNTIL done END REPEAT; 實現)。結束循環條件的語句爲:
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
這條語句定義了一個 CONTINUE HANDLER,它是在條件出現時被執行的代碼。這裏,它指出當 SQLSTATE ‘02000’ 出現時,SET done = 1。SQLSTATE ‘02000’ 是一個未找到條件,當 REPEAT 由於沒有更多的行共循環時,出現這個條件。
該存儲過程,計算出每個訂單號的帶稅的合計,並新建一個表,把這些數據插入到新建的表中。

mysql> source ./work/MySQL/use_cursor.sql;
mysql> CALL  processorder( );
mysql> SELECT * FROM ordertotals;

+————+———–+
| order_num | total |
+————+———–+
| 20005 | 158.86 |
| 20009 | 40.78 |
| 20006 | 58.30 |
| 20007 | 1060.00 |
| 20008 | 132.50 |
| 20008 | 132.50 |
+————+———–+

八、MySQL觸發器

8.1 什麼是觸發器

    如果你先讓某些語句在事件發生時自動執行,就需要用到觸發器。觸發器是 MySQL 響應以下任意語句而自動執行的一條 MySQL 語句:
DELETE; INSERT; UPDATE;
其它MySQL語句不支持觸發器。

8.2 創建觸發器

mysql> CREATE TRIGGER newproduct AFTER INSERT ON ordertotals 
     >  FOR EACH ROW SELECT 'Product added' INTO @q;

創建觸發器的語法:

CREATE
    [DEFINER = { user | CURRENT_USER }]
    TRIGGER trigger_name
    trigger_time trigger_event
    ON tbl_name FOR EACH ROW
    trigger_body

trigger_time: { BEFORE | AFTER }
trigger_event: { INSERT | UPDATE | DELETE }

注意:MySQL5.6 中的觸發器不能返回結果集; 只有表支持觸發器,視圖和臨時表都不支持。
這個觸發器,在每次向表 ordertotals 插入數據時(對於每行)都會執行 SELECT ‘Product added’ INTO @q 。

8.3 刪除觸發器

mysql> DROP TRIGGER newproduct;

觸發器不能更新或覆蓋,爲了修改一個觸發器,必須先刪除它然後重新創建。

8.4 使用觸發器

(1) INSERT觸發器

    在 INSERT 觸發器代碼內,可引用一個名爲 NEW 的虛擬表,訪問被插入的行;
在 BEFORE INSERT 觸發器中, NEW 中的值也可以被更新(允許更改將要被插入的值);
對於 AUTO_INCREMENT 列,NEW在 INSERT 之前包含0,在INSERT 之後包含新的自動生成值。

mysql> CREATE TRIGGER neworder AFTER INSERT ON orders                              
    -> FOR EACH ROW SELECT NEW.order_num INTO @o_num;

mysql> INSERT INTO orders(order_date, cust_id)
    -> VALUES(Now(), 10001);

mysql> SELECT @o_num;

+———-+
| @o_num |
+———-+
| 20010 |
+———-+

(2) DELETE觸發器

    在 DELETE 觸發器代碼內,你可以引用一個名爲 OLD 的虛擬表,訪問被刪除的行;
OLD 中的值全部都是隻讀的,不能更新;

mysql> DELIMITER //
mysql> CREATE TRIGGER deletemytable BEFORE DELETE ON mytable
    -> FOR EACH ROW
    -> BEGIN
    -> SELECT OLD.name INTO @n;
    -> INSERT INTO mytable_new(name, myphone) VALUES(OLD.name, OLD.phone);
    -> END//

mysql> DELETE FROM mytable WHERE name = 'Joy';
mysql> SELECT * FROM mytable_new;

mysql> SELECT @n;

+——+
| @n |
+——+
| Joy |
+——+
在觸發器中使用 BEGIN END 塊的好處是觸發器能容納多條SQL語句。

(3) UPDATE觸發器

    在 UPDATE 觸發器代碼中,你可以引用一個名爲 OLD 的虛擬表訪問以前(UPDATE語句執行前)的值,引用一個名爲 NEW 的虛擬表訪問新更新的值;
在 BERFORE UPDATE 觸發器中,NEW 中的值可能也被更新(允許更改將要用於UPDATE語句中的值);
OLD中的值全都是隻讀的,不能更新。

mysql> CREATE TRIGGER updatemytable BEFORE UPDATE ON mytable
    -> FOR EACH ROW SET NEW.name = Upper(NEW.name);

mysql> UPDATE mytable SET name = 'John'  WHERE myid = 1004;
mysql> SELECT name FROM mytable WHERE myid = 1004;

+——+
| name |
+——+
| JOHN |
+——+
早期版本(具體哪個版本開始可以不知)不允許在觸發器代碼中使用 CALL 調用存儲過程,在 MySQL 5.6 中是可以的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章