为什么 PostgreSQL 认为范围类型中的 NULL 边界与无限边界不同?

问题描述

只是作为序言,我不是问 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 这样的特殊值。以 integerint4range 为例。

为了更好地理解,请考虑 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 不被视为范围域中的值。它只是作为一种方便的语法说:“无界”。仅此而已。