目標是讓 SQLite 以符合標準的方式處理 NULL。但 SQL 標準中關於如何處理 NULL 的說明似乎很含糊。從標準文件中無法明確得知在所有情況下應如何處理 NULL。
因此,並未遵循標準文件,而是測試了各種流行的 SQL 引擎,以了解它們如何處理 NULL。目的是讓 SQLite 像所有其他引擎一樣運作。由志工開發了一個 SQL 測試腳本,並在各種 SQL RDBMS 上執行,並根據測試結果推論出每個引擎如何處理 NULL 值。原始測試於 2002 年 5 月執行。測試腳本的副本可在本文檔末尾找到。
SQLite 最初的編碼方式是,下表中所有問題的答案都是「是」。但對其他 SQL 引擎執行的實驗顯示,它們都沒有這樣運作。因此,SQLite 已修改為與 Oracle、PostgreSQL 和 DB2 相同。這包括讓 NULL 在 SELECT DISTINCT 陳述式和 SELECT 中的 UNION 運算子中不加區別。NULL 在 UNIQUE 欄位中仍然是不同的。這似乎有點武斷,但與其他引擎相容的願望大於此反對意見。
可以讓 SQLite 將 NULL 視為 SELECT DISTINCT 和 UNION 的不同。為此,應變更 sqliteInt.h 原始檔中 NULL_ALWAYS_DISTINCT #define 的值並重新編譯。
2003-07-13 更新:自此文件最初撰寫以來,已更新部分受測的資料庫引擎,使用者也很親切地寄來修正內容給下方的表格。原始資料顯示出各種不同的行為,但隨著時間推移,行為範圍已趨近於 PostgreSQL/Oracle 模型。唯一的重大差異是 Informix 和 MS-SQL 都將 NULL 視為 UNIQUE 欄位中的不特定值。
NULL 在 UNIQUE 欄位中為特定值,但在 SELECT DISTINCT 和 UNION 中卻為不特定值,這一點持續令人困惑。NULL 似乎應該在所有地方都為特定值,或在所有地方都不是特定值。SQL 標準文件建議 NULL 應在所有地方都為特定值。然而截至目前為止,沒有受測的 SQL 引擎將 NULL 視為 SELECT DISTINCT 陳述式或 UNION 中的特定值。
下表顯示 NULL 處理實驗的結果。
SQLite | PostgreSQL | Oracle | Informix | DB2 | MS-SQL | OCELOT | |
---|---|---|---|---|---|---|---|
將任何東西加到 null 會得到 null | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
將 null 乘以零會得到 null | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
null 在 UNIQUE 欄位中為特定值 | 是 | 是 | 是 | 否 | (註解 4) | 否 | 是 |
null 在 SELECT DISTINCT 中為特定值 | 否 | 否 | 否 | 否 | 否 | 否 | 否 |
null 在 UNION 中為特定值 | 否 | 否 | 否 | 否 | 否 | 否 | 否 |
"CASE WHEN null THEN 1 ELSE 0 END" 為 0? | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
"null OR true" 為 true | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
"not (null AND false)" 為 true | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
MySQL 3.23.41 |
MySQL 4.0.16 |
Firebird | SQL Anywhere |
Borland Interbase |
|
---|---|---|---|---|---|
將任何東西加到 null 會得到 null | 是 | 是 | 是 | 是 | 是 |
將 null 乘以零會得到 null | 是 | 是 | 是 | 是 | 是 |
null 在 UNIQUE 欄位中為特定值 | 是 | 是 | 是 | (註解 4) | (註解 4) |
null 在 SELECT DISTINCT 中為特定值 | 否 | 否 | 否 (註解 1) | 否 | 否 |
null 在 UNION 中為特定值 | (註解 3) | 否 | 否 (註解 1) | 否 | 否 |
"CASE WHEN null THEN 1 ELSE 0 END" 為 0? | 是 | 是 | 是 | 是 | (註解 5) |
"null OR true" 為 true | 是 | 是 | 是 | 是 | 是 |
"not (null AND false)" 為 true | 否 | 是 | 是 | 是 | 是 |
註解: | 1. | 舊版 Firebird 會從 SELECT DISTINCT 和 UNION 中省略所有 NULL。 |
2. | 測試資料無法取得。 | |
3. | MySQL 版本 3.23.41 不支援 UNION。 | |
4. | DB2、SQL Anywhere 和 Borland Interbase 不允許在 UNIQUE 欄位中使用 NULL。 | |
5. | Borland Interbase 不支援 CASE 表達式。 |
下列指令碼用於收集上方表格的資訊。
-- I have about decided that SQL's treatment of NULLs is capricious and cannot be -- deduced by logic. It must be discovered by experiment. To that end, I have -- prepared the following script to test how various SQL databases deal with NULL. -- My aim is to use the information gathered from this script to make SQLite as -- much like other databases as possible. -- -- If you could please run this script in your database engine and mail the results -- to me at drh@hwaci.com, that will be a big help. Please be sure to identify the -- database engine you use for this test. Thanks. -- -- If you have to change anything to get this script to run with your database -- engine, please send your revised script together with your results. -- -- Create a test table with data create table t1(a int, b int, c int); insert into t1 values(1,0,0); insert into t1 values(2,0,1); insert into t1 values(3,1,0); insert into t1 values(4,1,1); insert into t1 values(5,null,0); insert into t1 values(6,null,1); insert into t1 values(7,null,null); -- Check to see what CASE does with NULLs in its test expressions select a, case when b<>0 then 1 else 0 end from t1; select a+10, case when not b<>0 then 1 else 0 end from t1; select a+20, case when b<>0 and c<>0 then 1 else 0 end from t1; select a+30, case when not (b<>0 and c<>0) then 1 else 0 end from t1; select a+40, case when b<>0 or c<>0 then 1 else 0 end from t1; select a+50, case when not (b<>0 or c<>0) then 1 else 0 end from t1; select a+60, case b when c then 1 else 0 end from t1; select a+70, case c when b then 1 else 0 end from t1; -- What happens when you multiply a NULL by zero? select a+80, b*0 from t1; select a+90, b*c from t1; -- What happens to NULL for other operators? select a+100, b+c from t1; -- Test the treatment of aggregate operators select count(*), count(b), sum(b), avg(b), min(b), max(b) from t1; -- Check the behavior of NULLs in WHERE clauses select a+110 from t1 where b<10; select a+120 from t1 where not b>10; select a+130 from t1 where b<10 OR c=1; select a+140 from t1 where b<10 AND c=1; select a+150 from t1 where not (b<10 AND c=1); select a+160 from t1 where not (c=1 AND b<10); -- Check the behavior of NULLs in a DISTINCT query select distinct b from t1; -- Check the behavior of NULLs in a UNION query select b from t1 union select b from t1; -- Create a new table with a unique column. Check to see if NULLs are considered -- to be distinct. create table t2(a int, b int unique); insert into t2 values(1,1); insert into t2 values(2,null); insert into t2 values(3,null); select * from t2; drop table t1; drop table t2;