【问题标题】:How to create a new date range type with included upper bound in Postgres?如何在 Postgres 中创建包含上限的新日期范围类型?
【发布时间】:2015-07-05 20:48:21
【问题描述】:

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

我希望使用daterange 类型,但是我认为该类型是通过一个尴尬的选择实现的:日期范围的上限是排除。这意味着,如果我将值定义为 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 类型,此值“转换”为 [2014/01/01, 2014/02/01) 并给出 upper(daterange) = '2014/02/01'

【问题讨论】:

  • Postgres 范围类型始终转换为规范形式 [)。如果您需要呈现[A,B],则在呈现给用户之前处理该值。
  • 正确。 Postgres 还说你可以定义范围类型,其规范形式不是默认的[),而是任何你想要的。我一直在寻找能够减轻我的应用程序必须执行该处理的类型,而我现在在查询和插入数据时都必须这样做。
  • 我想不出现实生活中包含结束日期的单个应用程序或参考。如果我在酒店预订,我不会在结束日期停留,而是在到达日期,因此不包括结束日期。这就是为什么它是默认值。
  • 预订确实是我所说的一个很好的反例。想想任何类型的交互式报告。例如,您正在查看一家公司的销售报告,并且有一个日期范围下拉菜单可让您过滤特定日期。您单击开始日期和结束日期,并且总是意味着将它们都包括在内。我写这篇文章时正在开发的应用程序让用户输入他们选择的日期范围的特定数据,并且选择结束日期总是意味着包含它。
  • @MatthewSchinckel 的回答是正确的。日期范围的输出规范形式是 [)。例如 select daterange('20200101' , '20201220' , []) as range1 返回输出 [2020-01-01, 2020-12-21) 和 select daterange('20200101' , '20201220' , () ) as range2 返回 [2020-01-02, 2020-12-21) 的输出

标签: postgresql date-range


【解决方案1】:

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

    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")
    ;

【讨论】:

  • 请将评论翻译成英文,如我设法为日期范围创建了一个自定义类型,并添加一些关于您的代码的描述/说明,这似乎很好。跨度>
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    按照 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 玩得很好。

    【讨论】:

      【解决方案4】:

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

      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
      

      【讨论】:

      • 好吧,我能做到。浏览表格时仍然给出[2014-01-01,2014-02-01) 输出,upper(daterange) 返回2014-02-01。我已经在我的应用程序中实现了所有这些类型的解决方法。构建不需要任何此类解决方法的正确类型是我正在寻找的答案。
      • 我刚刚意识到,你编辑了我的问题的标题,改变了它的含义,然后你回答了这个改变的含义。
      • @dtheodor 对编辑感到抱歉。我明白这就是你首先要寻找的东西。您正在将数据表示与应该在不同层处理的含义混合在一起。
      • 我试图用新的编辑来澄清,希望现在更清楚
      • 对不起,我真的在寻找有关如何声明新的离散范围类型的信息,而不是其他解决方法。我自己努力弄清楚并接受了自己的答案。
      猜你喜欢
      • 1970-01-01
      • 2011-04-19
      • 1970-01-01
      • 1970-01-01
      • 2014-10-29
      • 2015-10-25
      • 2021-10-22
      • 2014-08-05
      • 1970-01-01
      相关资源
      最近更新 更多