在資料庫設計中,CHECK 約束常被開發者忽視,許多本應由宣告式約束處理的業務規則,卻被硬塞進觸發器(Trigger)裡。這不僅增加代碼維護成本,更在效能與交易管理上埋下隱患。本文透過實戰案例,示範如何用 CHECK 約束優雅地實現單欄、跨欄、甚至條件式業務驗證。
ALTER TABLE dbo.Employee
ADD CONSTRAINT CK_Employee_Salary
CHECK (Salary BETWEEN 30000 AND 100000);
嘗試插入 110,000 會直接被引擎拒絕,錯誤訊息清晰指向約束名稱,前端無需額外捕獲邏輯。
業務規則: 主管獎金比例 ≥ 5%,非主管 < 5%。
錯誤嘗試:
CHECK (Is_Manager = 1 AND BonusPct >= 5.00)
此寫法強制所有員工必須是主管且獎金 ≥ 5%,違反業務意圖。
正確寫法 — 使用 CASE 表達式實現條件邏輯:
CHECK (
CASE
WHEN Is_Manager <> 1 AND BonusPct >= 5.00 THEN 1 -- 非主管卻領高獎金 → 違規
ELSE 0
END = 0
)
邏輯解讀:只有「非主管且獎金 ≥ 5%」才回傳 1(違規),其他情況回傳 0(合規)。
若 Is_Manager 允許 NULL,上述約束會因 NULL <> 1 結果為 UNKNOWN,導致 CASE 走向 ELSE 0,讓 NULL 職位員工繞過檢查。
修正版:
CHECK (
CASE
WHEN Is_Manager IS NULL AND BonusPct >= 5.00 THEN 1
WHEN Is_Manager <> 1 AND BonusPct >= 5.00 THEN 1
ELSE 0
END = 0
)
或從根源解決:ALTER TABLE ... ALTER COLUMN Is_Manager BIT NOT NULL DEFAULT 0。
有人嘗試在 CHECK 中呼叫標量函數掃描整張表(如統計總獎金上限),SQL Server MVP David Portas 指出:這會導致並發嚴重下降、鎖升級、甚至死鎖。**CHECK 約束只能驗證「當前資料列」的邏輯真偽,不可跨列、跨表運算。**
CHECK 約束是資料庫引擎原生提供的「業務規則執行器」,適用於:
觸發器請留給:跨表驗證、稽核軌跡、複雜業務流程控制。工具用對地方,資料庫才能穩健演進。