NULL VALUES

虽然理论学家对此不以为然,但 NULL 值在关系型数据库中仍然扮演着重要角色:它提供了一种方便的方式来表示某个值要么未知,要么不存在。

然而,特殊的值需要特殊处理。除了理论上的不一致性之外,还有许多实际问题需要考虑。常规的布尔逻辑被三值逻辑取代,因此 NOT IN 的行为可能出乎意料。对于 NULL 值应该被视为大于还是小于普通值也不明确(因此存在用于排序的 NULLS FIRST 和 NULLS LAST 子句)。是否需要在聚合函数中考虑 NULL 值也并不十分明显。严格来说,NULL 根本不是一个值,因此优化器在处理它们时需要额外的信息。

除了在表级收集的最基本的统计信息之外,分析器还会为表的每一列收集统计信息。这些数据存储在系统目录的 pg_statistic 表中,但你也可以通过 pg_stats 视图访问,这个视图以更方便的格式提供这些信息。
列级统计信息中包括 NULL 值的比例;在分析过程中计算,并以 null_frac 属性表示。
例如,当我们查询尚未起飞的航班时,可以依赖它们的起飞时间未定义(NULL)这一事实:

1
2
3
4
5
=> EXPLAIN SELECT * FROM flights WHERE actual_departure IS NULL;
QUERY PLAN
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
Seq Scan on flights (cost=0.00..4772.67 rows=16702 width=63) Filter: (actual_departure IS NULL)
(2 rows)

为了估算结果,优化器将表的总行数乘以 NULL 值的比例:

1
2
3
4
5
6
7
=> SELECT round(reltuples * s.null_frac) AS rows FROM pg_class
JOIN pg_stats s ON s.tablename = relname WHERE s.tablename = 'flights'
AND s.attname = 'actual_departure';
rows
−−−−−−−
16702
(1 row)

以下是实际的行数:

1
2
3
4
5
SELECT count(*) FROM flights WHERE actual_departure IS NULL;
count
−−−−−−−
16348
(1 row)