SQL SERVER 用戶自定義函數(UDF)深入解析

来源:https://www.cnblogs.com/idreamo/archive/2019/05/21/10767923.html
-Advertisement-
Play Games

本文內容概要: UDF 概念、原理、優缺點、UDF 的分類 詳細講述3種 UDF 的創建、調用方法以及註意事項 基本原理: UDF:user-defined functions,用戶自定義函數的簡稱。 UDF 是一個常式,它接受參數、執行操作並返回該操作的結果。根據定義,結果可以是標量值(單個)或表 ...


本文內容概要:

  1. UDF 概念、原理、優缺點、UDF 的分類

  2. 詳細講述3種 UDF 的創建、調用方法以及註意事項

  3. UDF 的實踐建議

基本原理:

UDF:user-defined functions,用戶自定義函數的簡稱。

UDF 是一個常式,它接受參數、執行操作並返回該操作的結果。根據定義,結果可以是標量值(單個)或表。

UDF 的優點:

  1. UDF 可以把複雜的邏輯嵌入到查詢中。UDF 可以為複雜的表達式創建新函數。
  2. UDF 可以運用在一個表達式或 SELECT 語句的 FROM 子句中,並且還可以綁定到架構。此外,UDF 還可以接受參數。UDF 有助於實施一致性和可重用性。

UDF 的缺點:

該函數一旦誤用會產生潛在的性能問題。必須針對WHERE子句的每一行執行的任何函數,不管是用戶定義的函數還是系統函數,都將減慢執行速度。

UDF 的類型:

        UDF 主要有 3 種類型(SQL Server Management Studio 把內聯表值函數與多語句表值函數放到了一個組中):

  1. 標量函數
  2. 內聯表值函數
  3. 多語句表值函數

一、標量函數


標量函數是返回一個具體值的函數。函數可以接收多個參數、執行計算然後返回一個值。返回值通過RETURN命令返回。用戶定義的函數中的每個可能代碼路徑都以RETURN命令結尾。

標量函數可以運用於 SQL Server 中的任何表達式,甚至在 CHECK 約束的表達式中也可以使用(但不推薦這種用法)。

  • 函數限制

標量函數必須是確定性的,也就是說標量函數必須反覆地為相同的輸入參數返回相同的值。因此,如newid()函數和rand()函數不允許出現在標量函數中。不允許用戶定義標量函數更新資料庫、調用存儲過程或調用DBCC命令,唯一的例外是可以更新表變數。用戶定義函數不能返回BLOB(二進位大型對象)數據,如text、next、timestamp和image數據類型變數。也不能返回表變數可cursor數據類型。對於錯誤處理,UDF 也不包含 TRY...CATCH 或 RAISERROR。

UDF 可以調用嵌套深度為 32 層以內的其他用戶定義函數,或者遞歸調用自己到 32 層的深度。當然,這隻是理論限制,嵌套函數會嚴重影響性能,應儘可能避免使用嵌套函數。

  • 創建方法

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 的實踐建議

無疑 UDF 為我們的 T-SQL 選項添加了靈活性,但如果這些函數運用不當,帶來的性能缺陷也是很嚴重的。UDF 並不能成為子查詢、視圖或存儲過程的替代物。

從上面的示例,我們不難看出,三種類型函數可以產生基本相同的結果集,實踐中可以將自己的函數定義為其種任意一種。

建議一:性能最優化

如果選擇 UDF 來封裝查詢邏輯,則建議遵循下麵的這些基本原則:

  1. 相對於多語句表值函數,儘可能優先選擇內聯表值函數;
  2. 儘量避免使用標量函數,儘可能使用內聯表值函數取代它;
  3. 如果需要使用多語句表值函數,則對比一下存儲過程是不是更合適的解決文案。雖然需要花更多的時間,但考慮長期的性能影響,還是值得的。

建議二:命名一致性

為方便我們的T-SQL更易於閱讀更容易排除故障,我們應該確保為所有的 UDF 創建某種統一類型的命名約束。最常用的方法是採用名稱首碼,更進一步,可以讓首碼表明 UDF 是標量函數、內聯表值函數還是多語句表值函數。例如,返回每個產品類別的月平均銷售額的內聯表值函數,可以將其命名為 udfAvgMonSalesPerCategory 或 ifn_AvgMonSalesPerCategory。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • -U表示用戶-h表示主機-p表示埠號-t表示表名-f表示備份後的sql文件的名字-d表示要恢複數據庫名 一、打開cmd 進入postgresql安裝路徑下的bin文件夾,以我的為例: -U表示用戶-h表示主機-p表示埠號-t表示表名-f表示備份後的sql文件的名字-d表示要恢複數據庫名 一、打開 ...
  • MS SQL Server的COALESCE函數是從一系列表達式中返回第一個NOT NULL的值。 檢查[B],[Q],[S],[T],[U]的值: 檢查順序[B]->[Q]->[S]->[T]->[U],只要一遇上NOT NULL時,即刻返回。 IF OBJECT_ID('tempdb.dbo.# ...
  • 清理監聽日誌處理的方法1:首先停止監聽服務進程(tnslsnr)記錄日誌。lsnrctl ?set log_status off;? 2:將監聽日誌文件(listener.log)複製一份,以listener.log.yyyymmdd格式命名cp listener.log listener.log. ...
  • 數據完整性: 數據完整性要求資料庫中的數據具有“準確性”。如果兩個更多或者更多的表由於其存儲的信息相關聯,那麼只要修改了其中一個表,與之相關的所有表都要做出相應的修改,如果不這樣做,存儲的數據會不再準確,也就失去了數據完整性。 為了實現數據完整性,資料庫需要做一下兩方面的工作。 (1)檢驗每行數據是 ...
  • 前陣子有一個網友在群里問了一個關於Oracle資料庫的TX鎖問題,問題原文如下: 請教一個問題: 兩個會話執行不同的delete語句,結果都是刪除同一個行。先執行的會話里where條件不加索引走全表掃描,表很大,執行很慢;後執行的用where條件直接用rowid進行delete。 Oracle的什麼... ...
  • 最近在雲伺服器上安裝mysql 啟動時報錯了,從錯誤中可以看出,定位在pid文件上,有三種解決方案 1、重啟伺服器:因為伺服器更新時,可能會禁用某些守護進程,重啟後即可恢復 2、刪除配置文件,重啟試試 先備份一下 mv /etc/my.cnf /etc/my.cnf.backup ,重啟mysql服 ...
  • 剛有網友問及,第一列都需要由每一行的數據使用逗號串連起來。 Insus.NET先讓其參考: 《數據表列值轉換為逗號分隔字元串》https://www.cnblogs.com/insus/p/10848578.html 動態實現,因此Insus.NET試寫: CREATE TABLE #t ([qlr ...
  • 寫在前面:筆者之前也有一些MySQL方面的筆記,其中部分內容來自極客時間中丁奇老師的課程。後經園友提醒,這個做法確實不太好。之後我仍會繼續更新一下MySQL方面的學習記錄,在自己理解之後用自己的方式記錄下來。學習與記錄,也是我寫博客的初衷。 概述: 分區功能並不是在存儲引擎層完成的,因此很多存儲引擎 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...