開發者常面臨將一系列具備流程控制(變數宣告、條件判斷、暫存表操作)的 T-SQL 陳述式,重構為可重複呼叫的預存程序。本文透過實際案例,示範如何正確處理區域變數作用域、簡化參數傳遞,並延伸至多表 LEFT JOIN 查詢技巧。
原始碼執行邏輯:
@TeacherID、@a~@eTeacher 表查詢五門課程代碼至變數classname 並依序插入非 NULL 的課程代碼IF 判斷導致程式碼結構僵化、難以維護-- 錯誤示範:將區域變數誤宣告為輸入參數
ALTER PROCEDURE Pr_GetClass
@TeacherID int,
@a char(50), @b char(50), @c char(50), @d char(50), @e char(50)
AS
BEGIN
-- 邏輯同原始腳本
END
問題:呼叫端需傳入 6 個參數,實際上只需 @TeacherID;@a~@e 應為程序內部區域變數,而非介面契約。
CREATE PROCEDURE Pr_GetClass
@TeacherID int
AS
BEGIN
-- 區域變數宣告
DECLARE @a char(50), @b char(50), @c char(50), @d char(50), @e char(50);
-- 查詢課程代碼
SELECT
@a = DRClass1, @b = DRClass2, @c = DRClass3,
@d = DRClass4, @e = DRClass5
FROM Teacher
WHERE TeacherID = @TeacherID;
-- 重建暫存表
IF OBJECT_ID('tempdb..#classname') IS NOT NULL
DROP TABLE #classname;
CREATE TABLE #classname (classname char(50));
-- 簡化插入邏輯:使用 COALESCE 或 UNION ALL 替代巢狀 IF
INSERT INTO #classname (classname) VALUES (@a);
IF @b IS NOT NULL INSERT INTO #classname (classname) VALUES (@b);
IF @c IS NOT NULL INSERT INTO #classname (classname) VALUES (@c);
IF @d IS NOT NULL INSERT INTO #classname (classname) VALUES (@d);
IF @e IS NOT NULL INSERT INTO #classname (classname) VALUES (@e);
SELECT * FROM #classname;
END
GO
改進重點:
@TeacherIDBEGIN 區塊內,符合作用域規則#classname 避免命名衝突IF,提升可讀性-- 使用 UNION ALL 將非 NULL 值一次性插入
INSERT INTO #classname (classname)
SELECT v.classname FROM (
SELECT @a UNION ALL SELECT @b UNION ALL SELECT @c
UNION ALL SELECT @d UNION ALL SELECT @e
) AS v(classname)
WHERE v.classname IS NOT NULL;
此寫法更符合 SQL 集合運算特性,效能優於逐列判斷。
需求:查詢所有 KJ 記錄,帶出對應教師姓名與學院名稱。
SELECT
T1.KJName,
T2.TeacherName,
T3.CollageName
FROM KJ T1
LEFT JOIN Teacher T2 ON T2.TeacherID = T1.TeacherID
LEFT JOIN Collage T3 ON T3.CollageID = T2.CollageID;
關鍵點:
LEFT JOIN 保留 KJ 主表所有列,即使教師或學院資料缺失T1/T2/T3 簡化欄位引用T2.TeacherID = T1.TeacherID| 面向 | 建議 |
|---|---|
| 參數設計 | 僅暴露業務必要參數,內部變數於 BEGIN 區塊宣告 |
| 暫存物件 | 優先使用區域暫存表 #temp,避免全域衝突 |
| 流程控制 | 能用集合運算(UNION/CASE/COALESCE)替代逐列 IF 則優先採用 |
| 多表查詢 | 採用 LEFT JOIN 保留主表完整性,必要時加上 WHERE 過濾 |
| 可維護性 | 複雜邏輯拆分為多個 CTE 或子查詢,單一職責原則 |