在資料表結構 (table schema) 中,每個欄位除了基本的欄位名稱、型態、大小等資料外,有一個有趣的欄位很特別,就是是否允許 NULL,NULL 這個東西對程式設計師來說是又愛又恨,有時會被它搞得嫑嫑的,但是它有時卻又是一個有必要的存在。
NULL 在資料庫裡面代表的是未知的屬性值 (unknown property value),在一個資料表的結構裡面,雖然大多數的屬性可能是已經有的定義 (源自系統設計的資料結構定義),但有些情況,當資料在某些狀態之下,某些欄位的值用什麼樣的數值替代都沒有意義,這時候就會使用 NULL 來替代。
例如一個訂單資料表,裡面有 OrderId, OrderDate, ShipDate, Status 等欄位,其中 OrderId, OrderDate, Status 在訂單建立時就會有值 (訂單編號、訂單日期、狀態 = OrderStatus.Pending),但 ShipDate 只會在訂單出貨時才會有記錄,在訂單建立時硬給 ShipDate 的值是沒有意義的 (例如給 DateTime.MinValue
),若代入值會給程式帶來困擾時,就會使用 NULL 值來替代,表示這個欄位沒有任何的值。
由於 NULL 值是一種未知的資料,在資料庫裡面的定義基本上對 NULL 的運算都會回傳 NULL 值,各種類型的資料庫對 NULL 的判斷也不盡相同,像 SQL Server 使用的是 ISNULL()
、MySQL 則是用 IFNULL()
、Oracle 是用 NVL/NVL2
等方式,決定當發現值為 NULL 時就代換成某個預設值,當然也可以直接回傳 NULL。
不過,資料庫的 NULL 值和程式裡面的 null 完全不同,資料庫的 NULL 是一個值,以 .NET 來說,NULL 值會用 DBNull
類別來替代,程式設計師可以用 DBNull.Value
來取得;程式的 null 則是指沒有指向任何物件的指標 (也就是 null 指標),對宣告成 null 的變數進行存取會擲回 NullReferenceException
,因此對待 NULL 和 null 的方式必須完全不同,在 IDbDataReader
裡面也有個IsDBNull()
,能用來判斷目前資料列的特定屬性是否為 NULL。在 .NET Framework 中的文件也已經有提到了:
對付資料庫的 NULL 值對程式設計師而言是種挑戰,資料庫裡面存放的資料是實值型態 (Value Type),但是 .NET 的實值型態是不能設成 null 的,因此有不少程式設計值在處理 NULL 的時候被搞得嫑嫑的。
然後一堆奇怪的作法就衍生出來了,像是避免 NULL 值的出現 (例如這篇文),或是使用一個預設值去替代。
string
的問題不大,因為 string
本身是可 null 的型別。基於 NULL 和 null 是不同的東西,.NET 3.5 加入了一個新的物件 Nullable
,並實作了一個泛型類別 Nullable<T>
,擴充了所有 .NET 的實值型資料型態,例如:
Nullable<int> -> int?
Nullable<bool> -> bool?
Nullable<double> -> double?
Nullable<DateTime> -> DateTime?
Nullable
的出現為程式設計師減少了相當程度的判斷,程式設計師再也不需要為了 NULL 值去寫其他的程式判斷,只要在裝資料時先判斷 DBNull
,再使用 Nullable
物件存放值,當 Nullable
裡面的值是 NULL 時,Nullable.HasValue
會回傳 false
,當然也可以用 ==
來判斷。
NULL 的存在是基於資料結構的設計,資料結構的設計又是源自於需求規格或系統分析規格,因此不可以隨便用特定的預設值去替代,這會破壞原本系統設計的結構,當遇到 NULL 值的時候,也應該要用正確的觀念去看待它,而不是隨便的去解讀它的意思,畢竟 NULL 代表的就是 ... 未知。
References:
1. Fundamentals of Database Systems, 6/e
2. 維基百科的 NULL 條目
3. 使用可為 null 的類型