一、存儲過程
迄今為止,使用的大多數
SQL語句都是針對一個或多個表的單條語句。並非所有操作都這么簡單,經常會有一個完整的操作需要多條語句才能完成。例如,考慮以下的情形。
1、 為了處理訂單,需要核對以保證庫存中有相應的物品。
2、 如果庫存有物品,這些物品需要預定以便不將它們再賣給別的人,並且要減少可用的物品數量以反映正確的庫存量。
3、庫存中沒有的物品需要訂購,這需要與供應商進行某種交互。
4、 關於哪些物品入庫(並且可以立即發貨)和哪些物品退訂,需要通知相應的客戶。
這顯然不是一個完整的例子,它甚至超出了本書中所用樣例表的范圍,但足以幫助表達我們的意思了。執行這個處理需要針對許多表的多條MySQL語句。此外,需要執行的具體語句及其次序也不是固定的,它們可能會(和將)根據哪些物品在庫存中哪些不在而變化。
那么,怎樣編寫此代碼?一種是我們可以單獨編寫每條語句,並根據結果有條件地執行另外的語句。在每次需要這個處理時(以及每個需要它的應用中)都必須做這些工作。而另一種可以創建存儲過程。
其實簡單來說:存儲過程,就是為以后的使用而保存的一條或多條 MySQL語句的集合。可將其視為批文件,雖然它們的作用不僅限於批處理。
二、為什么要使用存儲過程
既然我們知道了什么是存儲過程,那么為什么要使用它們呢?有許
多理由,下面列出一些主要的理由。
1、通過把處理封裝在容易使用的單元中,簡化復雜的操作(正如前面例子所述)。
2、 由於不要求反復建立一系列處理步驟,這保證了數據的完整性。如果所有開發人員和應用程序都使用同一(試驗和測試)存儲過程,則所使用的代碼都是相同的。這一點的延伸就是防止錯誤。需要執行的步驟越多,出錯的可能性就越大。防止錯誤保證了數據的一致性。
3、簡化對變動的管理。如果表名、列名或業務邏輯(或別的內容)有變化,只需要更改存儲過程的代碼。使用它的人員甚至不需要知道這些變化。這一點的延伸就是安全性。通過存儲過程限制對基礎數據的訪問減少了數據訛誤(無意識的或別的原因所導致的數據訛誤)的機會。
4、提高性能。因為使用存儲過程比使用單獨的 SQL語句要快。
5、存在一些只能用在單個請求中的 MySQL元素和特性,存儲過程可以使用它們來編寫功能更強更靈活的代碼(在下一章的例子中可以看到。)
換句話說,使用存儲過程有 3個主要的好處,即簡單、安全、高性能。顯然,它們都很重要。不過,在將 SQL代碼轉換為存儲過程前,也必須知道它的一些缺陷。
1、一般來說,存儲過程的編寫比基本 SQL語句復雜,編寫存儲過程需要更高的技能,更豐富的經驗。
2、你可能沒有創建存儲過程的安全訪問權限。許多數據庫管理員限制存儲過程的創建權限,允許用戶使用存儲過程,但不允許他們創建存儲過程。
盡管有這些缺陷,存儲過程還是非常有用的,並且應該盡可能地使用。
不能編寫存儲過程?你依然可以使用:
MySQL
將編寫存儲過程的安全和訪問與執行存儲過程的安全和訪問區分開來。這是好事情。即使你不能(或不想)編寫自己的存儲過程,也
仍然可以在適當的時候執行別的存儲過程。
三、使用存儲過程
使用存儲過程需要知道如何執行(運行)它們。存儲過程的執行遠比其定義更經常遇到,因此,我們將從執行存儲過程開始介紹。然后再介紹創建和使用存儲過程。
執行存儲過程
MySQL
稱存儲過程的執行為調用,因此
MySQL
執行存儲過程的語句
為
CALL
。
CALL
接受存儲過程的名字以及需要傳遞給它的任意參數。請看以下例子:
call productpricing ( @ pricelow,
@ pricehigh,
@ priceaverage
);
其中執行productpricing 的存儲過程,他計算並返回產品的最低價格,最高價格,均價。
存儲過程可以顯示結果,也可以不顯示結果,接下來會提到。
創建存儲過程
正如所述,編寫存儲過程並不是微不足道的事情。為讓你了解這個過程,請看一個例子——一個返回產品平均價格的存儲過程。以下是其代碼:
CREATE PROCEDURE productpricing() BEGIN SELECT AVG(prod_price) AS priceaverage FROM products; END;
我們稍后介紹第一條和最后一條語句。此存儲過程名為productpricing,用CREATE PROCEDURE productpricing() 語句定義。如果存儲過程接受參數,它們將在 ()中列舉出來。此存儲過程沒有參數,但后跟的 ()仍然需要。BEGIN和 END語句用來限定存儲過程體,過程體本身僅是一個簡單的 SELECT語句(使用第12章介紹的 Avg()函數)。
在MySQL處理這段代碼時,它創建一個新的存儲過程 productpricing。沒有返回數據,因為這段代碼並未調用存儲過程,這里只是為以后使用而創建它。
這里有一個需要注意的就是:mysql命令行客戶機的分隔符
如果你使用的是 mysql命令行實用程序,應該仔細閱讀此說明。
默認的 MySQL語句分隔符為;(正如你已經在迄今為止所使用的MySQL語句中所看到的那樣)。 mysql命令行實用程序也使用;作為語句分隔符。如果命令行實用程序要解釋存儲過程自身內的 ;字符,則它們最終不會成為存儲過程的成分,這會使存儲過程中的 SQL出現句法錯誤。解決辦法是臨時更改命令行實用程序的語句分隔符,如下所示:
DELIMITER // CREATE PROCEDURE productpricing() BEGIN SELECT AVG(prod_price) AS priceaverage FROM products; END // DELIMITER ;
其中,
DELIMITER //告訴命令行實用程序使用 //作為新的語句結束分隔符,可以看到標志存儲過程結束的 END定義為END//而不是END; 。這樣,存儲過程體內的 ;仍然保持不動,並且正確地傳遞給數據庫引擎。最后,為恢復為原來的語句分隔符,可使用 DELIMITER ;。除\符號外,任何字符都可以用作語句分隔符。如果你使用的是 mysql命令行實用程序,在閱讀本章時請記住這里的內容。
那么,如何使用這個存儲過程?如下所示:
CALL productpricing();
結果是:
+--------------+ | priceaverage | +--------------+ | 16.133571 | +--------------+
CALL productpricing();執行剛創建的存儲過程並顯示返回的結果。因為存儲過程實際上是一種函數,所以存儲過程名后需要有()符號(即使不傳遞參數也需要)。
刪除存儲過程
存儲過程在創建之后,被保存在服務器上以供使用,直至被刪除。
刪除命令(類似於第
21
章所介紹的語句)從服務器中刪除存儲過程。為刪除剛創建的存儲過程,可使用以下語句:
DROP PROCEDURE productpricing;
這條語句刪除剛創建的存儲過程。請注意沒有使用后面的
()
,只給出存儲過程名。
僅當存在時刪除 :
如果指定的過程不存在,則
DROP PROCEDURE將產生一個錯誤。當過程存在想刪除它時(如果過程不存在也
不產生錯誤)可使用
DROP PROCEDURE IF EXISTS
。
使用參數
productpricing
只是一個簡單的存儲過程,它簡單地顯示 SELECT語句的結果。一般,存儲過程並不顯示結果,而是把結果返回給你指定的變量。這里所說的變量( variable)內存中一個特定的位置,用來臨時存儲數據。
以下是 productpricing的修改版本(如果不先刪除此存儲過程,則不能再次創建它):
DELIMITER // CREATE PROCEDURE pricing( OUT pl DECIMAL(8, 2), OUT ph DECIMAL (8, 2), OUT pa DECIMAL (8, 2), ) BEGIN SELECT MIN (prod_price) INTO pl FROM productes; SELECT MAX (prod_price) INTO ph FROM productes; SELECT AVG (prod_price) INTO pa FROM productes; END // DELIMITER ;
此存儲過程接受
3個參數:pl 存儲產品最低價格, ph存儲產品最高價格, pa存儲產品平均價格。每個參數必須具有指定的類型,這里使用十進制值。關鍵字 OUT指出相應的參數用來從存儲過程傳出一個值(返回給調用者)。 MySQL支持IN (傳遞給存儲過程)、 OUT(從存儲過程傳出,如這里所用)和 INOUT(對存儲過程傳入和傳出)類型的參數。存儲過程的代碼位於 BEGIN和END 語句內,如前所見,它們是一系列SELECT語句,用來檢索值,然后保存到相應的變量(通過指定 INTO關鍵字)。
注意參數的數據類型:存儲過程的參數允許的數據類型與表中使用的數據類型相同。注意,記錄集不是允許的類型,因此,不能通過一個參數返回多個行和列。這就是前面的例子為什么要使用 3個參數(和3條SELECT語句)的原因。
為調用此修改過的存儲過程,必須指定 3個變量名,如下所示:
CALL pricing (@pricelow, @pricehigh, @pricevarage );
由於此存儲過程要求
3個參數,因此必須正好傳遞 3個參數,不多也不少。所以,這條 CALL語句給出3 個參數。它們是存儲過程將保存結果的 3個變量的名字。
變量名 所有MySQL變量都必須以 @開始。
在調用時,這條語句並不顯示任何數據。它返回以后可以顯示(或在其他處理中使用)的變量。
SELECT @pricevarage
輸出:
+--------+ | @pricevarage| +--------+ | 55.00 | +--------+
為了獲得 3個值,可使用以下語句:
SELECT @pricelow, @pricehigh, @pricevarage ;
你會看到一個輸出結果。
下面是另外一個例子,這次使用 IN和OUT 參數。ordertotal接受訂單號並返回該訂單的合計:
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,因為要從存儲過程返回合計。 SELECT語句使用這兩個參數,WHERE子句使用 onumber選擇正確的行,INTO使用 ototal存儲計算出來的合計。
為調用這個新存儲過程,可使用以下語句:
call ordertotal(20005, @total);
必須給
ordertotal傳遞兩個參數;第一個參數為訂單號,第二個參數為包含計算出來的合計的變量名。
為了顯示次合計,可以如下操作:
SELECT @total;
輸出:
+--------+ | @total | +--------+ | 192.37| +--------+
@total已由ordertotal 的CALL語句填寫, SELECT顯示它包含的值。
為了得到另一個訂單的合計顯示,需要再次調用存儲過程,然后重新顯示變量:
call ordertotal(20009, @total); SELECT @total;
輸出:
+--------+ | @total | +--------+ | 38.47 | +--------+
以后我們每次要通過訂單號,來獲得商品的總價都可以使用這個方式。是不是很有用啊。。
建立智能存儲過程
迄今為止使用的所有存儲過程基本上都是封裝
MySQL簡單的SELECT語句。雖然它們全都是有效的存儲過程例子,但它們所能完成的工作你直接用這些被封裝的語句就能完成(如果說它們還能帶來更多的東西。那就是使事情更復雜)。只有在存儲過程內包含業務規則和智能處理時,它們的威力才真正顯現出來。
考慮這個場景。你需要獲得與以前一樣的訂單合計,但需要對合計增加營業稅,不過只針對某些顧客(或許是你所在州中那些顧客)。那么,你需要做下面幾件事情:
1、獲得合計(和以前一樣)
2、把營業稅有條件的添加到合計
3、返回合計(帶或不帶稅的)
我們輸入如下代碼:
-- Name: ordertotal // 添加注釋 -- Parameters: onumber = order number -- taxable = 0 if not taxable, 1 if taxtable -- ototal = order total variable 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;
此存儲過程有很大的變動。首先,增加了注釋(前面放置 --)。在存儲過程復雜性增加時,這樣做特別重要。添加了另外一個參數 taxable,它是一個布爾值(如果要增加稅則為真,否則為假)。在存儲過程體中,用 DECLARE語句定義了兩個局部變量。 DECLARE要求指定變量名和數據類型,它也支持可選的默認值(這個例子中的 taxrate的默認被設置為 6%)。SELECT 語句變,因此其結果存儲到 total(局部變量)而不是 ototal。IF 語句檢查taxable是否為真,如果為真,則用另一SELECT語句增加營業稅到局部變量 total。最后,用另一SELECT語句將total(它增加或許不增加營業稅)保存到 ototal。
注意:COMMENT關鍵字 ,本例子中的存儲過程在 CREATE PROCEDURE語句中包含了一個 COMMENT值。它不是必需的,但如果給出,將在SHOW PROCEDURE STATUS的結果中顯示。
這顯然是一個更高級,功能更強的存儲過程。為試驗它,請用以下兩條語句:
第一條:
call ordertotal(20009, 0,@total); SELECT @total;
輸出:
+--------+ | @total | +--------+ | 38.47 | +--------+
第二條:
call ordertotal(20009, 1,@total); SELECT @total;
輸出:
+--------+ | @total | +--------+ | 36.21 | +--------+
BOOLEAN
值指定為1 表示真,指定為 0表示假(實際上,非零值都考慮為真,只有 0被視為假)。通過給中間的參數指定 0或1 ,可以有條件地將營業稅加到訂單合計上。
這個例子給出了 MySQL的IF 語句的基本用法。 IF語句還支持 ELSEIF和ELSE 子句(前者還使用 THEN子句,后者不使用)。在以后章節中我們將會看到 IF的其他用法(以及其他流控制語句)。
檢查存儲過程
為顯示用來創建一個存儲過程的 CREATE語句,使用SHOW CREATE PROCEDURE語句:
show create procedure ordertotal;
為了獲得包括何時、由誰創建等詳細信息的存儲過程列表,使用 SHOW PROCEDURE STATUS。
注意:限制過程狀態結果,SHOW PROCEDURE STATUS列出所有存儲過程。為限制其輸出,可使用 LIKE指定一個過濾模式,例如:
SHOW PROCEDURE STATUS like 'ordertotal';