如何在 Postgres 中创建包含上限的新日期范围类型?

How to create a new date range type with included upper bound in Postgres?

Postgres 带有一个很好的特性,称为 Range Types,它提供有用的范围功能(重叠、包含等)。

我想使用 daterange 类型,但我认为该类型是通过一个尴尬的选择实现的:日期范围的上限是 excluded。这意味着如果我将我的值定义为 2014/01/01 - 2014/01/31,则显示为 [2014/01/01, 2014/01/31) 并且 1 月 31 日 被排除在 之外!

我认为这是错误的默认选择。我想不出现实生活中有任何应用程序或参考资料假设日期范围的结束日期被排除在外。至少不是我的经验。

我想为包含下限和上限的日期实现范围类型,但我遇到了 Postgres 文档墙:关于如何创建新的离散范围类型的参考资料含糊不清并且缺少任何示例(取自文档:Creating a canonical function is a bit tricky, since it must be defined before the range type can be declared).

有人可以提供一些帮助吗?甚至直接实现本身;它应该是 5-10 行代码,但是将这 5-10 行代码放在一起是一项认真的研究工作。

编辑:澄清:我正在寻找有关如何创建正确类型的信息,以便插入 [2014/01/01, 2014/01/31] 导致 upper(daterange) = '2014/01/31'。对于现有的 daterange 类型,此值是 "converted" 到 [2014/01/01, 2014/02/01) 并给出 upper(daterange) = '2014/02/01'

注意第三个构造函数参数:

select daterange('2014/01/01', '2014/01/31', '[]');
        daterange        
-------------------------
 [2014-01-01,2014-02-01)

或包含上限的直接转换:

select '[2014/01/01, 2014/01/31]'::daterange;
        daterange        
-------------------------
 [2014-01-01,2014-02-01)

编辑

不是新类型(错误的方法恕我直言)而是正确的功能:

create function inclusive_upper_daterange(dtr daterange)
returns date as $$

    select upper(dtr) - 1;

$$ language sql immutable;

select inclusive_upper_daterange('[2014/01/01, 2014/01/31]'::daterange);
 inclusive_upper_daterange 
---------------------------
 2014-01-31

按照 Postgres 文档中的说明,我想出了以下代码来创建我需要的类型。但是它不起作用(继续阅读)。

CREATE TYPE daterange_;

CREATE FUNCTION date_minus(date1 date, date2 date) RETURNS float AS $$
    SELECT cast(date1 - date2 as float);
$$ LANGUAGE sql immutable;

CREATE FUNCTION dr_canonical(dr daterange_) RETURNS daterange_ AS $$
BEGIN
    IF NOT lower_inc(dr) THEN
        dr := daterange_(lower(dr) + 1, upper(dr), '[]');
    END IF;
    IF NOT upper_inc(dr) THEN
        dr := daterange_(lower(dr), upper(dr) - 1, '[]');
    END IF;
    RETURN dr;
END;
$$ LANGUAGE plpgsql;

CREATE TYPE daterange_ AS RANGE (
    SUBTYPE = date,
    SUBTYPE_DIFF = date_minus,
    CANONICAL = dr_canonical    
);

据我所知,这个定义完全符合规范。但是,它无法使用 ERROR: SQL function cannot accept shell type daterange_.

声明 dr_canonical 函数

It looks like (code also) 不可能使用除 C 以外的任何语言来声明规范函数!因此,实际上不可能声明一个新的离散范围类型,尤其是如果您使用无法访问机器 运行 的 Postgres 云服务。玩得好 Postgres。

使用 PostgresQL 11,您可以使用 upper_inc 函数解决演示部分,例如:

select
    WHEN upper_inc(mydaterange) THEN upper(mydaterange)
    ELSE date(upper(mydaterange)- INTERVAL '1 day')
 END 

我设法为日期范围创建了自定义类型:

    CREATE or replace FUNCTION to_timestamptz(arg1 timestamptz, arg2 timestamptz) RETURNS float8 AS
    'select extract(epoch from (arg2 - arg1));' LANGUAGE sql STRICT
                                                             IMMUTABLE;
    ;
    create type tsrangetz AS RANGE
    (
      subtype = timestamptz,
      subtype_diff =
      to_timestamptz
    )
    ;
    select tsrangetz(current_date, current_date + 1)
    --["2020-10-05 00:00:00+07","2020-10-06 00:00:00+07")
    ;