[轉載]SQL Server 用戶定義的函數

原文地址:https://docs.microsoft.com/zh-cn/previous-versions/aa686015(v=msdn.10)?redirectedfrom=MSDNgit

SQL Server 用戶定義的函數

John Papasql

用戶定義的函數 (UDF) 是準備好的代碼片斷,它能夠接受參數,處理邏輯,而後返回某些數據。根據 SQL Server Books Online,SQL Server™ 2000 中的 UDF 能夠接受從 0 到 1024 的任意個數的參數,不過我必須認可,我還何嘗試將 1024 個參數傳遞到 UDF 中。UDF 的另外一個關鍵特徵是返回一個值。取決於 UDF 的類型,調用例程可使用這個值來繼續處理它的數據。所以,若是 UDF 返回單一值(標量值),調用例程就能夠在任何可以使用標準變量或文字值的地方使用這個值。若是 UDF 返回一個行集,則調用例程能夠循環訪問該行集,聯接到該行集,或簡單地從該行集中選擇列。編程

雖然如今大多數編程語言已經暫時支持函數,但只有 SQL Server 2000 引入了 UDF。存儲過程和視圖在 SQL Server 中可用的時間遠早於 UDF,但這些對象中的每個在 SQL Server 開發中都有本身適當的位置。存儲過程能夠很好地用於處理複雜的 SQL 邏輯、保證和控制對數據的訪問,以及將行集返回到調用例程,不管此例程是基於 Visual Basic® 的程序,仍是另外一個 Transact-SQL (T-SQL) 批處理文件。與視圖不一樣,存儲過程是已編譯的,這使得它們成爲用來表示和處理頻繁運行的 SQL 語句的理想候選者。視圖能夠很好地用於控制對數據的訪問,但它們的控制方式與存儲過程不一樣。視圖僅限於生成該視圖的基礎 SELECT 語句中的某些列和行。於是視圖經常使用於表示經常使用的 SELECT 語句,該語句能夠聯接多個表、使用 WHERE 子句,以及公開特定的列。在聯接到其餘表和視圖的 SQL 語句的 FROM 子句中常常會發現視圖。架構

在其核心部分,UDF 既相似於視圖,也相似於存儲過程。像視圖同樣,UDF 能夠返回一個行集,該行集可用於 JOIN 中。所以,當 UDF 返回一個行集並接受參數時,它像一個您能夠聯接到的存儲過程、或者一個參數化的視圖。可是,正如我將演示的,UDF 能夠作到這一點,甚至更多。編程語言

有兩種主要的 UDF 類型:返回標量值的 UDF 和返回表值的 UDF。在表值 UDF 中,您將找到返回內聯表和多語句表的 UDF(請參見圖 1)。在如下部分中,我將對每種類型都加以關注。函數

標量 UDF工具

返回標量值的 UDF 最相似於許多編程語言所引用的做爲函數的內容。它們返回由標量數據類型(例如,integer、varchar(n)、char(n)、money、datetime、bit,等等)組成的單一值。若是用戶定義的數據類型 (UDDT) 基於標量數據類型,UDF 也能夠返回這些數據類型。使用返回內聯或多語句表的 UDF,能夠經過表數據類型返回行集。然而,並不是全部的數據類型均可以從 UDF 中返回。例如,UDF 沒法返回下列數據類型中任何一個的值:text、ntext、image、cursor、或 timestamp。性能

返回標量數據類型的 UDF 能夠用於多種狀況,以使代碼具備更好的可維護性、可重用性和更少的複雜性。當 T-SQL 代碼的相同段在幾個地方(可能由幾個存儲過程和批 SQL 語句)使用時,這會很是有用。例如,假定一個應用程序中的幾個部分都須要查找產品是否必須從新訂購。在每一個須要此操做的地方,代碼能夠檢查從新訂購等級,並將它與庫存量加訂購量的和相比較。然而,由於這個代碼在幾個地方用到,因此能夠改成使用 UDF 以減小代碼塊,並使得萬一須要更改時維護函數更加容易。這樣的 UDF 可能看起來像圖 2 中的代碼,並可使用如下 SQL 語句進行調用:測試

SELECT    ProductID,
    ReorderLevel, 
    UnitsInStock, 
    UnitsOnOrder,
    dbo.fnNeedToReorder(ReorderLevel, UnitsInStock, UnitsOnOrder) 
AS sNeedToReorder
FROM    Products

圖 2 ** 中,fnNeedToReorder UDF 執行計算並返回適當的值。這原本能夠經過 CASE 語句在 SELECT 子句內完成,但若是改成使用 UDF,代碼就會簡潔得多。並且更容易傳播到其餘可能須要相同邏輯的地方。假定一個應用程序中有幾個部分須要肯定是否要從新訂購產品,那麼圖 2 中的 UDF 確實變得有價值,由於它使得當邏輯改變時應用程序更容易維護。例如,從新訂購已經終止的產品並非頗有意義。所以,經過更改 UDF 以說明這個業務規則,能夠在一個地方更改此邏輯(請參見圖 3)並使用下列代碼運行:spa

SELECT    ProductID,
    ReorderLevel, 
    UnitsInStock, 
    UnitsOnOrder,
    dbo.fnNeedToReorder(ReorderLevel, UnitsInStock, UnitsOnOrder, 
                        Discontinued) AS sNeedToReorder
FROM    Products

請注意,UDF 是使用由兩個部分(對象全部者和對象名)組成的名稱調用的。當使用返回標量數據類型值的 UDF 時須要該對象的全部者。能夠受權全部調用 UDF 的地方也必須加以更改,方法是將第四個參數 (Discontinued) 添加到 UDF 中。爲了更容易維護,我能夠從新編寫 UDF,以便使用每一行的 ProductID 來檢索數據自己,如圖 4 所示。這種技術更容易維護,由於它不須要任何調用例程來更改邏輯改變時更改 UDF 的方式,只要能夠從當前 Products 錶行中提取數據便可。然而,要得到這種可維護性,會有性能方面的損失。圖 4 中的 UDF 必須爲每一個從調用例程中返回的行從 Products 表中檢索行。由於調用例程已經從 Products 表中檢索每一個行,因此若是該表有 77 行,則代碼將執行 77 次 SELECT 語句(從主 SELECT 語句中返回每行一次)。雖然每一個 SELECT 都是基於主鍵字段 (ProductID) 進行選擇的,於是會很快,可是當行集很是大或者 SELECT 語句效率較低時,性能就會受到負面影響。圖 4 中的代碼能夠經過如下 SQL 片斷來調用:

SELECT    ProductID,
    ReorderLevel, 
    UnitsInStock, 
    UnitsOnOrder,
    dbo.fnNeedToReorder(ProductId) AS sNeedToReorder
FROM    Products

在 SELECT 語句中使用這個函數的可選方法是,在名爲 NeedToReorder 的 Products 表中建立一個計算所得的列。該列並不定義爲一種數據類型,而是定義爲如圖 3 所示的 fnNeedToReorder UDF 的返回值。要添加此列,我能夠按如下方式更改 Products 表,以指示應計算這個列:

ALTER TABLE Products
    ADD  NeedToReorder AS dbo.fnNeedToReorder(ReorderLevel,   
    UnitsInStock, UnitsOnOrder, Discontinued)

通用 UDF 和嵌套

至此,我已經展現了使用返回標量值的 UDF 解決同一問題的幾種方式。還有其餘有用的 UDF 應用程序,其中包括 T-SQL 中還未準備好可用的函數。一個例子是專用格式化函數。例如,電話號碼一般存儲(不帶格式化字符)在 char(10) 列中,這些列表示區號和電話號碼(假定這是一個美國的號碼)。UDF 能夠用於在格式化結構中檢索電話號碼(請參見圖 5)。所以,檢索和格式化電話號碼像下面同樣簡單:

SELECT dbo.fnCOM_FormatTelephoneNumber ('3335558888')

可使用這種技術建立任何經常使用函數,以增長 SQL Server 中可用函數的數量。另外一個示例是將日期格式化爲帶有前導零的 MM/DD/YYYY 格式的函數:

CREATE FUNCTION fnCOM_StandardDate (@dtDate DATETIME)
    RETURNS VARCHAR(10)
AS
BEGIN
    RETURN 
        dbo.fnCOM_2Digits (CAST(MONTH(@dtDate) AS VARCHAR(2))) + '/' +
        dbo.fnCOM_2Digits (CAST(DAY(@dtDate) AS VARCHAR(2))) + '/' +
        CAST(YEAR(@dtDate) AS VARCHAR(4)) 
END

fnCOM_StandardDate UDF 接受日期時間值,並返回 MM/DD/YYYY 格式的 varchar(10) 值。固然,這很簡單,若是您的應用程序經常須要特定格式,那麼這種技術就可使它更容易維護。在前面的代碼中須要注意的一個關鍵部分是嵌套 UDF 的使用。fnCOM_StandardDate UDF 兩次調用 fnCOM_2Digits UDF(在下一個示例中顯示),每次都在小於 10 的日或月前放置一個前導零。

CREATE FUNCTION fnCOM_2Digits (@sValue VARCHAR(2))
    RETURNS VARCHAR(2)
AS
BEGIN
    IF (LEN(@sValue) < 2)
        SET @sValue = '0' + @sValue 
    RETURN @sValue
END

UDF 能夠互相嵌套,只要其中的 UDF 是先建立的便可。使用嵌套函數的一個 catch 是非肯定性內置函數(例如 getdate 函數),不能在另外一個 UDF 內嵌套(不然會引起 SQL Server 錯誤)。非肯定性函數是用徹底相同的參數調用屢次時可能返回不一樣結果的函數。getdate 函數屬於這一類,由於每次調用時,它會返回新的當前日期和時間。另外一個經常使用的非肯定性內置函數是 NewID 函數。它也是非肯定性的,由於它老是返回惟一的 GUID,因此 NewID 函數一樣不容許在 UDF 內嵌套。

表值 UDF

表值 UDF 的類別中有兩種子類型:返回內聯表值的 UDF 和返回多語句表值的 UDF。返回內聯表的 UDF 經過 SQL Server 表數據類型返回一個行集。它們使用構成函數體的單一 SELECT 語句進行定義。返回內聯表值的 UDF 不能在定義它將返回的表的 SQL SELECT 語句以外包含其餘 T-SQL 邏輯。然而,它們比返回多語句表的 UDF 要容易建立,由於它們沒必要定義要返回的確切表結構。返回內聯表的 UDF 從 SELECT 語句自己推斷行集的結構。所以,UDF 將返回的列由 SELECT 列表中的列肯定。下列代碼顯示了 fnGetEmployeesByCity UDF,它接受一個城市,並返回包含全部員工名字、姓和地址的表:

CREATE FUNCTION fnGetEmployeesByCity (@sCity VARCHAR(30))
    RETURNS TABLE
AS
RETURN
    (
        SELECT    FirstName, LastName, Address
        FROM    Employees
        WHERE    City = @sCity 
    )
GO

能夠從這個返回內聯表值的 UDF 中選擇或者甚至聯接到它,由於它經過表數據類型返回一個行集,以下所示:

SELECT * FROM dbo.fnGetEmployeesByCity('seattle')

請注意,UDF 是使用由對象全部者和對象名這兩個部分組成的名稱調用的。然而,當使用返回表數據類型值的 UDF 時,對象全部者不是必需的(但倒是可接受的)。表值 UDF 很是靈活,由於它們能夠像準備好的和參數化的視圖(若是存在)同樣使用。在表值 UDF 中,您可使用參數,得到準備好的查詢的性能,並從獲得的行集(或本例中的表)中聯接或選擇。

儘管這種 UDF 類型是簡潔的,但重要的是要記住,若是您要向這種 UDF 中添加其餘邏輯,就必須將其轉換成返回多語句表值的 UDF。另外,返回內聯表值的 UDF 在 SELECT 語句中也不能有 ORDER BY 子句(除非它與 TOP 子句一塊兒使用)。

返回多語句表的 UDF 顯式定義要返回的表的結構。它經過在 RETURNS 子句中正肯定義列名稱和數據類型來作到這一點。所以,它會使用比返回內聯表值的 UDF 稍多的代碼來創建表結構。然而,與返回內聯表值的 UDF 相比,它有幾個優勢,其中包括容納更復雜的、更大量的 T-SQL 邏輯塊的功能。顧名思義,返回多語句表值的 UDF 容許多個語句定義 UDF。所以,諸如流控制、分配、遊標、SELECTS、INSERTS、UPDATES 和 DELETES 等語句都是容許的,而且均可以存在於單個 UDF 中。因此,與返回內聯表的 UDF 相反,返回多語句表的 UDF 並不限定於單個 SELECT 語句,也不由止對返回行集進行排序。

圖 6 顯示瞭如何將返回內聯表值的 UDF(我剛纔展現的代碼片斷中的)從新編寫爲返回多語句表值的 UDF。所以,內聯類型能作到的,多語句類型都能作到。返回多語句表的 UDF 的更復雜的用途包括按城市檢索全部員工,但若是沒有客戶與特定的城市相匹配,就返回一個虛行,其中的 Address 字段填寫「在指定的城市中未找到匹配的員工」,如圖 7 中所示。

包裝

還有其餘一些關鍵因素能夠幫助建立任何類型的功能強大的 UDF,其中的一種即是遞歸。UDF 支持遞歸,以便一個 UDF 能夠從自身中調用自身。基本上,遞歸只是嵌套 UDF,惟一不一樣的地方在於您所嵌套的 UDF 正是您所在的 UDF。這在某些狀況中可能很是有用,包括在建立一個必須計算某個因子或評估一個字符串中每一個字符的 UDF 時。在 SQL Server 2000 中,遞歸的限制深度爲 32 層,超出限制會引起錯誤。

還須要指出的是,一個 UDF 能夠綁定到它所引用的基礎對象架構。爲此,UDF 必須使用 WITH SCHEMABINDING 子句來進行建立。若是 UDF 是以這種方式建立的,則當有人試圖更改一個基礎對象架構而沒有先刪除架構綁定時,就會生成並引起錯誤。採用這種選擇將有助於確保不會由於基礎對象架構中的更改而引發意外的 UDF 中斷。

當評估 UDF 時,考慮性能和可維護性之間的平衡是相當重要的。雖然 UDF 能夠減小經常使用代碼的數量(用做經常使用函數庫的一部分),能夠提高更短的代碼塊,而且一般比相同 SQL 邏輯的其餘類型更容易維護,可是,若是不先考慮任何缺點就使用 UDF,這將是不計後果的。

若是性能嚴重下降,那麼使用 UDF 就不是一個好主意。例如,假定有一個執行 SQL SELECT 語句的 UDF,執行該語句須要一秒鐘。若是此 UDF 在 SELECT 或 WHERE 子句中使用,它將爲每一行執行。所以,執行主查詢所花費的時間會急劇增長,這取決於評估和返回的行數以及適當的索引類型這樣的因素。若是是這種狀況,則在使用 UDF 以前,要仔細地權衡所做的選擇並進行一些性能測試。然而,使用執行計算的 UDF(例如圖 3 中所顯示的)幾乎不影響查詢性能。正如任何工具同樣,若是在實際投入以前正確地使用並進行相應地評估,那麼UDF 會提供極大的便利和可維護性。

請將您的問題和給 John 的建議發送到 mmdata@microsoft.com.

John Papa 是一個棒球迷,在夏天的大多數夜晚都與他的兩個小女兒、妻子和忠實的狗 Kadi 一塊兒爲 YanKees 隊加油。他著有幾本關於 ADO、XML 和 SQL Server 的書,並經常在諸如 VSLive 這樣的行業大會上演講。您能夠與他聯繫:mmdata@microsoft.com.

相關文章
相關標籤/搜索