1. 程式人生 > 其它 >如何使用 SQL 儲存過程簡化複雜的操作

如何使用 SQL 儲存過程簡化複雜的操作

目錄

本文介紹什麼是 SQL 儲存過程,為什麼要使用儲存過程,如何使用儲存過程,以及建立和使用儲存過程的基本語法。

一、儲存過程

迄今為止,我們使用的大多數 SQL 語句都是針對一個或多個表的單條語句。並非所有操作都這麼簡單,經常會有一些複雜的操作需要多條語句才能完成,例如以下的情形。

  • 為了處理訂單,必須核對以保證庫存中有相應的物品。
  • 如果物品有庫存,需要預定,不再出售給別的人,並且減少物品資料以反映正確的庫存量。
  • 庫存中沒有的物品需要訂購,這需要與供應商進行某種互動。
  • 關於哪些物品入庫(並且可以立即發貨)和哪些物品退訂,需要通知相應的顧客。

這顯然不是一個完整的例子,它甚至超出了本文的範圍,但足以表達我們的意思了。執行這個處理需要針對許多表的多條 SQL 語句。

此外,需要執行的具體 SQL 語句及其次序也不是固定的,它們可能會根據物品是否在庫存中而變化。

那麼,怎樣編寫程式碼呢?可以單獨編寫每條 SQL 語句,並根據結果有條件地執行其他語句。在每次需要這個處理時(以及每個需要它的應用中),都必須做這些工作。

可以建立儲存過程。簡單來說,儲存過程就是為以後使用而儲存的一條或多條 SQL 語句。可將其視為批檔案,雖然它們的作用不僅限於批處理。

說明:不適用於 SQLite

SQLite 不支援儲存過程。

說明:還有更多內容

儲存過程很複雜,全面介紹它需要很大篇幅。

市面上有專門講儲存過程的書。

本文不打算講解儲存過程的所有內容,只給出簡單介紹,讓讀者對它們的功能有所瞭解。

因此,這裡給出的例子只提供 Oracle 和 SQL Server 的語法。

二、為什麼要使用儲存過程

我們知道了什麼是儲存過程,那麼為什麼要使用它們呢?理由很多,下面列出一些主要的。

  • 通過把處理封裝在一個易用的單元中,可以簡化複雜的操作(如前面例子所述)。
  • 由於不要求反覆建立一系列處理步驟,因而保證了資料的一致性。如果所有開發人員和應用程式都使用同一儲存過程,則所使用的程式碼都是相同的。
  • 上一點的延伸就是防止錯誤。需要執行的步驟越多,出錯的可能性就越大。防止錯誤保證了資料的一致性。
  • 簡化對變動的管理。如果表名、列名或業務邏輯(或別的內容)有變化,那麼只需要更改儲存過程的程式碼。使用它的人員甚至不需要知道這些變化。
  • 上一點的延伸就是安全性。通過儲存過程限制對基礎資料的訪問,減少了資料訛誤(無意識的或別的原因所導致的資料訛誤)的機會。
  • 因為儲存過程通常以編譯過的形式儲存,所以 DBMS 處理命令所需的工作量少,提高了效能。
  • 存在一些只能用在單個請求中的 SQL 元素和特性,儲存過程可以使用它們來編寫功能更強更靈活的程式碼。

換句話說,使用儲存過程有三個主要的好處,即簡單、安全、高效能。顯然,它們都很重要。不過,在將 SQL 程式碼轉換為儲存過程前,也必須知道它的一些缺陷。

  • 不同 DBMS 中的儲存過程語法有所不同。事實上,編寫真正的可移植儲存過程幾乎是不可能的。

    不過,儲存過程的自我呼叫(名字以及資料如何傳遞)可以相對保持可移植。

    因此,如果需要移植到別的 DBMS,至少客戶端應用程式碼不需要變動。

  • 一般來說,編寫儲存過程比編寫基本 SQL 語句複雜,需要更高的技能,更豐富的經驗。

    因此,許多資料庫管理員把限制儲存過程的建立作為安全措施(主要受上一條缺陷的影響)。

儘管有這些缺陷,儲存過程還是非常有用的,並且應該使用。事實上,多數 DBMS 都帶有用於管理資料庫和表的各種儲存過程。更多資訊請參閱具體的 DBMS 文件。

說明:不會寫儲存過程?你依然可以使用

大多數 DBMS 將編寫儲存過程所需的安全和訪問許可權與執行儲存過程所需的安全和訪問許可權區分開來。

這是好事情,即使你不能(或不想)編寫自己的儲存過程,也仍然可以在適當的時候執行別的儲存過程。

三、執行儲存過程

儲存過程的執行遠比編寫要頻繁得多,因此我們先介紹儲存過程的執行。

執行儲存過程的 SQL 語句很簡單,即 EXECUTE

EXECUTE 接受儲存過程名和需要傳遞給它的任何引數。

請看下面的例子(你無法執行這個例子,因為 AddNewProduct 這個儲存過程還不存在):

EXECUTE AddNewProduct('JTS01',
                      'Stuffed Eiffel Tower',
                      6.49,
                      'Plush stuffed toy with the text La Tour Eiffel in red white and blue');

這裡執行一個名為 AddNewProduct 的儲存過程,將一個新產品新增到 Products 表中。

AddNewProduct 有四個引數,分別是:供應商 ID(Vendors 表的主鍵)、產品名、價格和描述。

這 4 個引數匹配儲存過程中 4 個預期變數(定義為儲存過程自身的組成部分)。此儲存過程將新行新增到 Products 表,並將傳入的屬性賦給相應的列。

我們注意到,在 Products 表中還有另一個需要值的列 prod_id 列,它是這個表的主鍵。

為什麼這個值不作為屬性傳遞給儲存過程?要保證恰當地生成此 ID,最好是使生成此 ID 的過程自動化(而不是依賴於終端使用者的輸入)。

這也是這個例子使用儲存過程的原因。以下是儲存過程所完成的工作:

  • 驗證傳遞的資料,保證所有 4 個引數都有值;
  • 生成用作主鍵的唯一 ID;
  • 將新產品插入 Products 表,在合適的列中儲存生成的主鍵和傳遞的資料。

這就是儲存過程執行的基本形式。對於具體的 DBMS,可能包括以下的執行選擇。

  • 引數可選,具有不提供引數時的預設值。
  • 不按次序給出引數,以“引數=值”的方式給出引數值。
  • 輸出引數,允許儲存過程在正執行的應用程式中更新所用的引數。
  • SELECT 語句檢索資料。
  • 返回程式碼,允許儲存過程返回一個值到正在執行的應用程式。

四、建立儲存過程

正如所述,儲存過程的編寫很重要。為了獲得感性認識,我們來看一個簡單的儲存過程例子,它對郵件傳送清單中具有郵件地址的顧客進行計數。

下面是該過程的 Oracle 版本:

CREATE PROCEDURE MailingListCount (
  ListCount OUT INTEGER
)
IS
v_rows INTEGER;
BEGIN
    SELECT COUNT(*) INTO v_rows
    FROM Customers
    WHERE NOT cust_email IS NULL;
    ListCount := v_rows;
END;

這個儲存過程有一個名為 ListCount 的引數。此引數從儲存過程返回一個值而不是傳遞一個值給儲存過程。

關鍵字 OUT 用來指示這種行為。Oracle 支援 IN(傳遞值給儲存過程)、OUT(從儲存過程返回值,如這裡)、INOUT(既傳遞值給儲存過程也從儲存過程傳回值)型別的引數。

儲存過程的程式碼括在 BEGINEND 語句中,這裡執行一條簡單的 SELECT 語句,它檢索具有郵件地址的顧客。然後用檢索出的行數設定 ListCount(要傳遞的輸出引數)。

呼叫 Oracle 例子可以像下面這樣:

var ReturnValue NUMBER
EXEC MailingListCount(:ReturnValue);
SELECT ReturnValue;

這段程式碼聲明瞭一個變數來儲存儲存過程返回的任何值,然後執行儲存過程,再使用 SELECT 語句顯示返回的值。

下面是該過程的 SQL Server 版本。

CREATE PROCEDURE MailingListCount
AS
DECLARE @cnt INTEGER
SELECT @cnt = COUNT(*)
FROM Customers
WHERE NOT cust_email IS NULL;
RETURN @cnt;

此儲存過程沒有引數。呼叫程式檢索 SQL Server 的返回程式碼提供的值。

其中用 DECLARE 語句聲明瞭一個名為 @cnt 的區域性變數(SQL Server 中所有區域性變數名都以 @ 起頭);

然後在 SELECT 語句中使用這個變數,讓它包含 COUNT() 函式返回的值;最後,用 RETURN @cnt 語句將計數返回給呼叫程式。

呼叫 SQL Server 例子可以像下面這樣:

DECLARE @ReturnValue INT
EXECUTE @ReturnValue=MailingListCount;
SELECT @ReturnValue;

這段程式碼聲明瞭一個變數來儲存儲存過程返回的任何值,然後執行儲存過程,再使用 SELECT 語句顯示返回的值。

下面是另一個例子,這次在 Orders 表中插入一個新訂單。此程式僅適用於 SQL Server,但它說明了儲存過程的某些用途和技術:

CREATE PROCEDURE NewOrder @cust_id CHAR(10)
AS
-- 為訂單號宣告一個變數
DECLARE @order_num INTEGER
-- 獲取當前最大訂單號
SELECT @order_num=MAX(order_num)
FROM Orders
-- 決定下一個訂單號
SELECT @order_num=@order_num+1
-- 插入新訂單
INSERT INTO Orders(order_num, order_date, cust_id)
VALUES(@order_num, GETDATE(), @cust_id)
-- 返回訂單號
RETURN @order_num;

此儲存過程在 Orders 表中建立一個新訂單。

它只有一個引數,即下訂單顧客的 ID。訂單號和訂單日期這兩列在儲存過程中自動生成。

程式碼首先宣告一個區域性變數來儲存訂單號。接著,檢索當前最大訂單號(使用 MAX() 函式)並增加 1(使用 SELECT 語句)。

然後用 INSERT 語句插入由新生成的訂單號、當前系統日期(用 GETDATE() 函式檢索)和傳遞的顧客 ID 組成的訂單。

最後,用 RETURN @order_num 返回訂單號(處理訂單物品需要它)。

請注意,此程式碼加了註釋,在編寫儲存過程時應該多加註釋。

說明:註釋程式碼

應該註釋所有程式碼,儲存過程也不例外。增加註釋不影響效能,因此不存在缺陷(除了增加編寫時間外)。

註釋程式碼的好處很多,包括使別人(以及你自己)更容易地理解和更安全地修改程式碼。

對程式碼進行註釋的標準方式是在之前放置 --(兩個連字元)。

有的 DBMS 還支援其他的註釋語法,不過所有 DBMS 都支援 --,因此在註釋程式碼時最好都使用這種語法。

下面是相同 SQL Server 程式碼的一個很不同的版本:

CREATE PROCEDURE NewOrder @cust_id CHAR(10)
AS
-- 插入新訂單
INSERT INTO Orders(cust_id)
VALUES(@cust_id)
-- 返回訂單號
SELECT order_num = @@IDENTITY;

此儲存過程也在 Orders 表中建立一個新訂單。這次由 DBMS 生成訂單號。

大多數 DBMS 都支援這種功能;SQL Server 中稱這些自動增量的列為標識欄位(identity field),而其他 DBMS 稱之為自動編號(auto number)或序列(sequence)。

傳遞給此過程的引數也是一個,即下訂單的顧客 ID。

訂單號和訂單日期沒有給出,DBMS 對日期使用預設值(GETDATE() 函式),訂單號自動生成。

怎樣才能得到這個自動生成的 ID?在 SQL Server 上可在全域性變數 @@IDENTITY 中得到,它返回到呼叫程式(這裡使用 SELECT 語句)。

可以看到,藉助儲存過程,可以有多種方法完成相同的工作。不過,所選擇的方法受所用 DBMS 特性的制約。

五、小結

本文介紹了什麼是儲存過程,為什麼使用儲存過程。我們介紹了執行和建立儲存過程的語法,使用儲存過程的一些方法。

儲存過程是個相當重要的主題,一篇文章無法全部涉及。

各種 DBMS 對儲存過程的實現不一,你使用的 DBMS 可能提供了一些這裡提到的功能,也有其他未提及的功能,更詳細的介紹請參閱具體的 DBMS 文件。

原文連結:https://www.developerastrid.com/sql/sql-stored-procedures/

(完)