空值是資料庫開發繞不開的議題。本文聚焦三大實務場景:彙總函數計數差異、空值參與運算的陷阱、外鍵條件約束與空值的互動,提供具體解決方案與程式碼驗證。
議題一:COUNT(*) vs COUNT(欄位) vs COUNT(ISNULL(欄位,值))
行為差異
| 函數 | 統計對象 | 空值處理 |
COUNT(*) | 所有資料列 | 包含空值列 |
COUNT(欄位) | 該欄位非空值 | 忽略空值 |
COUNT(ISNULL(欄位,替代值)) | 所有資料列 | 空值被替代值取代 → 計入統計 |
驗證腳本
SET NOCOUNT ON
GO
CREATE TABLE xCount (
pkey1 INT IDENTITY NOT NULL CONSTRAINT pk_xCount PRIMARY KEY,
Col1 INT NULL
)
GO
INSERT xCount (Col1) VALUES (10), (15), (20), (NULL)
GO
SELECT
AVG(Col1) AS AvgWithoutIsNull,
AVG(ISNULL(Col1,0)) AS AvgWithIsNull,
COUNT(Col1) AS CountCol1,
COUNT(ISNULL(Col1,0)) AS CountIsNullCol1,
COUNT(*) AS CountStar
FROM xCount
GO
-- 結果:
-- AvgWithoutIsNull = 15 (10+15+20)/3
-- AvgWithIsNull = 11 (10+15+20+0)/4
-- CountCol1 = 3 (忽略 NULL)
-- CountIsNullCol1 = 4 (NULL→0 計入)
-- CountStar = 4 (所有列)
DROP TABLE xCount
GO
實務建議
- 需統計**所有列** →
COUNT(*)
- 需統計**該欄位有值的列** →
COUNT(欄位)
- 需統計**所有列但空值視為特定值** →
COUNT(ISNULL(欄位,值))
- 彙總函數 (
AVG, SUM, MIN, MAX) 皆**自動忽略空值**,若需空值參與運算必須 ISNULL/COALESCE
議題二:空值參與運算與比較的陷阱
三值邏輯真理表
| 運算 | 結果 | 說明 |
NULL + 100 | NULL | 算術運算任一操作數為 NULL → 結果 NULL |
NULL = NULL | UNKNOWN (非 TRUE) | 比較運算任一操作數為 NULL → UNKNOWN,WHERE/IF 視為 FALSE |
NULL <> 'abc' | UNKNOWN | 同理 |
NULL IS NULL | TRUE | 專用判斷語法 |
COALESCE(NULL, 'a', 'b') | 'a' | 回傳第一個非 NULL 值 |
常見錯誤與修正
-- 錯誤:想找出沒有電話的客戶
SELECT * FROM Customers WHERE Phone <> '' -- 遺漏 Phone IS NULL 的列!
-- 正確:
SELECT * FROM Customers WHERE Phone IS NULL OR Phone = ''
-- 或
SELECT * FROM Customers WHERE ISNULL(Phone, '') = ''
議題三:外鍵條件約束與空值
規則
- 外鍵欄位**允許 NULL** (除非額外加
NOT NULL)
- 外鍵值為 NULL 時,**不檢查** 參考資料表是否存在對應值
- 這符合業務邏輯:「尚未指派/不適用」不應強制關聯
驗證範例
CREATE TABLE Departments (
DeptID INT PRIMARY KEY,
DeptName VARCHAR(50)
)
CREATE TABLE Employees (
EmpID INT PRIMARY KEY,
EmpName VARCHAR(50),
DeptID INT NULL, -- 允許 NULL
CONSTRAINT FK_Emp_Dept FOREIGN KEY (DeptID) REFERENCES Departments(DeptID)
)
INSERT INTO Departments VALUES (1, 'IT'), (2, 'HR')
INSERT INTO Employees VALUES (101, 'Alice', 1), (102, 'Bob', NULL) -- Bob 無部門 → 允許
-- INSERT INTO Employees VALUES (103, 'Carol', 99) -- 違反 FK:DeptID=99 不存在 → 錯誤
業務場景:強制關聯 vs 可選關聯
| 業務需求 | 外鍵定義 | 範例 |
| 每筆訂單必須有客戶 | CustID INT NOT NULL REFERENCES Customers | 訂單-客戶 (強制) |
| 員工可能暫無部門 | DeptID INT NULL REFERENCES Departments | 員工-部門 (可選) |
| 產品可能暫無分類 | CategoryID INT NULL REFERENCES Categories | 產品-分類 (可選) |
綜合最佳實務
- DDL 階段:依業務語意決定
NULL/NOT NULL,外鍵同步設定
- 查詢階段:
COUNT(*) 統計列數,COUNT(欄位) 統計非空值,混用 ISNULL/COALESCE 控制運算行為
- 應用層:DTO/實體類別對應欄位用
Nullable(Of T) (VB) / T? (C#) 明確表達可空性
- 報表層:統一空值顯示文字 (如「N/A」、「未提供」、「--」),避免空白欄位造成誤讀