MySQL存儲過程入門教程
存儲過程介紹
存儲過程是一組為了完成特定功能的SQL語句集,經編譯后存儲在數據庫中。用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。存儲過程可由應用程序通過一個調用來執行,而且允許用戶聲明變量 。同時,存儲過程可以接收和輸出參數、返回執行存儲過程的狀態值,也可以嵌套調用。
存儲過程和函數的區別
函數往往作為公式使用,存儲過程作為完成某種功能使用。
函數分為表值函數跟標量函數。表值函數是經過一些sql語句方法最后返回一張表,標量函數是經過一些sql語句方法最后返回一個值。
函數可以在select語句中直接使用,而過程不能。
存儲過程的優點
作為存儲過程,有以下這些優點:
(1) 減少網絡通信量。調用一個行數不多的存儲過程與直接調用SQL語句的網絡通信量可能不會有很大的差別,可是如果存儲過程包含上百行SQL語句,那么其性能絕對比一條一條的調用SQL語句要高得多。
(2) 執行速度更快。存儲過程創建的時候,數據庫已經對其進行了一次解析和優化。其次,存儲過程一旦執行,在內存中就會保留一份這個存儲過程,這樣下次再執行同樣的存儲過程時,可以從內存中直接中讀取。
(3) 更強的安全性。存儲過程是通過向用戶授予權限(而不是基於表),它們可以提供對特定數據的訪問,提高代碼安全,比如防止 SQL注入。
(4) 業務邏輯可以封裝存儲過程中,這樣不僅容易維護,而且執行效率也高
當然存儲過程也有一些缺點,比如:
(1) 可移植性方面:當從一種數據庫遷移到另外一種數據庫時,不少的存儲過程的編寫要進行部分修改。
(2) 存儲過程需要花費一定的學習時間去學習,比如學習其語法等。
在MySQL中,推薦使用MySQL Query Browswer(http://dev.mysql.com/doc/query-browser/en/)這個工具去進行存儲過程的開發和管理。下面分步驟來學習MYSQL中的存儲過程。
1、定義存儲過程的結束符
在存儲過程中,通常要輸入很多SQL語句,而SQL語句中每個語句以分號來結束,因此要告訴存儲過程,什么位置是意味着整個存儲過程結束,所以我們在編寫存儲過程前,先定義分隔符,我們這里定義//為分隔符,我們使用DELIMITER //這樣的語法,就可以定義結束符了,當然你可以自己定義其他喜歡的符號。
2、如何創建存儲過程
下面先看下一個簡單的例子,代碼如下:
復制代碼
DELIMITER //
CREATEPROCEDURE `p2` ()
LANGUAGE SQL
DETERMINISTIC
SQL SECURITY DEFINER
COMMENT 'A procedure'
BEGIN
SELECT'Hello World !';
END//
復制代碼
下面講解下存儲過程的組成部分:
1) 首先在定義好終結符后,使用CREATE PROCEDURE+存儲過程名的方法創建存儲過程,LANGUAGE選項指定了使用的語言,這里默認是使用SQL。
2) DETERMINISTIC關鍵詞的作用是,當確定每次的存儲過程的輸入和輸出都是相同的內容時,可以使用該關鍵詞,否則默認為NOT DETERMINISTIC。
3) SQL SECURITY關鍵詞,是表示調用時檢查用戶的權限。當值為INVOKER時,表示是用戶調用該存儲過程時檢查,默認為DEFINER,即創建存儲過程時檢查。
4) COMMENT部分是存儲過程的注釋說明部分。
5) 在BEGIN END部分中,是存儲過程的主體部分。
3、調用存儲過程的方法
調用存儲過程的方法很簡單,只需要使用call命令即可,后面跟要調用存儲過程的名稱及輸入的變量列表,比如:
CALL stored_procedure_name (param1, param2, ....)
CALL procedure1(10 , 'string parameter' , @parameter_var);
4、修改和刪除存儲過程
可以用ALTER的語法去修改存儲過程的主要特征和參數,要修改其存儲過程的主體部分的話,必須要先刪除然后再重建。比如下面修改存儲過程num_from_employee的定義。將讀寫權限改為MODIFIES SQL DATA,並指明調用者可以執行。代碼執行如下:
ALTER PROCEDURE num_from_employee
MODIFIES SQL DATA SQL SECURITY INVOKER ;
而刪除存儲過程的語法為使用DROP關鍵詞即可。如下
DROP PROCEDURE IF EXISTS p2;
5、存儲過程的參數
下面來學習下存儲過程中的參數,先看下存儲過程中的參數形式,如下:
CREATE PROCEDURE proc1 () 這個存儲過程中是空的參數列表
CREATE PROCEDURE proc1 (IN varname DATA-TYPE) 這個存儲過程中有一個輸出參數,名稱為varname,后面是跟數據類型DATA-TYPE,IN參數是默認的,因此可以省略不寫
CREATE PROCEDURE proc1 (OUT varname DATA-TYPE) 這個存儲過程中varname為輸出參數
CREATE PROCEDURE proc1 (INOUT varname DATA-TYPE) 這個存儲過程中,varname既是輸入參數也是輸出參數
下面具體看個例子,首先是IN輸入參數的例子,如下:
DELIMITER //
CREATE PROCEDURE `proc_IN` (IN var1 INT)
BEGIN
SELECT var1 + 2 AS result;
END//
輸出OUT參數例子如下:
DELIMITER //
CREATE PROCEDURE `proc_OUT` (OUT var1 VARCHAR(100))
BEGIN
SET var1 = 'This is a test';
END //
IN-OUT的例子:
DELIMITER //
CREATE PROCEDURE `proc_INOUT` (OUT var1 INT)
BEGIN
SET var1 = var1 * 2;
END //
6、如何定義變量
下面講解下MySQL 5存儲過程中,如何定義變量。必須顯式地在存儲過程的一開始聲明變量,並指出它們的數據類型,一但聲明了變量后,就可以在存儲過程中使用,定義變量的語法如下:
DECLARE varname DATA-TYPE DEFAULT defaultvalue
舉例說明:
DECLARE a, b INT DEFAULT 5;
DECLARE str VARCHAR(50);
DECLARE today TIMESTAMP DEFAULT CURRENT_DATE;
DECLARE v1, v2, v3 TINYINT;
一旦定義好變量,就可以在存儲過程中對其進行賦初值,並進行各類相關的操作,比如:
復制代碼
DELIMITER //
CREATE PROCEDURE `var_proc` (IN paramstr VARCHAR(20))
BEGIN
DECLARE a, b INT DEFAULT 5;
DECLARE str VARCHAR(50);
DECLARE today TIMESTAMP DEFAULT CURRENT_DATE;
DECLARE v1, v2, v3 TINYINT;
INSERT INTO table1 VALUES (a);
SET str = 'I am a string';
SELECT CONCAT(str,paramstr), today FROM table2 WHERE b>=5;
END //
復制代碼
7、MYSQL存儲過程的語法結構
MYSQL存儲過程中支持IF,CASE,ITERATE,LEAVE LOOP,WHILE和REPEAT等語法結構和語句,在本文中,着重介紹IF,CASE和WHILE語法,因為它們使用的最為廣泛。
IF 語句
if語句使用的是if…then end if的語法結構,例子如下:
復制代碼
DELIMITER //
CREATE PROCEDURE `proc_IF` (IN param1 INT)
BEGIN
DECLARE variable1 INT;
SET variable1 = param1 + 1;
IF variable1 = 0 THEN
SELECT variable1;
END IF;
IF param1 = 0 THEN
SELECT 'Parameter value = 0';
ELSE
SELECT 'Parameter value <= 0';
END IF;
END //
復制代碼
CASE語句
當有很多IF語句時,就應該考慮使用CASE語句了,它是多分支選擇語句,有兩種寫法:
第一種寫法:
復制代碼
DELIMITER //
CREATE PROCEDURE `proc_CASE` (IN param1 INT)
BEGIN
DECLARE variable1 INT;
SET variable1 = param1 + 1;
CASE variable1
WHEN 0 THEN
INSERT INTO table1 VALUES (param1);
WHEN 1 THEN
INSERT INTO table1 VALUES (variable1);
ELSE
INSERT INTO table1 VALUES (99);
END CASE;
END //
復制代碼
另外一種寫法:
復制代碼
DELIMITER //
CREATE PROCEDURE `proc_CASE` (IN param1 INT)
BEGIN
DECLARE variable1 INT;
SET variable1 = param1 + 1;
CASE
WHEN variable1 = 0 THEN
INSERT INTO table1 VALUES (param1);
WHEN variable1 = 1 THEN
INSERT INTO table1 VALUES (variable1);
ELSE
INSERT INTO table1 VALUES (99);
END CASE;
END //
復制代碼
WHILE語句
WHILE語句跟普通編程語言中的while語句差不多,例子如下:
復制代碼
DELIMITER //
CREATE PROCEDURE `proc_WHILE` (IN param1 INT)
BEGIN
DECLARE variable1, variable2 INT;
SET variable1 = 0;
WHILE variable1
INSERT INTO table1 VALUES (param1);
SELECT COUNT(*) INTO variable2 FROM table1;
SET variable1 = variable1 + 1;
END WHILE;
END //
復制代碼
8、MYSQL存儲過程中的游標
MySQL中的游標是一個十分重要的概念。游標提供了一種對從表中檢索出的數據進行操作的靈活手段,就本質而言,游標實際上是一種能從包括多條數據記錄的結果集中每次提取一條記錄的機制。MySQL中的游標的語法如下:
DECLARE cursor-name CURSOR FOR SELECT ...; /* 聲明一個游標,名稱為cursor-name,並用CURSOR FOR SELECT*/
DECLARE CONTINUE HANDLER FOR NOT FOUND /*指定當遍歷完結果集后,游標如何繼續處理*/
OPEN cursor-name; /*打開游標 */
FETCH cursor-name INTO variable [, variable]; /* 將變量賦值給游標*/
CLOSE cursor-name; /*使用后關閉游標*/
一個具體的例子如下:
復制代碼
DELIMITER //
CREATE PROCEDURE `proc_CURSOR` (OUT param1 INT)
BEGIN
DECLARE a, b, c INT;
DECLARE cur1 CURSOR FOR SELECT col1 FROM table1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET b = 1;
OPEN cur1;
SET b = 0;
SET c = 0;
WHILE b = 0 DO
FETCH cur1 INTO a;
IF b = 0 THEN
SET c = c + a;
END IF;
END WHILE;
CLOSE cur1;
SET param1 = c;
END //
復制代碼
其中,DECLARE cur1 CURSOR FOR SELECT col1 FROM table1;
表示將從table1表中選取col1列的內容放到游標curl中,即每次游標遍歷的結果都放在curl中,要注意游標只能向前遍歷,而不能向后,並且注意,游標不能更新,最后關閉游標。
代碼示例:
CREATE PROCEDURE `PRO_OPERATION`()
BEGIN
DECLARE new_followers VARCHAR (20);
DECLARE cancel VARCHAR (20);
DECLARE total VARCHAR (20);
DECLARE invoice_total VARCHAR (20);
DECLARE data_time VARCHAR (20);
DECLARE visitor_count INT;
DECLARE error_msg VARCHAR(100);
/**定義異常SQLEXCEPTION **/
DECLARE
EXIT HANDLER FOR SQLEXCEPTION,
NOT FOUND
BEGIN
SET error_msg = 'SQLEXCEPTION';
INSERT INTO log_info (
pk_log,
pro_name,
log_msg,
create_time
)
VALUES
(
UUID_SHORT(),
'PRO_OPERATION',
error_msg,
SYSDATE()
);
END;
-- 查詢新增關注人數、取關人數、累計關注人數
SELECT
wxc.new,
wxc.cancel ,
wxc.total,
wxc.create_time INTO new_followers,cancel,total,data_time
FROM
wx_concern wxc
WHERE
wxc.mp_type = '1'
AND
DATE_FORMAT(wxc.create_time, '%Y%m%d') = DATE_FORMAT(
date_sub(curdate(), INTERVAL 1 DAY),
'%Y%m%d'
);
/** 查詢發票總數量 **/
SELECT yesterday.yesterday_total - vorgestern.total INTO invoice_total
FROM
(
SELECT
wn.jd_num + wn.gm_num + wn.mdl_num + wn.nojd_num as yesterday_total
FROM
wx_invoice_num wn
WHERE
wn.mp_type = '1'
AND DATE_FORMAT(wn.create_time, '%Y%m%d') = DATE_FORMAT(
date_sub(curdate(), INTERVAL 1 DAY),
'%Y%m%d')
)
yesterday ,
(
SELECT
wn.jd_num + wn.gm_num + wn.mdl_num + wn.nojd_num as total
FROM
wx_invoice_num wn
WHERE
wn.mp_type = '1'
AND DATE_FORMAT(wn.create_time, '%Y%m%d') = DATE_FORMAT(
date_sub(curdate(), INTERVAL 2 DAY),
'%Y%m%d')
)vorgestern WHERE
DATE_FORMAT(yesterday.create_time, '%Y%m%d') - DATE_FORMAT(vorgestern.create_time, '%Y%m%d') = '1' ;
/**
查詢uv總數
**/
SELECT
MAX(et.visitor_count) INTO visitor_count
FROM
ele_baidu_toppage et
WHERE
et.url= "https://wdfp.5ifapiao.com/my-invoice/html/public/footer.html"
AND
DATE_FORMAT(et.count_time, '%Y%m%d') = DATE_FORMAT(
date_sub(curdate(), INTERVAL 1 DAY),
'%Y%m%d'
);
/** 插入今日數據 **/
INSERT INTO wx_operational (
pk_operational,
new_followers,
cancel,
total,
invoice_total,
visitor_count,
data_time,
create_time
)
VALUE
(
UUID_SHORT(),
new_followers,
cancel,
total,
invoice_total,
visitor_count,
data_time,
NOW()
);
COMMIT;
END
循環游標存儲過程
注意事項:
這可能是Mysql的一個BUG,在游標范圍內字段名稱應該不能和變量名稱重復,而Mysql是不區分大小寫的,所以即使字段名稱是大寫,變量名稱是小寫,也會被認為是同一個字符串。
游標使用會多執行一個 需要提前定義一個判斷
如果值為空的時候 將標識定義為1
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
--定義名稱
CREATE PROCEDURE `PRO_INVOICE_TYPE`()
BEGIN
DECLARE invo_data_time VARCHAR (20);
DECLARE invo_kpxmsl int (12);
DECLARE invo_fpzl VARCHAR (5);
DECLARE error_msg VARCHAR(100);
DECLARE done INT DEFAULT 0; -- 自定義控制游標循環變量,默認0
/** 查詢今日發票數據**/
declare inv_num cursor for(
SELECT
create_time AS data_time,
kpxmsl,
fpzl
FROM
`myinvoice-new`.invoice_info
WHERE
DATE_FORMAT(create_time, '%Y%m%d') = DATE_FORMAT(
date_sub(curdate(), INTERVAL 1 DAY),
'%Y%m%d'
) GROUP BY fpzl
);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
/** 插入今日數據 **/
OPEN inv_num; -- 打開游標
myLoop: LOOP -- 開始循環體,myLoop為自定義循環名,結束循環時用到
FETCH inv_num INTO invo_data_time,invo_kpxmsl,invo_fpzl;
IF done THEN -- 判斷是否繼續循環
LEAVE myLoop; -- 結束循環
END IF;
INSERT INTO `report`.`wx_invoice_type`
(
pk_invoice_type,
kpxmsl,
fpzl,
data_time,
create_time
)
VALUES
(
UUID(),
invo_kpxmsl,
invo_fpzl,
invo_data_time,
NOW()
);
COMMIT; -- 提交事務
END LOOP myLoop; -- 結束自定義循環體
CLOSE inv_num; -- 關閉游標
END
注意:觸發器編寫完成后,需要查看定時器是否執行
觸發器 :
DROP EVENT IF EXISTS EVENT_PRO_INVOICE_TYPE;
CREATE EVENT EVENT_PRO_INVOICE_TYPE
--設置時間 多久執行一次 從什么時間開始
ON SCHEDULE EVERY 3 minute STARTS '2018-06-14 19:40:00'
-- 從什么時間開始 STARTS now()
DO CALL PRO_INVOICE_TYPE();-- 設置定時啟動哪個存儲過程 call后是存儲過程名稱
EVERY 后面跟
可以選 1 second秒,3 minute分,5 hour時,9 day天,1 month月,1 quarter(季度),1 year 年
觸發器不執行/不允許執行
set global event_scheduler = on; 開啟 OFF 關閉
show variables like 'event_scheduler'; 查看定時器是否允許執行