游標 (CURSOR) 屬於逐列處理 (RBAR),在集合導向的 SQL Server 中效能極差。使用者定義函數 (UDF) 結合集合操作,可將複雜邏輯封裝、在單一陳述式中完成運算,大幅提升吞吐量。本文介紹兩大實戰模式。
場景:更新 TABLEFMECA 表的 Cvalue_M 欄位,需依 TASKPHASEID 關聯 TABLETASKPHASE、TABLERELATION、TABLETASK 多表查詢計算工時比例。
CREATE FUNCTION dbo.FUN_GETTIME (@TASKPHASEID INT)
RETURNS FLOAT
AS
BEGIN
DECLARE @TASKID INT, @HOUR FLOAT, @PERCENT FLOAT, @RETURN FLOAT;
IF @TASKPHASEID IS NULL RETURN 0.0;
SELECT @TASKID = TASKID, @PERCENT = ISNULL(WORKPERCENT, 0) / 100.0
FROM TABLETASKPHASE WHERE ID = @TASKPHASEID;
SELECT @HOUR = ISNULL(TASKTIME, 0) FROM TABLETASK WHERE ID = @TASKID;
SET @RETURN = @HOUR * @PERCENT;
RETURN @RETURN;
END;
CREATE PROCEDURE dbo.PROC_CALCCA @ROID INT
AS
BEGIN
UPDATE TABLEFMECA
SET Cvalue_M = ISNULL(MODERATE, 0) * ISNULL(FMERATE, 0) * ISNULL(B.BASFAILURERATE, 0) * dbo.FUN_GETTIME(C.ID)
FROM TABLEFMECA
INNER JOIN TABLERELATION B ON B.ID = @ROID
INNER JOIN TABLETASKPHASE C ON TABLEFMECA.TASKPHASEID = C.ID
WHERE TABLEFMECA.ROID = @ROID;
DECLARE @CA FLOAT;
SELECT @CA = SUM(ISNULL(Cvalue_M, 0)) FROM TABLEFMECA WHERE ROID = @ROID;
UPDATE TABLERELATION SET CRITICALITY = @CA WHERE ID = @ROID;
END;
優勢:單一掃描完成多表關聯計算,避免游標循環的上下文切換開銷。
場景:依 PARTID、ENID、SOURCEID、QUALITYID、COUNT 多條件查詢失效率,再乘以 WORKPERCENT 加總。
CREATE FUNCTION dbo.FUN_RATE (
@PARTID INT, @ENID INT, @SOURCEID INT, @QUALITYID INT, @COUNT INT
)
RETURNS FLOAT
AS
BEGIN
DECLARE @QXS FLOAT, @G FLOAT, @RATE FLOAT;
IF @ENID IS NULL OR @PARTID IS NULL OR @SOURCEID IS NULL OR @QUALITYID IS NULL
RETURN 0.0;
SELECT @QXS = ISNULL(XS, 0) FROM TABLEQUALITY WHERE ID = @QUALITYID;
SELECT @G = ISNULL(FRATE_G, 0)
FROM TABLEFAILURERATE
WHERE SUBKINDID = @PARTID
AND ENID = @ENID
AND DATASOURCEID = @SOURCEID
AND ( (ISNULL(MINCOUNT, 0) <= ISNULL(@COUNT, 0) AND ISNULL(MAXCOUNT, 0) >= ISNULL(@COUNT, 0))
OR ISNULL(@COUNT, 0) > ISNULL(MAXCOUNT, 0) );
SET @RATE = ISNULL(@QXS * @G, 0);
RETURN @RATE;
END;
CREATE PROCEDURE dbo.PROC_FAULTRATE
@PARTID INT, @QUALITYID INT, @SOURCEID INT, @COUNT INT,
@ROID INT, @GRADE INT, @RATE FLOAT = 0 OUTPUT
AS
BEGIN
DECLARE @TASKID INT;
SET @RATE = 0.0;
SELECT @TASKID = ISNULL(TASKPROID, -1)
FROM TABLERELATION
WHERE ID = (SELECT PID FROM TABLERELATION WHERE ID = @ROID);
IF @TASKID = -1 OR @GRADE = 1 BEGIN SET @RATE = 0; RETURN; END;
SELECT @RATE = SUM( dbo.FUN_RATE(@PARTID, ENID, @SOURCEID, @QUALITYID, @COUNT) * ISNULL(WORKPERCENT, 0) / 100.0 )
FROM TABLETASKPHASE
WHERE TASKID = @TASKID;
END;
關鍵點:SUM( dbo.FUN_RATE(...) * ... ) 讓聚合與逐列計算在單一查詢中完成,SQL Server 8.0+ 甚至可內聯標量 UDF (Inline Scalar UDF) 進一步消除函數呼叫開銷。
當需要回傳多列多行、或參與 JOIN 時,內嵌表值函數 (Inline TVF) 是最佳選擇:
CREATE FUNCTION dbo.GetTaskPhasesByTaskID (@TASKID INT)
RETURNS TABLE
AS RETURN
SELECT ID, TASKID, WORKPERCENT
FROM TABLETASKPHASE
WHERE TASKID = @TASKID; -- 單一 SELECT,優化器可內聯展開
使用方式如同檢視表:
SELECT tp.*, f.ID AS FaultID
FROM TABLETASK t
CROSS APPLY dbo.GetTaskPhasesByTaskID(t.ID) tp
LEFT JOIN FAULTS f ON f.TASKPHASEID = tp.ID;
| 模式 | 適用場景 | 效能 | 注意事項 |
|---|---|---|---|
| 標量 UDF + UPDATE/聚合 | 單值計算、跨表查找 | 良好 (8.0+ 內聯後極佳) | 避免在 WHERE/JOIN 條件中使用 (阻礙 SARGable) |
| 內嵌 TVF (Inline TVF) | 回傳結果集、參與 APPLY/JOIN | 最佳 (視圖級最佳化) | 僅能包含單一 SELECT;多語句 TVF 效能較差 |
| 多語句 TVF | 複雜邏輯需暫存表 | 一般 | 有物化開銷,慎用 |
| 游標 | 極少數需逐列邏輯 (如動態 SQL 迴圈) | 極差 | 最後手段 |
「能用單一陳述式解決,就不用游標。」將商業邏輯封裝於 UDF/TVF,搭配集合操作,是 SQL Server 高效能開發的核心心法。SQL Server 2019+ 的標量 UDF 內聯 功能更讓此模式如虎添翼。