SQL必知必會(huì)(存儲(chǔ)過(guò)程)

一、什么是存儲(chǔ)過(guò)程,如何創(chuàng)建一個(gè)存儲(chǔ)過(guò)程

存儲(chǔ)過(guò)程的英文是 Stored Procedure。它的思想很簡(jiǎn)單,就是 SQL 語(yǔ)句的封裝。一旦存儲(chǔ)過(guò)程被創(chuàng)建出來(lái),使用它就像使用函數(shù)一樣簡(jiǎn)單,我們直接通過(guò)調(diào)用存儲(chǔ)過(guò)程名即可。

定義一個(gè)存儲(chǔ)過(guò)程:

CREATE PROCEDURE 存儲(chǔ)過(guò)程名稱(chēng) ([參數(shù)列表])
BEGIN
    需要執(zhí)行的語(yǔ)句
END      

在這里,我們使用 CREATE PROCEDURE 創(chuàng)建一個(gè)存儲(chǔ)過(guò)程,后面是存儲(chǔ)過(guò)程的名稱(chēng),以及過(guò)程所帶的參數(shù),可以包括輸入?yún)?shù)和輸出參數(shù)。最后由 BEGIN 和 END 來(lái)定義我們所要執(zhí)行的語(yǔ)句塊。

和視圖一樣,我們可以刪除已經(jīng)創(chuàng)建的存儲(chǔ)過(guò)程,使用的是 DROP PROCEDURE。如果要更新存儲(chǔ)過(guò)程,我們需要使用 ALTER PROCEDURE。

講完了如何創(chuàng)建,更新和刪除一個(gè)存儲(chǔ)過(guò)程,下面我們來(lái)看下如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的存儲(chǔ)過(guò)程。比如我想做一個(gè)累加運(yùn)算,計(jì)算 1+2+…+n 等于多少,我們可以通過(guò)參數(shù) n 來(lái)表示想要累加的個(gè)數(shù),那么如何用存儲(chǔ)過(guò)程實(shí)現(xiàn)這一目的呢?這里我做一個(gè) add_num 的存儲(chǔ)過(guò)程,具體的代碼如下:

CREATE PROCEDURE `add_num`(IN n INT)
BEGIN
       DECLARE i INT;
       DECLARE sum INT;
       
       SET i = 1;
       SET sum = 0;
       WHILE i <= n DO
              SET sum = sum + i;
              SET i = i +1;
       END WHILE;
       SELECT sum;
END

當(dāng)我們需要再次使用這個(gè)存儲(chǔ)過(guò)程的時(shí)候,直接使用 CALL add_num(50);即可。這里我傳入的參數(shù)為 50,也就是統(tǒng)計(jì) 1+2+…+50 的積累之和,查詢(xún)結(jié)果為:

CALL add_num(50);

這就是一個(gè)簡(jiǎn)單的存儲(chǔ)過(guò)程,除了理解 1+2+…+n 的實(shí)現(xiàn)過(guò)程,還有兩點(diǎn)你需要理解,一個(gè)是 DELIMITER 定義語(yǔ)句的結(jié)束符,另一個(gè)是存儲(chǔ)過(guò)程的三種參數(shù)類(lèi)型。

二、DELIMITER 的作用

如果你使用 Navicat 這個(gè)工具來(lái)管理 MySQL 執(zhí)行存儲(chǔ)過(guò)程,那么直接執(zhí)行上面這段代碼就可以了。

如果用的是 MySQL,你還需要用 DELIMITER 來(lái)臨時(shí)定義新的結(jié)束符。因?yàn)槟J(rèn)情況下 SQL 采用(;)作為結(jié)束符,這樣當(dāng)存儲(chǔ)過(guò)程中的每一句 SQL 結(jié)束之后,采用(;)作為結(jié)束符,就相當(dāng)于告訴 SQL 可以執(zhí)行這一句了。

但是存儲(chǔ)過(guò)程是一個(gè)整體,我們不希望 SQL 逐條執(zhí)行,而是采用存儲(chǔ)過(guò)程整段執(zhí)行的方式,因此我們就需要臨時(shí)定義新的 DELIMITER,新的結(jié)束符可以用(//)或者($$)。

如果你用的是 MySQL,那么上面這段代碼,應(yīng)該寫(xiě)成下面這樣:

DELIMITER //
CREATE PROCEDURE `add_num`(IN n INT)
BEGIN
       DECLARE i INT;
       DECLARE sum INT;
       
       SET i = 1;
       SET sum = 0;
       WHILE i <= n DO
              SET sum = sum + i;
              SET i = i +1;
       END WHILE;
       SELECT sum;
END //
DELIMITER ;

首先我用(//)作為結(jié)束符,又在整個(gè)存儲(chǔ)過(guò)程結(jié)束后采用了(//)作為結(jié)束符號(hào),告訴 SQL 可以執(zhí)行了,然后再將結(jié)束符還原成默認(rèn)的(;)。

需要注意的是,如果你用的是 Navicat 工具,那么在編寫(xiě)存儲(chǔ)過(guò)程的時(shí)候,Navicat 會(huì)自動(dòng)設(shè)置 DELIMITER 為其他符號(hào),我們不需要再進(jìn)行 DELIMITER 的操作。

三、存儲(chǔ)過(guò)程的 3 種參數(shù)類(lèi)型

我們?cè)賮?lái)看下存儲(chǔ)過(guò)程的 3 種參數(shù)類(lèi)型。在剛才的存儲(chǔ)過(guò)程中,我們使用了 IN 類(lèi)型的參數(shù),另外還有 OUT 類(lèi)型和 INOUT 類(lèi)型,作用如下:

IN 和 OUT 的結(jié)合,既用于存儲(chǔ)過(guò)程的傳入?yún)?shù),同時(shí)又可以把計(jì)算結(jié)果放到參數(shù)中,調(diào)用者可以得到返回值。

你能看到,IN 參數(shù)必須在調(diào)用存儲(chǔ)過(guò)程時(shí)指定,而在存儲(chǔ)過(guò)程中修改該參數(shù)的值不能被返回。而 OUT 參數(shù)和 INOUT 參數(shù)可以在存儲(chǔ)過(guò)程中被改變,并可返回。

舉個(gè)例子,這里會(huì)用到我們之前講過(guò)的王者榮耀的英雄數(shù)據(jù)表 heros。假設(shè)我想創(chuàng)建一個(gè)存儲(chǔ)類(lèi)型 get_hero_scores,用來(lái)查詢(xún)某一類(lèi)型英雄中的最大的最大生命值,最小的最大魔法值,以及平均最大攻擊值,那么該怎么寫(xiě)呢?

CREATE PROCEDURE `get_hero_scores`(
       OUT max_max_hp FLOAT,
       OUT min_max_mp FLOAT,
       OUT avg_max_attack FLOAT,  
       s VARCHAR(255)
       )
BEGIN
       SELECT MAX(hp_max), MIN(mp_max), AVG(attack_max) FROM heros WHERE role_main = s INTO max_max_hp, min_max_mp, avg_max_attack;
END

你能看到我定義了 4 個(gè)參數(shù)類(lèi)型,其中 3 個(gè)為 OUT 類(lèi)型,分別為 max_max_hp、min_max_mp 和 avg_max_attack,另一個(gè)參數(shù) s 為 IN 類(lèi)型。

這里我們從 heros 數(shù)據(jù)表中篩選主要英雄定位為 s 的英雄數(shù)據(jù),即篩選條件為 role_main=s,提取這些數(shù)據(jù)中的最大的最大生命值,最小的最大魔法值,以及平均最大攻擊值,分別賦值給變量 max_max_hp、min_max_mp 和 avg_max_attack。

然后我們就可以調(diào)用存儲(chǔ)過(guò)程,使用下面這段代碼即可:

CALL get_hero_scores(@max_max_hp, @min_max_mp, @avg_max_attack, '戰(zhàn)士');
SELECT @max_max_hp, @min_max_mp, @avg_max_attack;

四、流控制語(yǔ)句

流控制語(yǔ)句是用來(lái)做流程控制的,我剛才講了兩個(gè)簡(jiǎn)單的存儲(chǔ)過(guò)程的例子,一個(gè)是 1+2+…+n 的結(jié)果計(jì)算,一個(gè)是王者榮耀的數(shù)據(jù)查詢(xún),你能看到這兩個(gè)例子中,我用到了下面的流控制語(yǔ)句:

    1. BEGIN…END:BEGIN…END 中間包含了多個(gè)語(yǔ)句,每個(gè)語(yǔ)句都以(;)號(hào)為結(jié)束符。
    1. DECLARE:DECLARE 用來(lái)聲明變量,使用的位置在于 BEGIN…END 語(yǔ)句中間,而且需要在其他語(yǔ)句使用之前進(jìn)行變量的聲明。
    1. SET:賦值語(yǔ)句,用于對(duì)變量進(jìn)行賦值。
    1. SELECT…INTO:把從數(shù)據(jù)表中查詢(xún)的結(jié)果存放到變量中,也就是為變量賦值。

除了上面這些用到的流控制語(yǔ)句以外,還有一些常用的流控制語(yǔ)句:

  • 1.IF…THEN…ENDIF:條件判斷語(yǔ)句,我們還可以在 IF…THEN…ENDIF 中使用 ELSE 和 ELSEIF 來(lái)進(jìn)行條件判斷。

  • 2.CASE:CASE 語(yǔ)句用于多條件的分支判斷,使用的語(yǔ)法是下面這樣的。

CASE 
    WHEN expression1 THEN ...
    WHEN expression2 THEN ...
    ...
    ELSE 
    --ELSE 語(yǔ)句可以加,也可以不加。加的話(huà)代表的所有條件都不滿(mǎn)足時(shí)采用的方式。
END
  • 3.LOOP、LEAVE 和 ITERATE:LOOP 是循環(huán)語(yǔ)句,使用 LEAVE 可以跳出循環(huán),使用 ITERATE 則可以進(jìn)入下一次循環(huán)。如果你有面向過(guò)程的編程語(yǔ)言的使用經(jīng)驗(yàn),你可以把 LEAVE 理解為 BREAK,把 ITERATE 理解為 CONTINUE。
  • 4.REPEAT…UNTIL…END REPEAT:這是一個(gè)循環(huán)語(yǔ)句,首先會(huì)執(zhí)行一次循環(huán),然后在 UNTIL 中進(jìn)行表達(dá)式的判斷,如果滿(mǎn)足條件就退出,即 END REPEAT;如果條件不滿(mǎn)足,則會(huì)就繼續(xù)執(zhí)行循環(huán),直到滿(mǎn)足退出條件為止。
  • 5.WHILE…DO…END WHILE:這也是循環(huán)語(yǔ)句,和 REPEAT 循環(huán)不同的是,這個(gè)語(yǔ)句需要先進(jìn)行條件判斷,如果滿(mǎn)足條件就進(jìn)行循環(huán),如果不滿(mǎn)足條件就退出循環(huán)。

我們之前說(shuō)過(guò) SQL 是聲明型語(yǔ)言,使用 SQL 就像在使用英語(yǔ),簡(jiǎn)單直接。今天講的存儲(chǔ)過(guò)程,尤其是在存儲(chǔ)過(guò)程中使用到的流控制語(yǔ)句,屬于過(guò)程性語(yǔ)言,類(lèi)似于 C++ 語(yǔ)言中函數(shù),這些語(yǔ)句可以幫我們解決復(fù)雜的業(yè)務(wù)邏輯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容