Posted on 2007-04-22 14:48
停留的風 閱讀(2069)
評論(3) 編輯 收藏 所屬分類:
.NET技巧特輯
存 儲 過 程
介紹怎樣使用存儲過程。存儲過程是數據庫服務器端的一段程序,它有兩種類型。一種類似于SELECT查詢,用于檢索數據,檢索到的數據能夠以數據集的形式返回給客戶。另一種類似于INSERT或DELETE查詢,它不返回數據,只是執行一個動作。有的服務器允許同一個存儲過程既可以返回數據又可以執行動作。
10.1 概 述
在不同類型的服務器上,存儲過程的工作方式是不同的。例如,對于InterBase服務器來說,它能夠以輸出參數的形式返回數據,而對于其他服務器如MicrosoftSQL Server和Sybase,能夠以數據集的形式返回數據和信息。
在Delphi 4中,要訪問和操縱服務器上的存儲過程,可以使用TStoredProc構件或TQuery構件。至于到底選擇哪一個,取決于存儲過程本身是怎樣編寫的、數據怎樣返回和使用哪一種服務器。TStoredProc構件和TQuery構件都是從TDataSet繼承下來的。
TStoredProc構件適合于執行那些不需要返回數據,并且通過輸出參數來返回信息的存儲過程。TStoredProc構件的Params屬性用于管理這些參數,同時,TStoredProc構件的GetResults函數可以顯式地申請返回結果。總之,TStoredProc構件適合于執行那些不需要返回結果或者只是通過輸出參數返回結果的存儲過程。
TQuery構件適合于執行那些能夠返回數據集的存儲過程,包括InterBase服務器上通過輸出參數返回數據集的存儲過程。當然,TQuery構件也適合于執行那些不需要返回結果或者只是通過輸出參數返回結果的存儲過程。
參數既可以由存儲過程傳遞給客戶程序,也可以由客戶程序傳遞給存儲過程,前者稱為輸出參數,后者稱為輸入參數。對于有的服務器來說,輸出參數只能傳遞一個值,而有的服務器允許輸出參數傳遞一個數據集。
10.2 什么時候需要用存儲過程
如果服務器定義了存儲過程,應當根據需要決定是否要用存儲過程。存儲過程通常是一些經常要執行的任務,這些任務往往是針對大量的記錄而進行的。在服務器上執行存儲過程,可以改善應用程序的性能。這是因為:
.服務器往往具有強大的計算能力和速度。
.避免把大量的數據下載到客戶端,減少網絡上的傳輸量。
例如,假設一個應用程序需要計算一個數據,這個數據需要涉及到許多記錄。如果不使用存儲過程的話,把這些數據下載到客戶端,導致網絡上的流量劇增。
不僅如此,客戶端可能是一臺老掉牙的計算機,它的運算速度很慢。而改用存儲過程后,服務器會很快地把數據計算出來,并且只需傳遞一個數據給客戶端,其效率之高是非常明顯的。
10.3 怎樣使用存儲過程
應用程序怎樣使用存儲過程,取決于存儲過程本身是怎樣編寫的、數據怎樣返回和使用哪一種服務器。
10.3.1 使用存儲過程的一般步驟
要訪問服務器上的存儲過程,一般是這么幾個步驟:
第一步,把一個TStoredProc構件放到窗體或數據模塊上。
第二步,設置DatabaseName屬性指定一個數據庫,可以設為BDE別名或者應用程序專用的別名(如果用TDatabase構件連接數據庫的話)。
第三步,設置StoredProcName屬性指定存儲過程的名稱。如果前面正確設置了DatabaseName屬性,就可以從一個下拉列表中選擇一個存儲過程。由于經常要在運行期執行不同的存儲過程,因此,StoredProcName屬性一般是在運行期設置的。
第四步,單擊Params邊上的省略號按鈕打開一個編輯器。如果第二步和第三步設置正確的話,在這個編輯器中將顯示所有的輸入和輸出參數,否則,這個編輯器就是空的。
要說明的是,并不是所有的服務器都能夠提供有關的參數的信息。如果服務器沒有提供有關參數的信息,就得自己建立這些參數。
10.3.2 準備和執行存儲過程
在執行存儲過程之前,最好先通知服務器準備好,這就要調用TStoredProc構件的Prepare函數,例如:
StoredProc1.Prepare;
注意:如果應用程序在運行期改變了參數的信息,必須重新調用Prepare函數。要執行存儲過程,可以調用TStoredProc構件的ExecProc函數,程序示例如下:
StoredProc1.Params[0].AsString := Edit1.Text;
StoredProc1.Prepare;
StoredProc1.ExecProc;
注意:如果在調用ExecProc之前沒有調用Prepare,TStoredProc構件會自動把參數準備好,存儲過程執行完畢后,再自動取消準備。不過,如果一個存儲過程要反復執行多次的話,最好顯式地調用Prepare,不再需要執行存儲過程時調用UnPrepare函數。
執行了存儲過程后,它有可能返回這樣幾種數據:
.一是數據集,可以用標準的數據控件顯示其中的數據。
.二是輸出參數。
.三是狀態信息。
10.4 創建一個存儲過程
存儲過程一般是用專門的工具編寫的。不過,這里要介紹的是怎樣用SQL語句在運行期動態地創建存儲過程。對于不同的服務器來說,即使是相同功能的存儲過程,SQL語句也有可能是不同的,因此,必須事先查閱服務器的文檔。
10.4.1 使用SQL語句創建存儲過程
要使用SQL語句創建存儲過程,就要用到TQuery構件的SQL屬性。如果存儲過程中要用到參數的話,必須把TQuery構件的ParamCheck屬性設為False。
下面的例子演示了怎樣用SQL語句創建一個存儲過程:
With Query1 Do
Begin
ParamCheck := False;
With SQL Do
Begin
Clear;
Add('CREATE PROCEDURE GET_MAX_EMP_NAME');
Add('RETURNS (Max_Name CHAR(15))');
Add('AS');Add('BEGIN');
Add('SELECT MAX(LAST_NAME)');
Add('FROM EMPLOYEE');
Add('INTO :Max_Name;');
Add('SUSPEND;');
Add('END');End;
ExecSQL;
End;
當然,也可以用SQL Explorer來創建存儲過程。
10.4.2 用TQuery構件檢索數據集
要用TQuery構件從存儲過程中檢索數據集,必須正確設置SQL屬性。在SELECT語句中,要用存儲過程的名稱代替表格的名稱。如果存儲過程需要傳遞輸入參數的話,要仿照Object Pascal語言的過程那樣,在存儲過程后面用一對圓括號把參數的值括起來。如果有多個輸入參數,彼此之間要用逗號隔開。
例如,InterBase服務器上有一個存儲過程叫GET_EMP_PROJ,它需要傳遞一個輸入參數叫EMP_NO,并且通過一個輸出參數叫PROJ_ID來傳遞執行結果。下面是這個存儲過程的代碼:
CREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
RETURNS (PROJ_ID CHAR(5))
AS
BEGIN
FOR SELECT PROJ_ID
FROM EMPLOYEE_PROJECT
WHERE EMP_NO = :EMP_NO
INTO :PROJ_ID
DO
SUSPEND;
END
相應地,要通過上面這個存儲過程檢索數據集,SQL語句可以這樣寫:
SELECT *
FROM GET_EMP_PROJ(52)
10.4.3 用TStoredProc構件檢索數據集
要用TStoredProc構件從存儲過程中檢索數據集,必須設置StoredProcName屬性指定一個存儲過程的名稱。如果存儲過程需要傳遞輸入參數的話,可以通過Params屬性或ParamByName函數提供參數。
例如,Sybase服務器上有一個存儲過程叫GET_EMPLOYEES,它有個輸入參數叫@EMP_NO。下面是這個存儲過程的代碼:
CREATE PROCEDURE GET_EMPLOYEES @EMP_NO SMALLINT
AS SELECT EMP_NAME, EMPLOYEE_NO FROM EMPLOYEE_TABLE
WHERE (EMPLOYEE_NO = @EMP_NO)
相應地,要通過上面這個存儲過程檢索數據集,程序應當這樣寫:
With StoredProc1 Do
Begin
Close;
ParamByName('@EMP_NO').AsSmallInt := 52;
Active := True;
End;
10.4.4 用TQuery構件通過參數檢索數據
用TQuery構件通過參數返回的數據是一條記錄,即使存儲過程只有一個輸出參數。因此,應用程序需要從返回的數據中檢索出每一個字段的值。
首先,要在SELECT語句中用存儲過程的名稱代替表格的名稱。
如果有多個輸出參數的話,您可以選擇其中部分輸出參數,也可以用星號表示選擇所有輸出參數。
如果存儲過程需要傳遞輸入參數的話,在存儲過程后面用一對圓括號把參數的值括起來。如果有多個輸入參數,彼此之間要用逗號隔開。
例如,InterBase服務器上有一個存儲過程叫GET_HIGH_EMP_NAME,通過一個輸出參數叫High_Last_Name來返回EMPLOYEE表的LAST_NAME字段。
下面是這個存儲過程的代碼:
CREATE PROCEDURE GET_HIGH_EMP_NAME
RETURNS (High_Last_Name CHAR(15))
AS
BEGIN
SELECT MAX(LAST_NAME)
FROM EMPLOYEE
INTO :High_Last_Name;
SUSPEND;
END
相應地,SQL語句應當這樣寫:
SELECT High_Last_Name
FROM GET_HIGH_EMP_NAME
10.4.5 用TStoredProc構件通過參數檢索數據
要用TStoredProc構件通過參數檢索數據,首先要設置StoredProcName屬性指定一個存儲過程。如果存儲過程需要傳遞輸入參數的話,可以通過Params屬性或ParamByName函數提供參數。
調用ExecProc執行了存儲過程后,可以通過Params屬性或ParamByName函數訪問輸出參數。
例如,InterBase服務器上有一個存儲過程叫GET_HIGH_EMP_NAME,通過一個輸出參數叫High_Last_Name返回EMPLOYEE表的LAST_NAME字段。
下面是這個存儲過程的代碼:
CREATE PROCEDURE GET_HIGH_EMP_NAME
RETURNS (High_Last_Name CHAR(15))
AS
BEGIN
SELECT MAX(LAST_NAME)FROM EMPLOYEE
INTO :High_Last_Name;
SUSPEND;
END
相應地,要通過上面這個存儲過程檢索數據,程序應當這樣寫:
With StoredProc1 Do
Begin
StoredProcName := 'GET_HIGH_EMP_NAME'ExecProc;
Edit1.Text := ParamByName('High_Last_Name').AsString;
End;
10.4.6 用TQuery構件執行一個動作
有的存儲過程并不返回數據,它們只是執行一些動作。例如,要刪除一條記錄,既可以用DELETE語句直接刪除記錄,也可以執行一個存儲過程。
要用TQuery構件執行一個動作,需在SQL語句中包含要執行的存儲過程的名稱。如果存儲過程需要傳遞輸入參數的話,在存儲過程后面用一對圓括號把參數的值括起來。如果有多個輸入參數,彼此之間要用逗號隔開。
例如,InterBase服務器上有一個存儲過程叫ADD_EMP_PROJ,用于向EMPLOYEE_PROJECT表中增加一條記錄。
下面是這個存儲過程的代碼:
CREATE PROCEDURE ADD_EMP_PROJ (EMP_NO SMALLINT, PROJ_ID CHAR(5))
AS
BEGIN
BEGIN
INSERT INTO EMPLOYEE_PROJECT (EMP_NO, PROJ_ID)
VALUES (:EMP_NO, :PROJ_ID);
WHEN SQLCODE -530 DO
EXCEPTION UNKNOWN_EMP_ID;
END
SUSPEND;
END
相應地,SQL語句應當這樣寫:
EXECUTE PROCEDURE ADD_EMP_PROJ(20, 'GUIDE');
10.4.7 用TStoredProc構件執行一個動作
要用TStoredProc構件執行一個動作,首先要設置StoredProcName屬性指定一個存儲過程。可以通過Params屬性或ParamByName函數提供輸入參數(如果需要的話)。
例如,InterBase服務器上有一個存儲過程叫ADD_EMP_PROJ,用于向EMPLOYEE_PROJECT表中增加一條記錄。它的代碼請參見上一小節。
要執行這個存儲過程,程序應當這樣寫:
With StoredProc1 Do
Begin
StoredProcName := 'ADD_EMP_PROJ';
ExecProc;
End;
10.5 存儲過程的參數
要執行服務器上的存儲過程,往往要傳遞一些參數。這些參數分為四種類型:
第一種稱為輸入參數,由客戶程序向存儲過程傳遞值。
第二種稱為輸出參數,由存儲過程向客戶程序返回結果。
第三種稱為輸入/輸出參數,既可以由客戶程序向存儲過程傳遞值,也可以由存儲過程向客戶程序返回結果。
第四種稱為狀態參數,由存儲過程向客戶程序返回錯誤信息。
要說明的是,并不是所有的服務器都支持上述四種類型的參數,例如,InterBase就不支持狀態參數。
可以通過TStoredProc構件的Params屬性訪問存儲過程的參數(TParam對象)。如果在設計期正確設置了StoredProcName屬性,Params屬性中將自動包含存儲過程的參數,否則,需要自己建立參數。
10.5.1 輸入參數
輸入參數用于由客戶程序向存儲過程傳遞值,值實際上是傳遞給存儲過程中的SQL語句。如果一個存儲過程有輸入參數,一定要在執行該存儲過程之前對輸入參數賦值。
如果用TQuery構件執行存儲過程,可以把輸入參數用一對圓括號括起來,彼此之間用逗號隔開,就像調用Object Pascal的過程一樣。例如,假設要執行一個存儲過程叫GET_EMP_PROJ,它需要傳遞一個輸入參數,其值為52,SQL語句如下:
SELECT PROJ_ID
FROM GET_EMP_PROJ(52)
如果用TStoredProc構件執行存儲過程,可以通過Params屬性或ParamByName函數來訪問每一個輸入參數。要在執行存儲過程前對輸入參數賦值。例如,假設要執行一個存儲過程叫GET_EMP_PROJ,它需要傳遞一個輸入參數叫EMP_NO,其數據類型為SMALLINT,其值為52,相應地程序代碼應當這樣寫:
With StoredProc1 Do
Begin
ParamByName('EMP_NO').AsSmallInt := 52;
ExecProc;
End;
10.5.2 輸出參數
輸出參數用于由存儲過程向客戶程序傳遞結果。輸出參數是由存儲過程賦值的,客戶程序只能在執行了存儲過程以后,才能訪問輸出參數的值。
要訪問輸出參數的值,可以通過TStoredProc構件的Params屬性或ParamByName函數。例如,下面的代碼把輸出參數的值顯示到一個編輯框中:
With StoredProc1 Do
Begin
ExecProc;
Edit1.Text := Params[0].AsString;
End;
大多數存儲過程都有一個或幾個輸出參數,輸出參數既可以返回一個單獨的值,也可以返回一個數據集。
注意:有的服務器如InFormix可能不提供參數的信息,只能從存儲過程的代碼中查看它有無輸出參數。
10.5.3 輸入/輸出參數
輸入/輸出參數既可以用于由客戶程序向存儲過程傳遞值,也可以由存儲過程向客戶程序返回結果,也就是說,同一個參數兼具兩種角色。作為輸入參數,必須在執行存儲過程之前對它賦值。作為輸出參數,只能在執行了存儲過程后訪問它的值。
例如,Oracle服務器中有一個存儲過程,它的IN_OUTVAR參數就是一個輸入/輸出參數。這個存儲過程的代碼如下:
CREATE OR REPLACE PROCEDURE UPDATE_THE_TABLE (IN_OUTVAR IN OUT INTEGER)
AS
BEGIN
UPDATE ALLTYPETABLE
SET NUMBER82FLD = IN_OUTVAR
WHERE KEYFIELD = 0;
IN_OUTVAR:=1;
END
UPDATE_THE_TABLE;
相應地,要執行上面這個存儲過程,程序代碼應當這樣寫:
With StoredProc1 Do
Begin
ParamByName('IN_OUTVAR').AsInteger := 103;
ExecProc;IntegerVar := ParamByName('IN_OUTVAR').AsInteger;
End;
10.5.4 狀態參數
除了返回數據集或輸出參數外,有的存儲過程還可以返回一個狀態參數。狀態參數不需要事先賦值,只有在執行了存儲過程之后才能訪問它的值。
要訪問輸出參數的值,可以通過TStoredProc構件的Params屬性或ParamByName函數。例如,下面的代碼訪問ByOutputParam參數:
DateVar := StoredProc1.ParamByName('ByOutputParam').AsDate;
10.5.5 怎樣在設計期訪問參數
如果在設計期正確設置了DatabaseName和StoredProcName屬性,就可以在設計期看到這些參數,對于其中的輸入參數,可以設置它們的值。不過,有的數據庫服務器不提供存儲過程的參數信息,這種情況下,只能使用SQLExplorer去查看存儲過程的代碼,從中找出參數的名稱和類型,然后在對象觀察器中手動建立這些參數。
要在設計期訪問參數,可以單擊Params屬性邊上的省略號按鈕打開如圖10.1所示的編輯器:
圖10.1 存儲過程的參數
單擊工具欄上的按鈕可以創建一個新的參數,單擊按鈕可以刪除一個參數,單擊按鈕可以把參數的順序上移,單擊按鈕可以把參數的順序下移。
選擇其中一個參數,對象觀察器將同步顯示該參數的屬性。其中,ParamType屬性必須設置,以指定參數的使用類型,可以設為Input、Output、Input/Output或Result。DataType屬性也必須設置,以指定參數的數據類型。注:對于Oracle的存儲過程來說,要返回數據集,必須把DataType屬性設為ftCursor。對于輸入參數或輸入/輸出來說,必須設置Value屬性給參數賦值。不能對輸出參數和狀態參數賦值。
10.5.6 怎樣在運行期訪問參數
如果服務器沒有提供有關參數的信息,就必須自己建立參數。在運行期,可以通過TParam的Create或TParams的AddParam來創建一個參數。
例如,InterBase服務器上有一個存儲過程叫GET_EMP_PROJ,這個存儲過程有一個輸入參數叫EMP_NO和一個輸出參數叫PROJ_ID。這個存儲過程的代碼如下:
CREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
RETURNS (PROJ_ID CHAR(5))
AS
BEGIN
FOR SELECT PROJ_IDFROM EMPLOYEE_PROJECT
WHERE EMP_NO = :EMP_NO
INTO :PROJ_ID
DO
SUSPEND;
END
下面通過編程動態地創建這兩個參數:
var
P1, P2: TParam;
Begin
...
With StoredProc1 Do
Begin
StoredProcName := 'GET_EMP_PROJ';
Params.Clear;
P1 := TParam.Create(Params, ptInput);
P2 := TParam.Create(Params, ptOutput);
TryParams[0].Name := 'EMP_NO';Params[1].Name := 'PROJ_ID';
ParamByname('EMP_NO').AsSmallInt := 52;
ExecProc;Edit1.Text := ParamByname('PROJ_ID').AsString;
FinallyP1.Free;
P2.Free;
End;
End;
...
End;
10.5.7 ParamBindMode屬性
這個屬性用于設置Params屬性中的每一個參數與存儲過程的參數怎樣匹配。
如果ParamBindMode屬性設為pbByName(默認),表示Params屬性中的參數按名稱與存儲過程的參數匹配。
如果ParamBindMode設為pbByNumber,表示Params屬性中的參數按序號與存儲過程的參數匹配。
建議把ParamBindMode屬性設為pbByName,因為按名稱匹配不需要參數的順序,而按序號匹配往往容易搞錯。不過,有的情況下可能需要按序號匹配,因為有的服務器并沒有提供參數的名稱。