我们的目的是让SQLite以与标准兼容的方式来处理 NULL。 但但起来,在 SQL 标准中,对于如何处理 NULL 是不明确的。 从标准文档中,并不能在所有情况下都清楚地看出应该如何确切地处理 NULL。
所以,除了遵循标准的文档,我们还测试了其它许多流行的 SQL 引擎, 来看它们是如果处理 NULL 的。我们的思想是,让 SQLite 像其它数据库引擎那样工作。 有志愿者编写了一个测试脚本,用于在各种各样的关系型数据库系统上测试 NULL 值的处理方式。我们最初是在2005年5月测试的。在本文的最后, 有该测试脚本的一个拷贝。
最初,SQLite 遵循的编码标准是-对于下面的表格,每个问题的回答都应该是 YES 。 但通过对其它 SQL 数据库引擎的测试,我们发现,没有任何一个是那样的。 所以,SQLite 就改成了与Oracle、PostgreSQL 以及 DB2 相同的方式来处理 NULL。 这就使得在 SELECT DISTINCT 语句及 SELECT 中的 UNION 操作符等为获得不重复值而设的操作,会认为 NULL 是彼此不同的。 但在一个具有唯一约束的列上, NULL 值仍然是相同的。这看起来有些专制, 但与我们与其它数据库引擎相比,这种专制就显得不那种重要了。
当然,也可以让 SQLite 认为 SELECT DISTINCT 及 UNION 中的 NULL 是相同的。 这可以通过改变 sqliteInt.h 源文件中 NULL_ALWAYS_DISTINCT 的 #define 预理宏并重新编译来实现。
更新于 2003-07-13: 由于本当初写文本的时候所测试的数据库引擎都已经更新了, 所以用户很可能期待修正下表。最初的数据显示了非常不一致的行为, 但随着时间的推移,这些不一致性大都趋从了 PostgreSQL/Oracle 模型。 仅有的重要区别是 Informix 和 MS-SQL 都认为 UNIQUE 列中的 NULL 是彼此不同的。
事实是,NULLs 在 UNIQUE 上应该是彼此相同的,而对于 SELECT DISTINCT 及 UNION 仍然不好说。 看起来,在任何地方,NULL都应该相同,或者任何地方都不同。 并且 SQL 标准文档也建议 NULL 值应该在任何地方都是相同的。 但是,直到本文写作时,也没有 SQL 引擎在 SELECT DISTINCT 或 UNION 中把 NULL 看作是相同的。
下表列出了对于 NULL 处理的试验结果。
  | SQLite | PostgreSQL | Oracle | Informix | DB2 | MS-SQL | OCELOT |
---|---|---|---|---|---|---|---|
NULL 加上任何值都得 NULL | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
NULL 乘 0 得 NULL | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
NULL 值在 UNIQUE 列中是彼此相同的 | Yes | Yes | Yes | No | (Note 4) | No | Yes |
NULL 值在 SELECT DISTINCT 时是彼此相同的 | No | No | No | No | No | No | No |
NULL 值在 UNION 中是彼此相同的 | No | No | No | No | No | No | No |
"CASE WHEN null THEN 1 ELSE 0 END" 将会是 0? | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
"null OR true" 将会是 true | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
"not (null AND false)" 是 true | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
  | MySQL 3.23.41 |
MySQL 4.0.16 |
Firebird | SQL Anywhere |
Borland Interbase |
---|---|---|---|---|---|
NULL 加上任何值都得 NULL | Yes | Yes | Yes | Yes | Yes |
NULL 乘 0 得 NULL | Yes | Yes | Yes | Yes | Yes |
NULL 值在 UNIQUE 列中是彼此相同的 | Yes | Yes | Yes | (Note 4) | (Note 4) |
NULL 值在 SELECT DISTINCT 时是彼此相同的 | No | No | No (Note 1) | No | No |
NULL 值在 UNION 中是彼此相同的 | (Note 3) | No | No (Note 1) | No | No |
"CASE WHEN null THEN 1 ELSE 0 END" 将会是 0? | Yes | Yes | Yes | Yes | (Note 5) |
"null OR true" 将会是 true | Yes | Yes | Yes | Yes | Yes |
"not (null AND false)" 是 true | No | Yes | Yes | Yes | Yes |
注意: | 1. | 老版本的 firebird 会在 SELECT DISTINCT 及 UNION 在忽略所有的NULL。 |
2. | 无测试数据。 | |
3. | MySQL version 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 gather 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 multiple 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;