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

Nic*_*ise 6 postgresql null range infinity

作为序言,我不是问NULL 边界和无限边界之间有什么区别 - 这在另一个问题中有所介绍。相反,我问为什么PostgreSQL 会区分 NULL 和无限边界(据我所知)它们的功能完全相同。

我最近开始使用 PostgreSQL 的范围类型,我对范围类型中 NULL 值的含义有点困惑。文档说:

范围的下限可以省略,这意味着所有小于上限的值都包含在范围 中,例如(,3]。同样,如果省略范围的上限,则所有大于下限的值都包含在范围中。如果下限和上限都被省略,则元素类型的所有值都被认为在该范围内。

这对我来说表明范围中省略的边界(与范围类型的构造函数中指定的等效 NULL 边界)应被视为无限。然而,PostgreSQL 区分了 NULL 边界和无限边界。文档继续:

您可以将[范围内]的这些缺失值视为+/-无穷大,但它们是特殊范围类型值,并且被视为超出任何范围元素类型的+/-无穷大值。

这令人费解。“超越无穷大”没有意义,因为无限值的全部要点是没有什么可以大于+无穷大或小于-无穷大。这并没有破坏“范围内的元素”类型的检查,但它确实引入了一个有趣的主键案例,我认为大多数人都不会想到。或者至少,没想到。

假设我们创建一个基本表,其唯一字段是日期范围,这也是 PK:

CREATE TABLE public.range_test
(
    id daterange NOT NULL,
    PRIMARY KEY (id)
);
Run Code Online (Sandbox Code Playgroud)

然后我们可以毫无问题地用以下数据填充它:

INSERT INTO range_test VALUES (daterange('-infinity','2021-05-21','[]'));
INSERT INTO range_test VALUES (daterange(NULL,'2021-05-21','[]'));
Run Code Online (Sandbox Code Playgroud)

选择所有数据表明我们有这两个元组:

[-infinity,2021-05-22)
(,2021-05-22)
Run Code Online (Sandbox Code Playgroud)

因此这两个元组是不同的,否则就会出现主键违规。但同样,当我们处理构成范围的实际元素时,NULL 边界和无限边界的工作原理完全相同。例如,不存在date值 X 使得 的结果X <@ [-infinity,2021-05-22)返回与 不同的结果X <@ (,2021-05-22)。这是有道理的,因为 NULL 值不能具有 类型,因此它们甚至不能与范围进行比较(为了双重确定, datePostgreSQL 甚至将 NULL 下限上的包含边界转换daterange(NULL,'2021-05-21','[]')为独占边界)。(,2021-05-22)但为什么在所有实际方面都相同的两个范围被认为是不同的呢?

当我还在上学的时候,我记得无意中听到一些关于“未知”和“不存在”之间区别的讨论 - 两个比我聪明的人在谈论为什么 NULL 值经常引起问题的背景下,并且用单独的“未知”和“不存在”值替换单个 NULL 可能会解决这些问题,但当时的讨论超出了我的范围。想到这个奇怪的功能让我想起了那个讨论。那么“未知”和“不存在”之间的区别是 PostgreSQL 将 NULL 和 +-无穷大视为不同的原因吗?如果是这样,为什么范围是 PostgreSQL 中唯一允许这种区别的类型?如果不是,为什么 PostgreSQL 将功能等效的值视为不同的值?

Erw*_*ter 4

相反,我问为什么 PostgreSQL 会区分 NULL 和无限边界(据我所知)它们的功能完全相同。

但他们没有。用作范围界限时语法方便,而NULL/-infinity范围域中的infinity实际值。抽象值意味着小于/大于任何其他值,但仍然是(可以包含或排除)。

此外,NULL适用于任何范围类型,而大多数数据类型没有像-infinity/这样的特殊值infinity。以integerint4range为例。

为了更好地理解,请考虑a_horse 提供的pgsql-general 中的线程:

这是有道理的,因为 NULL 值不能具有日期类型,因此它们甚至不能与范围进行比较

每种数据类型都可以NULL,甚至是明确的域NOT NULL。看:

date当然,这包括(就像阿德里安评论的那样):

test=> SELECT NULL::date, pg_typeof(NULL::date);
 date | pg_typeof 
------+-----------
      | date
(1 row)
Run Code Online (Sandbox Code Playgroud)

但试图将NULL其作为进行讨论(当用作范围的界限时)从一开始就是一种误导性的方法。这不是一个值。

...(为了双重确定,PostgreSQL 甚至将 NULL 下限的包含边界转换daterange(NULL,'2021-05-21','[]')为独占边界)。(,2021-05-22)

同样,NULL不被视为范围域中的值。它只是作为方便的语法来表达:“无界”。仅此而已。