UDF 概念、原理、優缺點、UDF 的分類sql
詳細講述3種 UDF 的建立、調用方法以及注意事項數據庫
UDF:user-defined functions,用戶自定義函數的簡稱。架構
UDF 是一個例程,它接受參數、執行操做並返回該操做的結果。根據定義,結果能夠是標量值(單個)或表。函數
該函數一旦誤用會產生潛在的性能問題。必須針對WHERE子句的每一行執行的任何函數,不論是用戶定義的函數仍是系統函數,都將減慢執行速度。性能
UDF 主要有 3 種類型(SQL Server Management Studio 把內聯表值函數與多語句表值函數放到了一個組中):優化
標量函數是返回一個具體值的函數。函數能夠接收多個參數、執行計算而後返回一個值。返回值經過RETURN命令返回。用戶定義的函數中的每一個可能代碼路徑都以RETURN命令結尾。spa
標量函數能夠運用於 SQL Server 中的任何表達式,甚至在 CHECK 約束的表達式中也可使用(但不推薦這種用法)。code
標量函數必須是肯定性的,也就是說標量函數必須反覆地爲相同的輸入參數返回相同的值。所以,如newid()函數和rand()函數不容許出如今標量函數中。不容許用戶定義標量函數更新數據庫、調用存儲過程或調用DBCC命令,惟一的例外是能夠更新表變量。用戶定義函數不能返回BLOB(二進制大型對象)數據,如text、next、timestamp和image數據類型變量。也不能返回表變量可cursor數據類型。對於錯誤處理,UDF 也不包含 TRY...CATCH 或 RAISERROR。對象
UDF 能夠調用嵌套深度爲 32 層之內的其餘用戶定義函數,或者遞歸調用本身到 32 層的深度。固然,這只是理論限制,嵌套函數會嚴重影響性能,應儘量避免使用嵌套函數。blog
1 CREATE FUNCTION FunctionName (InputParameters) 2 RETURNS DataType 3 AS 4 BEGIN 5 Code; 6 RETURN Expression; 7 END;
InputParameters 輸入參數包含數據類型定義。參數能夠設置默認值(Parameter = default ),須要注意的是在 UDF 中有默認值的參數並不能成爲可選參數,爲在調用函數時請求到默認值,須要把關鍵字 DEFAULT 傳遞到函數的默認值參數位置。
示例1:下面的 UDF 執行一個簡單的數學計算,其中第二個參數帶有默認值。
CREATE FUNCTION dbo.ufnCalculate (@Numer_a numeric(5,2), @Numer_b numeric(5,2) = 1.0) RETURNS numeric(5,2) AS BEGIN RETURN @Numer_a / @Numer_b ; END; GO select dbo.ufnCalculate(15.3 , 6.54), dbo.ufnCalculate(9.0 , DEFAULT); 結果: ------ ------ 2.38 9.00
示例2:計算並返回某個時間所在月份的天數。
CREATE FUNCTION [dbo].[GetMonthDay](@date datetime) RETURNS int AS BEGIN DECLARE @date1 datetime SELECT @date1 =Dateadd(MM,1,@date) RETURN day(Dateadd(DD,-day(@date1),@date1)) END;
在接受單值的表達式中,標量函數可用於任何地方。用戶定義的標量函數必須經過一個最少有兩部分的名稱(全部者.函數名)來調用。
下面的腳本演示了在數據庫的訂單表中調用示例2中的函數及其返回值。
SELECT S.BIL_DD,dbo.GetMonthDay(BIL_DD) as DAYS_M FROM Orders S 結果 BIL_DD DAYS_M ------ ------ 2019-01-31 31 2019-02-15 28
與視圖類似,內聯表值函數也是爲一個存儲的SELECT語句封裝。內聯表值函數保留了視圖的優勢,還添加了一些參數。
內聯表值用戶定義函數沒有BEGIN / END主體。SELECT語句是做爲一個虛擬數據表返回的:
CREATE FUNCTION FunctionName (InputParameters) RETURNS Table AS RETURN (Select Statement);
示例:下面的示例返回某個客戶所訂購產品的彙總狀況。
CREATE FUNCTION dbo.ufnGetProductTotalByCust (@custNo varchar (10)) RETURNS Table AS RETURN( SELECT H.CUS_NO,B.PRD_NO,SUM(B.QTY) as TOTAL_PRD FROM TF_POS AS B --訂單貨品明細表 LEFT JOIN MF_POS AS H --訂單客戶信息表 ON H.OS_NO=B.OS_NO WHERE H.CUS_NO=@custNo GROUP BY H.CUS_NO,B.PRD_NO ); GO
經過dbo.ufnGetProductTotalByCust查詢客戶代號爲"CT060228" 的產品彙總數據,函數出如今SELECT語句的FROM部分:
SELECT PRD_NO,TOTAL_PRD FROM dbo.ufnGetProductTotalByCust('CT060228') ORDER BY PRD_NO DESC
返回結果(部分):
PRD_NO TOTAL_PRD ------------ ------------------ 10910030006 5792.00000000 10910040003 10776.00000000 10912060014 11442.00000000 10913040009 9276.00000000 11410030028 900.00000000 ......
與視圖相比,內聯表值函數的優點在於其可使用參數。而視圖不包含參數,並且在運行時想要限制結果須要把 WHERE 子句添加到調用視圖的 SELECT 語句中來實現。
示圖的調用示例,假設已經存在視圖 dbo.vwProductTotalByCust,調用視圖時,在 SELECT 語句中添加了一個 WHERE 子句限制:
SELECT * FROM dbo.vwProductTotalByCust WHERE cus_no='CT060228'
表值用戶定義函數的關聯可使用 APPLY 命令,從而使 UDF 針對由主查詢處理的每一行接受一個不一樣的參數值。
APPLY 命令具備兩種形式。最普通的一種形式是 CROSS APPLY,它運行起來更像一個內聯接。CROSS APPLY 命令聯接主查詢的數據與來自用戶自定義函數的任意表值數據集。若是未從UDF 返回數據,那麼主查詢的行也不能返回,以下圖的例子所示:
SELECT T.PRD_NO,P.NAME,T.TOTAL_PRD FROM PRDT P --產品資料表 CROSS APPLY dbo.ufnGetProductTotalByCust('CT060228') T ORDER BY T.PRD_NO DESC 結果:
PRD_NO NAME TOTAL_PRD ------------ ------------ ------------------------ 10910030006 3pcs storage jar 5792.00000000 10910040003 2pcs storage jar 10776.00000000 10912060014 4pcs spice jar 11442.00000000 10913040009 6pcs spice jar 9276.00000000 11410030028 salad dressing 900.00000000 ......
CROSS APPLY 的第2種形式是 OUTER APPLY 命令,操做上與左聯接類似。這種形式下,主查詢的行將包含在結果集中,而無論 UDF 返回的虛擬表是否爲空。
標量函數和內聯表值函數可生成完成相同的結果集,那麼這二者的區別是什麼呢?
標量函數針對每一行運行一次,而內聯表值函數由查詢優化器處理,很是相似於視圖。由於內聯表值函數會由查詢優化器進行處理,因此建議儘量優先使用內聯表值函數,而非標量函數。
架構綁定阻止更改或刪除函數所依賴的任何對象。若是架構綁定函數引用了某個表A,那麼表A不可更改或刪除,但能夠將列添加到表A。
架構綁定的方法:在函數建立語句的 RETURNS 以後和 AS 以前添加選項 WITH SCHEMA BINDING,以下所示:
1 CREATE FUNCTION FunctionName (InputParameters) 2 RETURNS DataType 3 WITH SCHEMA BINDING 4 AS 5 BEGIN 6 Code; 7 RETURN Expression; 8 END;
可使用ALTER修改函數,使其再也不包含架構綁定,以即可以修改引用對象。
將標量函數與內聯表值函數的功能結合起來就構成了複雜的多語句表值函數。
特徵:這種類型的函數建立了一個表變量,將它置於代碼中,而後從函數返回,以便能在SELECT語句中使用。
優勢:能夠代碼內生成複雜結果集,以便在SELECT語句中使用,在查詢中構建複雜邏輯,並解決那些沒有遊標就很難解決的問題。
建立多語句表值函數的語法與建立標量函數的語法類似:
CREATE FUNCTION FunctionName (InputParamenters) RETURNS @TableName TABLE (columns) AS BEGIN; Code to populate table variable RETURN; END;
示例:下面的過程構建了一個返回基本結果集的多語句表值用戶定義的函數,函數首先在 CREATE FUNCTION 頭中建立了一個名爲 @PruductList 的表變量,在函數體中,兩個 INSERT 語句置於@ProductList 表變動中,若是函數執行完畢,表變動 @ProductList 將做爲函數的輸出傳回。
ufnGetProductsAndOrderTotals函數返回Product表中的每一個產品和每一個產品的訂單總數。
CREATE FUNCTION ufnGetProductsAndOrderTotals() RETURNS @ProductList TABLE (ProductID int, ProductName nvarchar(100), TotalOrders int) AS BEGIN; INSERT @ProductList(ProductID,ProductName) SELECT ProductID,Name FROM Product; UPDATE p1 SET TotalOrders = (SELECT sum(sod.OrderQty) FROM @ProductList ip1 JOIN SalesOrderDetail sod ON ip1.ProductID = sod.ProductID WHERE ip1.ProductID = p1.ProductID) FROM @ProductList p1 ; RETURN; END;
只須要在SELECT語句的FROM部分引用該函數,便可查詢到函數的執行結果。下面的代碼檢索ufnGetProductsAndOrderTotals函數的結果:
SELECT ProductID,ProductName,TotalOrders FROM ufnGetProductsAndOrderTotals() ORDER BY TotalOrders DESC
結果集以下:
ProductID ProductName TotalOrders ------------ ------------------- -------------- 715 4 PCS Storage Jar 8311 780 6 PCS Spice Jar 6800 ......
無疑 UDF 爲咱們的 T-SQL 選項添加了靈活性,但若是這些函數運用不當,帶來的性能缺陷也是很嚴重的。UDF 並不能成爲子查詢、視圖或存儲過程的替代物。
從上面的示例,咱們不難看出,三種類型函數能夠產生基本相同的結果集,實踐中能夠將本身的函數定義爲其種任意一種。
若是選擇 UDF 來封裝查詢邏輯,則建議遵循下面的這些基本原則:
爲方便咱們的T-SQL更易於閱讀更容易排除故障,咱們應該確保爲全部的 UDF 建立某種統一類型的命名約束。最經常使用的方法是採用名稱前綴,更進一步,可讓前綴代表 UDF 是標量函數、內聯表值函數仍是多語句表值函數。例如,返回每一個產品類別的月平均銷售額的內聯表值函數,能夠將其命名爲 udfAvgMonSalesPerCategory 或 ifn_AvgMonSalesPerCategory。