1. 程式人生 > >mysql 儲存過程與函式

mysql 儲存過程與函式

 

什麼是儲存過程和函式

    儲存過程和函式是事先經過編譯儲存在資料庫中的一段 SQL語句的集合,呼叫儲存過程和函式可以簡化應用開發人員的很多工作,減少資料在資料庫和應用伺服器中的傳輸,對於提高資料處理的效率是有好處的。

    儲存過程和函式的區別在於:函式必須有返回值,而儲存過程沒有,儲存過程的引數可以使用 IN,OUT,INOUT 型別,而函式的引數只能是IN 型別的。如果有函式從其他型別的資料庫遷移到 MySQL,那麼就有可能因此需要將函式改造成儲存過程。

儲存過程和函式的相關操作

    在對儲存過程或函式進行相關操作時,需要首先確認使用者是否具有相關的許可權。例如,建立儲存過程或者函式需要 CREATE ROUTINE 許可權。修改或者刪除儲存過程或者函式需要 ALTER ROUTINE 許可權,執行儲存過程或者函式需要 EXECUTE 許可權。

 

建立 或 修改 儲存過程 或 函式

CREATE PROCEDURE sp_name ([proc_parameter[,....]])
    [characteristic...] routine_body


CREATE FUNCTION sp_name ([func_parameter[,....]])
    RETURNS type
    [characteristic...] routine_body



    proc_parameter:
    [IN | OUT | INOUT ] param_name type
    
    func_parameter:
    param_name type

    type:
    Any valid MySQL data type 

    characteristic:
    LANQUAGE SQL
    | [NOT] DETERMINISTIC 
    | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
    | SQL SECURITY { DEFINER | INVOKER}
    | COMMENT 'string'
    
    routine_body:
     Valid SQL procedure statement or statements
    

ALTER { PROCEDURE | FUNCTION } sp_name [characteristic...]

    characteristic:

      { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
    | SQL SECURITY { DEFINER | INVOKER}
    | COMMENT 'string'  

 

    呼叫過程的語法如下:

CALL sp_name([parameter[,....]]);

    MySQL 的儲存過程和函式中允許包含 DDL 語句,也允許在儲存過程中執行 提交 (Commit,即確認之前的修改或者 回滾 (Rollback, 即放棄之前的修改),但是儲存過程和函式中不允許執行 LOAD DATA INFILE 語句。此外,儲存過程和函式中允許呼叫其它的過程或者函式。

下面建立了一個新的過程 proc_adder:

mysql > DELIMITER $$
mysql > CREATE PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int)
      > BEGIN
      > DECLARE c int;
      > if a is null then
      >    set a = 0; 
      > end if;
      > if b is null then
      >     set b = 0;
      > end if;
      > set sum  = a + b;
      > END $$
Query Ok, 0 rows affected (0.00 sec)
mysql >
mysql > DELIMITER ;


     上面的儲存過程比較簡單做的是一個加法運算。

     通常我們在執行建立過程和函式之前,都會通過  【DELIMITER $$ 】命令將語句的結束符由 【;】 修改成其它符號,這裡使用的是 【$$】 ,這樣在過程和函式中的【;】 就不會被 MySQL 解釋成語句的結束而提示錯誤。在儲存過程或者函式建立完畢,通過 【DELIMITER ;】 再將結束符改回成 【;】。

  執行結果:

mysql> set @b=5;
Query OK, 0 rows affected (0.00 sec)

mysql>  call proc_adder(2,@b,@s);
Query OK, 0 rows affected (0.00 sec)

mysql> select @s as sum;
+------+
| sum  |
+------+
|    7 |
+------+
1 row in set (0.00 sec)

mysql> 

    可以看到,呼叫儲存過程與直接執行SQL語句的效果是相同的,但是儲存過的好處在於處理邏輯都封裝在資料庫端,呼叫值不需要了解中間的處理邏輯,一旦處理邏輯發生變化,只需要修改儲存過程即可,而對呼叫者的程式完全沒有影響。

    另外,和檢視的建立語法稍有不同,儲存過程和函式的 CREATE 語法不支援用  CRATE OR REPLACE 對儲存過程和函式進行修改,如果需要對已有的儲存過程和函式進行修改,需要執行ALTER 語法。

    下面對 characteristic 特徵值的部分進行簡單說明:

  • LANAUAGE  SQL: 說明下面過程的 body 是使用SQL 語言編寫,這條是系統預設的,為今後 MySQL 會支援的除 SQL 外的其它語言支援的儲存過程而準備。
  • [NOT] DETERMINISTIC: DETERMINISTIC 確定的,即每次輸入一樣,輸出也一樣的程式,NOT DETERMINISTIC 非確定的,預設是非確定的,當前這個特徵值還沒有被優化程式使用。
  • { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }: 這些特徵值提供子程式使用資料的內在資訊,這些特徵值目前只是提供給伺服器,並沒有根據這些特徵值來約束過程實際使用資料的情況。CONTAINS SQL:表示子程式包含讀或寫資料的語句,NO SQL:表示子程式不包含 SQL 語句,READS SQL DATA: 表示子程式包含讀資料的語句,但不包含寫資料的語句,MODIFIES SQL DATA: 表示子程式包含寫資料的語句,如果這些特徵值沒有明確給定,預設使用的值是:CONTAINS SQL。
  • SQL SECURITY { DEFINER | INVOKER}: 可以用來指定子程式該用建立子程式的許可來執行,還是使用呼叫的者的許可來執行,預設值是 DEFINER。
  • COMMENT 'string' :儲存過程或是函式的註釋資訊。

刪除儲存過程或是函式

    一次只能刪除一個儲存過程或者函式,刪除儲存過程或者函式要有該過程或者函式的 ALTER ROUTINE許可權,具體語法如下:

DROP { PROCEDURE | FOUNCTION }{IF EXISTS} sp_name;

檢視儲存過程或者函式

show { PROCEDURE | FOUNCTION } STATUS [like 'pattern'];

       檢視儲存過程 proc_adder 的資訊:


mysql> SHOW PROCEDURE STATUS LIKE 'proc_adder'\G;
*************************** 1. row ***************************
                  Db: daicooper
                Name: proc_adder
                Type: PROCEDURE
             Definer: [email protected]
            Modified: 2018-12-06 14:07:52
             Created: 2018-12-06 14:07:52
       Security_type: DEFINER
             Comment: 
character_set_client: utf8mb4
collation_connection: utf8mb4_unicode_ci
  Database Collation: utf8_general_ci
1 row in set (0.00 sec)

ERROR: 
No query specified

mysql> 

檢視儲存過程或者函式的定義

show CREATE { PROCEDURE | FOUNCTION } sp_name;

     檢視儲存過程 proc_adder 的定義:


mysql> SHOW PROCEDURE STATUS LIKE 'proc_adder'\G;
*************************** 1. row ***************************
                  Db: daicooper
                Name: proc_adder
                Type: PROCEDURE
             Definer: [email protected]
            Modified: 2018-12-06 14:07:52
             Created: 2018-12-06 14:07:52
       Security_type: DEFINER
             Comment: 
character_set_client: utf8mb4
collation_connection: utf8mb4_unicode_ci
  Database Collation: utf8_general_ci
1 row in set (0.00 sec)

ERROR: 
No query specified

mysql> 

通過 information_schema.routines 瞭解儲存過程和函式的資訊

    檢視儲存過程 proc_adder 的定義:

mysql> select * from information_schema.routines where ROUTINE_NAME = 'proc_adder'\G;
*************************** 1. row ***************************
           SPECIFIC_NAME: proc_adder
         ROUTINE_CATALOG: def
          ROUTINE_SCHEMA: daicooper
            ROUTINE_NAME: proc_adder
            ROUTINE_TYPE: PROCEDURE
               DATA_TYPE: 
CHARACTER_MAXIMUM_LENGTH: NULL
  CHARACTER_OCTET_LENGTH: NULL
       NUMERIC_PRECISION: NULL
           NUMERIC_SCALE: NULL
      DATETIME_PRECISION: NULL
      CHARACTER_SET_NAME: NULL
          COLLATION_NAME: NULL
          DTD_IDENTIFIER: NULL
            ROUTINE_BODY: SQL
      ROUTINE_DEFINITION: BEGIN
    #Routine body goes here...

    DECLARE c int;
    if a is null then set a = 0; 
    end if;
  
    if b is null then set b = 0;
    end if;

    set sum  = a + b;
END
           EXTERNAL_NAME: NULL
       EXTERNAL_LANGUAGE: NULL
         PARAMETER_STYLE: SQL
        IS_DETERMINISTIC: NO
         SQL_DATA_ACCESS: CONTAINS SQL
                SQL_PATH: NULL
           SECURITY_TYPE: DEFINER
                 CREATED: 2018-12-06 14:07:52
            LAST_ALTERED: 2018-12-06 14:07:52
                SQL_MODE: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
         ROUTINE_COMMENT: 
                 DEFINER: [email protected]
    CHARACTER_SET_CLIENT: utf8mb4
    COLLATION_CONNECTION: utf8mb4_unicode_ci
      DATABASE_COLLATION: utf8_general_ci
1 row in set (0.00 sec)

ERROR: 
No query specified

mysql> 

變數的使用

     儲存過程和函式中可以使用變數,變數是不區分大小寫的。

1:變數的定義

    通過 DECLARE 可以定義一個區域性變數,改變數的作用範文只能在 BEGIN....END 塊中,可以用在巢狀的塊中。

    變數的定義必須寫在複合語句的開頭,並且在任何其它語句的前面,可以一次宣告多個相同型別的變數。如果需要,可以使用 DEFAULT 賦預設值。

    定義一個變數的語法如下:

DELARE var_name [,....] type [DEFAULT value]

    例如:定義一個 DATE 型別的變數 ,名稱是 last_month_start

DELARE last_month_start DATE;

1:變數的賦值

    變數可以直接賦值,或者通過查詢賦值。直接賦值使用 SET ,可以賦值常量或者表示式,語法如下:

set last_month_start = DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH);

    也可以通過查詢將結果賦值給變數, 這要求查詢返回的結果必須只有一行。具體語法如下:

select col_name [,....]  INTO var_name [,....] table_expr

    通過查詢結果賦值給 last_month_start :

select DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH) INTO last_month_start from DUAL

流程控制

    可以使用 IF, CASE, LOOP, LEAVE, ITERATE, REPEAT,WHILE 語句進行流程控制。

    1:IF 語句

    if 實現條件判斷,滿足不同的條件執行不同的語句列表,具體語法:

IF search_condition THEN statement_list
    [ELSEIF search_condition THEN statement_list] ....
    [ELSE statement_list]
END IF 

2:CASE 語句

CASE case_value 
    WHEN when_value THEN statement_list 
    [ WHEN when_value THEN statement_list ]...
    [ ELSE  statement_list]
END CASE 

或者

CASE 
    WHEN search_condition THEN  statement_list
    [ WHEN search_condition THEN  statement_list ]...
    [ ELSE  statement_list]
END CASE

    比如 ,過程 proc_case 判斷輸入的引數值是 0 還是 1 或是 other:

CREATE PROCEDURE `proc_case`(IN type int)
BEGIN
   #Routine body goes here...
   DECLARE c varchar(500);
   CASE type
   WHEN 0 THEN
       set c = 'param is 0';
   WHEN 1 THEN
       set c = 'param is 1';
   ELSE
       set c = 'param is others, not 0 or 1';
   END CASE;
   select c;
END

3:LOOP 語句

    LOOP 語句實現簡單的迴圈,退出迴圈的條件需要使用其它的語句定義,通常可以使用 LEAVE 語句實現,具體語法:

[begin_label:] LOOP
    statement_list
END LOOP [end_label]

    如果不在 statement_list 中增加退出迴圈的語句,那麼 LOOP 語句可以用來實現簡單的死迴圈。

4:LEAVE 語句

    用來從標註的流程構造中退出,通常和 BEGIN...END 或迴圈一起使用。

    下面是一個 LOOP 和 LEAVE 一起使用的一個簡單的例子,迴圈一百次向 test 表中插入記錄,當插入 100 條記錄後退出迴圈。

CRATE PROCEDURE proc_Loop(OUT sum INT)
    BEGIN
        set @x = 0;
        ins:LOOP
            set @x = @x +1;
            IF  @x = 100 THEN 
                LEAVE  ins;
            END IF;
            set sum = sum + @x;
        END LOOP;
        select sum;
    END

  執行結果

mysql> call proc_Loop(@sum);
+------+
| sum  |
+------+
| 4950 |
+------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)