小巧、快速、可靠。
任選三項。

SQLite 與其他資料庫引擎的 NULL 處理

目標是讓 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;