问题描述
只是作为序言,我不是问 NULL 边界和无限边界之间的区别是什么 - 那是 covered in this other question。相反,我问的是为什么 Postgresql 在 NULL 和无限边界之间进行区分,而(据我所知)它们的功能完全相同。
我最近开始使用 Postgresql 的范围类型,我对范围类型中的 NULL 值应该意味着什么感到有些困惑。 The documentation 说:
范围的下限可以省略,这意味着所有小于上限的值都包含在范围内,例如,(,3]
。同样,如果省略了范围的上限,则所有大于下限的值都包含在范围内。如果上下限都被省略,元素类型的所有值都被认为在范围内。
这向我表明,应该将范围中省略的边界(即范围类型的构造函数中指定的等效 NULL 边界)视为无限。但是,Postgresql 区分了 NULL 边界和无限边界。文档继续:
您可以将这些缺失值 [范围内] 视为 +/-无穷大,但它们是特殊的范围类型值,被视为超出任何范围元素类型的 +/- 无穷大值。
这令人费解。 “超越无限”没有意义,因为无限值的全部意义在于没有可以大于+无限或小于-无限。这不会破坏“范围内的元素”类型的检查,但它确实为主键引入了一个有趣的案例,我认为大多数人都不会想到。或者至少,我没想到。
假设我们创建了一个基本表,它的唯一字段是日期范围,这也是 PK:
CREATE TABLE public.range_test
(
id daterange NOT NULL,PRIMARY KEY (id)
);
然后我们可以毫无问题地使用以下数据填充它:
INSERT INTO range_test VALUES (daterange('-infinity','2021-05-21','[]'));
INSERT INTO range_test VALUES (daterange(NULL,'[]'));
[-infinity,2021-05-22)
(,2021-05-22)
所以这两个元组是不同的,否则就会发生主键冲突。但同样,当我们处理构成范围的实际元素时,NULL 边界和无限边界的工作方式完全相同。例如,没有 date
值 X 使得 X <@ [-infinity,2021-05-22)
的结果返回与 X <@ (,2021-05-22)
不同的结果。这是有道理的,因为 NULL 值不能具有 date
的类型,因此它们甚至无法与范围进行比较(并且 Postgresql 甚至将 daterange(NULL,'[]')
中的 NULL 下限的包含边界转换为一个排他边界,(,2021-05-22)
加倍确定)。但是为什么在每个实际方面都相同的两个范围被认为是不同的?
当我还在学校的时候,我记得无意中听到一些关于“未知”和“不存在”之间区别的讨论——两个比我更聪明的人在讨论为什么 NULL 值经常导致问题,并且用单独的“未知”和“不存在”值替换单数 NULL 可能会解决这些问题,但当时的讨论超出了我的头脑。想到这个奇怪的功能让我想起了那个讨论。那么“未知”和“不存在”之间的区别是 Postgresql 将 NULL 和 +-infinity 视为不同的原因吗?如果是这样,为什么范围是 Postgresql 中唯一允许这种区别的类型?如果不是,为什么 Postgresql 将功能等效的值视为不同的?
解决方法
相反,我问的是为什么 PostgreSQL 在(据我所知)功能完全相同的情况下区分 NULL 和无限边界。
但他们没有。 NULL
在用作范围的边界时是一种语法便利,而 -infinity
/ infinity
是域中的实际值范围。抽象值意味着小于/大于任何其他值,但值仍然如此(可以包括或排除)。
此外,NULL
适用于任何范围类型,而大多数数据类型没有像 -infinity
/ infinity
这样的特殊值。以 integer
和 int4range
为例。
为了更好地理解,请考虑 pgsql-general 中的线程a_horse provided:
这是有道理的,因为 NULL 值不能具有日期类型,因此它们甚至无法与范围进行比较
每个数据类型都可以是 NULL
,甚至是明确为 NOT NULL
的域。见:
这当然包括 date
(如 Adrian commented):
test=> SELECT NULL::date,pg_typeof(NULL::date);
date | pg_typeof
------+-----------
| date
(1 row)
但是试图将 NULL
讨论为 value(当用作范围的界限时)是一种误导性的方法。这不是一个值。
...(PostgreSQL 甚至将 daterange(NULL,'2021-05-21','[]')
中 NULL 下界的包含边界转换为排它边界,(,2021-05-22)
是双重确定的)。
同样,NULL
不被视为范围域中的值。它只是作为一种方便的语法说:“无界”。仅此而已。