1. 程式人生 > >儲存過程與使用者自定義函式(SQL SERVER)

儲存過程與使用者自定義函式(SQL SERVER)

聯絡
二者本質上沒有什麼區別。當儲存過程和函式被執行的時候,SQL Manager會到procedure cache中去取相應的查詢語句,如果在procedure cache裡沒有相應的查詢語句,SQL Manager就會對儲存過程和函式進行編譯。Procedure cache 中儲存的是執行計劃,當編譯好之後就執行procedure cache中的execution plan,之後SQL SERVER會根據每個execution plan的實際情況來考慮是否要在cache中儲存這個plan,評判的標準一個是這個execution plan可能被使用的頻率;其次是生成這個plan的代價,也就是編譯的耗時。儲存在cache中的plan在下次執行時就不用再編譯了。
二者的區別就是 1) 函式返回的整數值將可充當一個有意義的資料,而儲存過程返回值用來顯示執行成功還是失敗。2) 可以在查詢中執行嵌入式函式,但儲存過程卻不能。

儲存過程
儲存過程(Store Procedure,簡稱SP)和函式類似,都是SQL面向過程的一種資料庫物件。儲存過程實際上就是一段獨立的SQL命令,它儲存於資料庫中而不是單獨的檔案中,它有輸入引數、輸出引數和返回值。
儲存過程是以特定順序排列的T-SQL語句序列,可為其指定名稱,加以編譯並儲存到SQL SERVER上。一旦由DBMS編譯並儲存,使用者可使用應用程式或者其他SQL指令碼呼叫並執行儲存過程中的語句。其類似於應用程式中的程式呼叫子程式。儲存過程與程式有很多相似之處,具體如下所述:
儲存過程可以接受輸入引數,最終以輸出引數或者輸出結果的格式向呼叫該儲存過程的其他儲存過程或T-SQL批處理返回值。
儲存過程可以執行復雜的邏輯操作和運算,可以呼叫其他儲存過程。
儲存過程不像函式那樣將值返回,其向呼叫過程或批處理返回的是狀態值

,以指明成功或者失敗(以及失敗的原因)。
可以使用T-SQL的EXECUTE(簡寫為EXEC)語句來執行儲存過程。在實際應用開發過程中,推薦在SQL SERVER中使用儲存過程而不使用T-SQL,因為使用儲存過程有以下好處:
1. 儲存過程作為一個數據庫物件已在伺服器註冊。
2. 儲存過程具有安全特性(例如許可權)和所有權連結,以及可以附加它們的證書。使用者可以被授予許可權來執行儲存過程,而不必直接對儲存過程中引用物件有許可權。
3. 儲存過程可以加強應用程式的安全性。使用引數化儲存過程有助於保護應用程式,降低SQL注入攻擊的可能性。
4. 儲存過程允許模組化程式設計。儲存過程與呼叫該儲存過程的程式相分離,減少了應用程式與資料庫之間的耦合。
5. 儲存過程在編譯後將把執行計劃進行儲存
,在以後的呼叫中就可以不用像T-SQL語句一樣每次進行編譯然後再執行。由於減少了編譯的過程,從而提高了執行的效率。
6. 儲存過程可以減少網路通訊流量。一個需要數百行T-SQL程式碼的操作可以通過將T-SQL程式碼中的邏輯寫在儲存過程當中,然後由應用程式執行儲存過程即可,而不需要在網路中傳送數百行程式碼。
7. 儲存過程可以進行加密,包含其中的處理邏輯不被其他使用者獲取。

建立儲存過程

CREATE PROCEDURE|PROC <sp name>
[ { @parameter [ type_schema_name. ] data_type }
    [ VARYING ] [ = default ] [ OUT [ PUT ] ] [ ,...n ]
    [ WITH [ RECOMPILE ] [ ,ENCRYPTION ] ]
    [ FOR REPLICATION ]
AS
<code>

儲存過程名後跟的是引數表。引數表是可選項,有些儲存過程可以不帶引數,有些帶一到多個引數。引數的宣告是由引數名、資料型別、預設值和方向4部分構成。當然在宣告引數時並不需要必須都要將這4部分寫出。一般的引數只有引數名和資料型別兩部分。
引數名必須以@符號開始,引數名的命名規則與其他資料庫物件的命名規則類似,只是引數名中不能有空格。
資料型別是在宣告變數時必須指出的,並且必須是有效的SQL SERVER資料型別。
預設值是引數與變數存在分歧的地方。通常變數將會被初始化為NULL,而引數則不是。如果需要定義一個沒有提供預設值的引數,那麼就需要在呼叫儲存過程時提供一個初始值。
方向是指資料傳輸的方向,在沒有指定的情況下預設為傳入。若宣告OUTPUT或者簡寫為OUT,則表示資料是從儲存過程中傳出的。

   --建立不帶引數的儲存過程
   CREATE PROC GetStudent
   AS
   SELECT *
   FROM Student

   --執行儲存過程
   EXEC GetStudent


   --建立帶引數的儲存過程
   CREATE PROC GetStudent
   @ssex varchar(2) = '男'
   AS
   SELECT *
   FROM Student
   WHERE sex = @ssex

   --在沒有引數賦值的情況下呼叫儲存過程,儲存過程將使用引數的預設值
   EXEC GetStudent

   --在儲存過程名後給出引數的值,引數的值順序必須與儲存過程中定義的引數順序相同
   EXEC GetStudent '女'

   --在儲存過程名後以“@引數名=引數值”的形式給出引數值。引數順序可以與儲存過程中定義的順序不同
   EXEC GetStudent @ssex = '女'

   --修改儲存過程
   ALTER PROC GetStudent
   @ssex varchar(2) --去掉預設值,呼叫該儲存過程必須傳入引數
   AS
   SELECT *
   FROM Student
   ORDER BY sid --按學號排序

   --刪除儲存過程
   --DROP PROCEDURE|PROC <sp name>
   DROP PROC GetStudent 

儲存過程除了可以被其他儲存過程呼叫外,更多的情況是作為資料庫與應用程式的介面,被外部應用程式呼叫。除了使用SELECT命令返回表集外,還可以使用OUTPUT引數返回資料以及儲存過程的RETURN值的功能。
如果在過程定義中為引數指定OUTPUT關鍵字,則儲存過程在退出時可將該引數的當前值返回至呼叫程式。若要用變數儲存引數值以便在呼叫程式中使用,則呼叫程式必須在執行儲存過程時使用OUTPUT關鍵字。

--使用OUTPUT引數的儲存過程
--儲存過程calc實現兩個數相加並將結果傳給OUTPUT引數。
CREATE PROC
@a int,
@b int,
@c int out
AS
SET @c = @a + @b  --輸出引數為輸入2個引數之和
GO
DECLARE @ans int 
EXEC @ans 1, 2, @ans out --執行儲存過程
PRINT @ans
GO
DROP PROC calc

儲存過程可以返回一個整數值(稱為“返回程式碼”),指示過程的執行狀態。與OUTPUT引數一樣,執行儲存過程時必須將返回程式碼儲存到變數中,才能在呼叫程式時使用返回程式碼值。

--使用RETURN返回值的儲存過程
CREATE PROC compareNumber
@a int,
@b int
AS
IF( @a >= @b )
    RETURN 100
ELSE
    RETURN 200
GO
DECLARE @ans int
EXEC compareNumber 123, 234 --執行儲存過程
PRINT @ans
GO
DROP PROC compareNumber

/*
--返回程式碼通常用在儲存過程內的控制流塊中,為每種可能的錯誤情況設定返回程式碼值。可以在T-SQL語句後使用@@ERROR函式,來檢測該語句在執行過程中是否有錯誤發生。
*/

使用者定義函式
使用者定義函式(User Defined Function,簡稱UDF)同儲存過程類似,是一組有序的被預先優化的T-SQL語句,並且能夠作為一個獨立工作單元被呼叫。UDF與儲存過程的主要區別在於如何返回結果。由於支援不同型別的返回值,所以UDF得限制比儲存過程要多一些。
使用儲存過程可以輸入引數也可以得到返回的引數值。儲存過程可以返回一個整數值,用於表示成功或失敗而不是返回資料。儲存過程也可以返回一個結果集,但是如果沒有將這些結果集插入到某類表(表變數或臨時表)中,則不能使用這些結果集。
但是在UDF中只能使用輸入引數,而不能使用輸出引數。輸出引數的概念已經被一個更強的返回值所替代。UDF中返回值不像儲存過程一樣只能是整型,相反,UDF不僅可以返回SQL SERVER中的大部分資料型別(除了BLOB、遊標、和時間戳),也能夠返回一張表。返回標量值得函式叫標量值函式,返回表的函式叫做表值函式。
使用者定義函式有以下優點:
1. 允許模組化程式設計。使用者只需建立一次函式並將其儲存在資料庫中,以後便可在T-SQL語句、儲存過程或其他函式中呼叫任意次。由於使用者定義函式以資料庫物件的形式儲存在資料庫中,所以可以獨立於程式原始碼進行修改,從而降低了程式與資料庫之間的耦合。
2. 執行速度更快。與儲存過程相似,使用者定義函式在編譯以後其執行計劃便會被快取,通過快取計劃並在重複執行時重用它可以降低使用T-SQL語句的編譯開銷。
3. 減少網路流量。在定義複雜約束或複雜WHERE條件時,可以使用函式來表示,以減少傳送至客戶端的數字或行數。

建立標量值函式

--語法:
CREATE FUNCTION [ schema_name. ] function_name
(
    [ { @parameter_name [AS][type_schema_name. ] parameter_data_type 
    [ = default ][ READONLY ] }
    [ ,...n ]
    ]
)
RETURNS return_data_type
    [ WITH <function_option> [ ,...n ] ]
    [ AS ]
    BEGIN
        function_body
        RETURN scalar_expression
    END

語法
function_name:使用者定義函式名稱。函式名稱必須符合有關標示符的規則,並且在資料庫中以及對其架構來說是唯一的。
@parameter_name:使用者定義函式中的引數。可以宣告一個或多個引數。
return_data_type:標量使用者定義函式的返回值。對於T-SQL函式,可以使用除timestamp資料型別之外的所有資料型別(包含CLR使用者定義型別)。對於CLR函式,允許使用除text、ntext和timestamp資料型別之外的所有資料型別(包含CLR使用者定義型別)。不能將非標量型別cursor和table指定為T-SQL函式或CLR函式中的返回資料型別。
function_body:指定一系列定義函式值的T-SQL語句。這些語句在一起使用不會產生負面影響(例如修改表)。function_body僅用於標量函式和多語句表值函式。在標量函式中,function_body是一系列T-SQL語句,這些語句一起使用的計算結果為標量值。在多語句表值函式中,function_body是一系列T-SQL語句。這些語句將填充TABLE返回變數。
scalar_expression:指定標量函式返回的標量值。

/*
一個函式最多可以有1024個引數。執行函式時,如果未定義引數的預設值,則使用者必須提供每個已宣告引數的值。
*/

--建立標量值函式
--求人的年齡
CREATE FUNCTION dbo.CalcAge( @birthday datetime )
RETURNS int --返回型別
AS
BEGIN
    RETURN year( getdate() ) - year( @birthday )
    --用當前年份減去出生年份就是年齡
END

--執行
PRINT dbo.CalcAge( '1989-12-02' )

/*
注意:與儲存過程等其他資料庫物件不同,在呼叫使用者定義函式時必須使用schema_name.function_name的形式,如果只使用函式名,寫成PRINT  CalcAge( '1989-12-02' ),則系統丟擲錯誤:'CalcAge'不是可以識別的內建函式名稱。
*/

--建立的UDF也可以嵌入到查詢中,查詢學生的名字和年齡
SELECT s.sname , dbo.Calc( s.birthday ) AS age
FROM Student
ORDER BY age

建立表值函式

--返回值是表
CREATE FUNCTION [ schema_name. ] function_name
(
    [ { @parameter_name [AS][type_schema_name. ] parameter_data_type 
    [ = default ][ READONLY ] }
    [ ,...n ]
    ]
)
RETURNS TABLE
    [ WITH <function_option> [ ,...n ] ]
    [ AS ]
    RETURN [ ( ] select_stmt [ ) ]

語法
RETURNS子句為函式返回的表定義區域性返回變數名。RETURNS子句還定義表的格式。區域性返回變數名的作用域位於函式內。
函式體中的T-SQL語句生成行並將其插入RETURNS子句的返回值中。
當執行RETURN語句時,插入變數的行為將作為函式的表格輸出返回。RETURN語句不能有引數。

CREATE FUNCTION GetStudentInfo() --定義函式名和引數
RETURNS TABLE --返回的是一個表
AS
RETURN
(
    SELECT * FROM Student
)

--若要使用該表值函式,可以將該表值函式當表一樣進行查詢,
--唯一不同的是在使用函式時必須為“架構.函式名”的形式。

--修改與儲存過程一樣用alter
--刪除用drop