­

MySQL存儲過程入門了解

0.環境說明:

mysql版本:5.7

1.使用說明

​ 存儲過程是資料庫的一個重要的對象,可以封裝SQL語句集,可以用來完成一些較複雜的業務邏輯,並且可以入參出參(類似於java中的方法的書寫)。

​ 創建時會預先編譯後保存,用戶後續的調用都不需要再次編譯。

// 把editUser類比成一個存儲過程
public void editUser(User user,String username){
    String a = "nihao";
    user.setUsername(username);
}
main(){
    User user = new User();
	editUser(user,"張三");
    user.getUseranme();   //java基礎還記得不
}

​ 大家可能會思考,用sql處理業務邏輯還要重新學,我用java來處理邏輯(比如循環判斷、循環查詢等)不行嗎?那麼,為什麼還要用存儲過程處理業務邏輯呢?

優點:
	在生產環境下,可以通過直接修改存儲過程的方式修改業務邏輯(或bug),而不用重啟伺服器。
	執行速度快,存儲過程經過編譯之後會比單獨一條一條執行要快。
	減少網路傳輸流量。
	方便優化。
缺點:
	過程化編程,複雜業務處理的維護成本高。
	調試不便
	不同資料庫之間可移植性差。-- 不同資料庫語法不一致!

2.準備:

資料庫參閱資料中的sql腳本;

delimiter $$ --聲明結束符

3.語法

-- 官方參考網址
//dev.mysql.com/doc/refman/5.6/en/sql-statements.html
//dev.mysql.com/doc/refman/5.6/en/sql-compound-statements.html

3.0 語法結構

CREATE
    [DEFINER = user]
	PROCEDURE sp_name ([proc_parameter[,...]])
    [characteristic ...] routine_body
    
-- proc_parameter參數部分,可以如下書寫:
	[ IN | OUT | INOUT ] param_name type
	-- type類型可以是MySQL支援的所有類型
	
-- routine_body(程式體)部分,可以書寫合法的SQL語句 BEGIN ... END

簡單演示:

-- 聲明結束符。因為MySQL默認使用『;』作為結束符,而在存儲過程中,會使用『;』作為一段語句的結束,導致『;』使用衝突
delimiter $$

CREATE PROCEDURE hello_procedure ()
BEGIN
	SELECT 'hello procedure';
END $$

call hello_procedure();

3.1 變數及賦值

類比一下java中的局部變數和成員變數的聲明和使用
局部變數:

用戶自定義,在begin/end塊中有效

語法:
聲明變數 declare var_name type [default var_value];
舉例:declare nickname varchar(32);
-- set賦值
create procedure sp_var01()
begin
	declare nickname varchar(32) default 'unkown';
	set nickname = 'ZS';
	-- set nickname := 'SF';
	select nickname;
end$$
-- into賦值
delimiter $$
create procedure sp_var_into()
begin
	declare emp_name varchar(32) default 'unkown' ;
	declare emp_no int default 0;
	select e.empno,e.ename into emp_no,emp_name from emp e where e.empno = 7839;
	select emp_no,emp_name;
end$$
用戶變數:

用戶自定義,當前會話(連接)有效。類比java的成員變數

語法: 
@var_name
不需要提前聲明,使用即聲明
-- 賦值
delimiter $$
create procedure sp_var02()
begin
	set @nickname = 'ZS';
	-- set nickname := 'SF';
end$$
call sp_var02() $$
select @nickname$$  --可以看到結果
會話變數:

由系統提供,當前會話(連接)有效

語法:
@@session.var_name
show session variables; -- 查看會話變數
select @@session.unique_checks; -- 查看某會話變數
set @@session.unique_checks = 0; --修改會話變數
全局變數:

由系統提供,整個mysql伺服器有效

語法:
@@global.var_name

舉例

-- 查看全局變數中變數名有char的記錄
show global variables like '%char%'; 

-- 查看全局變數character_set_client的值
select @@global.character_set_client; 

3.2 入參出參

-- 語法
in | out | inout param_name type

舉例

-- IN類型演示
delimiter $$
create procedure sp_param01(in age int)
begin
	set @user_age = age;
end$$
call sp_param01(10) $$
select @user_age$$
-- OUT類型,只負責輸出!
-- 需求:輸出傳入的地址字元串對應的部門編號。
delimiter $$

create procedure sp_param02(in loc varchar(64),out dept_no int(11))
begin
	select d.deptno into dept_no from dept d where d.loc = loc;
	--此處強調,要麼表起別名,要麼入參名不與欄位名一致
end$$
delimiter ;

--測試
set @dept_no = 100;
call sp_param01('DALLAS',@dept_no);
select @dept_no;
-- INOUT類型 
delimiter $$
create procedure sp_param03(inout name varchar)
begin
	set name = concat('hello' ,name);
end$$
delimiter ;

set @user_name = '小明';
call sp_param03(@user_name);
select @user_name;

3.3 流程式控制制-判斷

官網說明
//dev.mysql.com/doc/refman/5.6/en/flow-control-statements.html
if
-- 語法
IF search_condition THEN statement_list
    [ELSEIF search_condition THEN statement_list] ...
    [ELSE statement_list]
END IF

舉例:

-- 前置知識點:timestampdiff(unit,exp1,exp2) 取差值exp2-exp1差值,單位是unit
select timestampdiff(year,e.hiredate,now()) from emp e where e.empno = '7499';
-- 需求:入職年限<=38是新手 >38並且<=40老員工 >40元老
delimiter $$
create procedure sp_hire_if()
begin
	declare result varchar(32);
	if timestampdiff(year,'2001-01-01',now()) > 40 
		then set result = '元老';
	elseif timestampdiff(year,'2001-01-01',now()) > 38
		then set result = '老員工';
	else 
		set result = '新手';
	end if;
	select result;
end$$
delimiter ;
case

此語法是不僅可以用在存儲過程,查詢語句也可以用!

-- 語法一(類比java的switch):
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

舉例:

-- 需求:入職年限年齡<=38是新手 >38並 <=40老員工 >40元老
delimiter $$
create procedure sp_hire_case()
begin
	declare result varchar(32);
	declare message varchar(64);
	case
    when timestampdiff(year,'2001-01-01',now()) > 40 
		then 
			set result = '元老';
			set message = '老爺爺';
	when timestampdiff(year,'2001-01-01',now()) > 38
		then 
			set result = '老員工';
			set message = '油膩中年人';
	else 
		set result = '新手';
		set message = '萌新';
	end case;
	select result;
end$$
delimiter ;

3.4 流程式控制制-循環

loop
-- 語法
[begin_label:] LOOP
    statement_list
END LOOP [end_label]

舉例

需要說明,loop是死循環,需要手動退出循環,我們可以使用leave來退出。

可以把leave看成我們java中的break;與之對應的,就有iterate(繼續循環)——類比java的continue

--需求:循環列印1到10
-- leave控制循環的退出
delimiter $$
create procedure sp_flow_loop()
begin
	declare c_index int default 1;
	declare result_str  varchar(256) default '1';
	cnt:loop
	
		if c_index >= 10
		then leave cnt;
		end if;

		set c_index = c_index + 1;
		set result_str = concat(result_str,',',c_index);
		
	end loop cnt;
	
	select result_str;
end$$

-- iterate + leave控制循環
delimiter $$
create procedure sp_flow_loop02()
begin
	declare c_index int default 1;
	declare result_str  varchar(256) default '1';
	cnt:loop

		set c_index = c_index + 1;
		set result_str = concat(result_str,',',c_index);
		if c_index < 10 then 
			iterate cnt; 
		end if;
		-- 下面這句話能否執行到?什麼時候執行到? 當c_index < 10為false時執行
		leave cnt;
		
	end loop cnt;
	select result_str;
	
end$$
repeat

1586956047152

[begin_label:] REPEAT
    statement_list
UNTIL search_condition	-- 直到…為止,才退出循環
END REPEAT [end_label]
-- 需求:循環列印1到10
delimiter $$
create procedure sp_flow_repeat()
begin
	declare c_index int default 1;
	-- 收集結果字元串
	declare result_str varchar(256) default '1';
	count_lab:repeat
		set c_index = c_index + 1;
		set result_str = concat(result_str,',',c_index);
		until c_index >= 10
	end repeat count_lab;
	select result_str;
end$$
while
類比java的while(){}
[begin_label:] WHILE search_condition DO
    statement_list
END WHILE [end_label]
-- 需求:循環列印1到10
delimiter $$
create procedure sp_flow_while()
begin
	declare c_index int default 1;
	-- 收集結果字元串
	declare result_str varchar(256) default '1';
	while c_index < 10 do
		set c_index = c_index + 1;
		set result_str = concat(result_str,',',c_index);
	end while;
	select result_str;
end$$

3.5 流程式控制制-退出、繼續循環

leave
類比java的breake
-- 退出 LEAVE can be used within BEGIN ... END or loop constructs (LOOP, REPEAT, WHILE).
LEAVE label
iterate
類比java的continue
-- 繼續循環 ITERATE can appear only within LOOP, REPEAT, and WHILE statements
ITERATE label

3.6 游標

用游標得到某一個結果集,逐行處理數據。

類比jdbc的ResultSet
-- 聲明語法
DECLARE cursor_name CURSOR FOR select_statement
-- 打開語法
OPEN cursor_name
-- 取值語法
FETCH cursor_name INTO var_name [, var_name] ...
-- 關閉語法
CLOSE cursor_name
-- 需求:按照部門名稱查詢員工,通過select查看員工的編號、姓名、薪資。(注意,此處僅僅演示游標用法)
delimiter $$
create procedure sp_create_table02(in dept_name varchar(32))
begin
	declare e_no int;
	declare e_name varchar(32);
	declare e_sal decimal(7,2);
	
	declare lp_flag boolean default true;
	
	declare emp_cursor cursor for 
		select e.empno,e.ename,e.sal
		from emp e,dept d
		where e.deptno = d.deptno and d.dname = dept_name;
		
	-- handler 句柄
	declare continue handler for NOT FOUND set lp_flag = false;
		
	open emp_cursor;
	
	emp_loop:loop
		fetch emp_cursor into e_no,e_name,e_sal;
		
		if lp_flag then
			select e_no,e_name,e_sal;
		else
			leave emp_loop;
		end if;
		
	end loop emp_loop;
	set @end_falg = 'exit_flag';
	close emp_cursor;
end$$

call sp_create_table02('RESEARCH');
DROP PROCEDURE if EXISTS sp_update_create_time;
delimiter $$
create procedure sp_update_create_time()
BEGIN
declare value_id bigint(20);
declare lp_flag boolean default true;
declare update_create_time_cursor cursor for
	SELECT
		commodity_discnt_item_value_id 
	FROM
		td_co_discnt_item_value 
	WHERE
		create_time IS NULL;
declare continue handler for NOT FOUND set lp_flag = false;
open update_create_time_cursor;
update_create_time_loop:loop
fetch update_create_time_cursor into value_id;
if lp_flag then 
UPDATE td_co_discnt_item_value SET create_time =now() WHERE commodity_discnt_item_value_id =value_id;
else leave update_create_time_loop;
end if;
end loop update_create_time_loop;
set @end_falg = 'exit_flag';
close update_create_time_cursor;
end$$
call sp_update_create_time();

說明:以上存儲過程目的為更新表中create_time為空的數據設置為當前時間

特別注意:

在語法中,變數聲明、游標聲明、handler聲明是必須按照先後順序書寫的,否則創建存儲過程出錯。

3.7 存儲過程中的handler

DECLARE handler_action HANDLER
    FOR condition_value [, condition_value] ...
    statement

handler_action: {
    CONTINUE
  | EXIT
  | UNDO
}

condition_value: {
    mysql_error_code
  | SQLSTATE [VALUE] sqlstate_value
  | condition_name
  | SQLWARNING
  | NOT FOUND
  | SQLEXCEPTION
}


CONTINUE: Execution of the current program continues.
EXIT: Execution terminates for the BEGIN ... END compound statement in which the handler is declared. This is true even if the condition occurs in an inner block.


SQLWARNING: Shorthand for the class of SQLSTATE values that begin with '01'.
NOT FOUND: Shorthand for the class of SQLSTATE values that begin with '02'.
SQLEXCEPTION: Shorthand for the class of SQLSTATE values that do not begin with '00', '01', or '02'.
-- 各種寫法:
	DECLARE exit HANDLER FOR SQLSTATE '42S01' set @res_table = 'EXISTS';
	DECLARE continue HANDLER FOR 1050 set @res_table = 'EXISTS';
	DECLARE continue HANDLER FOR not found set @res_table = 'EXISTS';

4.練習

——大家注意,存儲過程的業務過程在java程式碼中一般也可以實現,我們下面的需求是為了練習存儲過程

4.1 利用存儲過程更新數據

為某部門(需指定)的人員漲薪100;如果是公司總裁,則不漲薪。
delimiter //
create procedure high_sal(in dept_name varchar(32))
begin
	declare e_no int;
	declare e_name varchar(32);
	declare e_sal decimal(7,2);
	
	declare lp_flag boolean default true;
	
	declare emp_cursor cursor for 
		select e.empno,e.ename,e.sal
		from emp e,dept d
		where e.deptno = d.deptno and d.dname = dept_name;
		
	-- handler 句柄
	declare continue handler for NOT FOUND set lp_flag = false;
		
	open emp_cursor;
	
	emp_loop:loop
		fetch emp_cursor into e_no,e_name,e_sal;
		
		if lp_flag then
			if e_name = 'king' then 
				iterate emp_loop;
			else 
				update emp e set e.sal = e.sal + 100 where e.empno = e_no;
			end if;
		else
			leave emp_loop;
		end if;
		
	end loop emp_loop;
	set @end_falg = 'exit_flag';
	close emp_cursor;
end //


call high_sal('ACCOUNTING');

結語

歡迎關注微信公眾號『碼仔zonE』,專註於分享Java、雲計算相關內容,包括SpringBoot、SpringCloud、微服務、Docker、Kubernetes、Python等領域相關技術乾貨,期待與您相遇!

Tags: