ISO 8601 允许的不精确日期值的单一数据类型

big*_*ose 8 postgresql datatypes timestamp sql-standard

如何在 PostgreSQL 类型中以降低的精度存储日期和时间值,并使它们表现为日期和/或时间值

ISO 8601 允许精度降低的日期值。'1964', '1964-05', '1964-05-02' 都是值的有效表示,精度越来越高。Python 'datetime' 类型也允许以这种方式降低精度的值。

PostgreSQL 本机时间类型不允许降低精度

在原生日期类型中,日期的每个元素都必须存在,否则该值将被拒绝。将低于所需精度级别的元素设置为“00”也会失败。

=> SELECT CAST('1964-05-02' AS DATE);
    date    
------------
 1964-05-02
(1 row)

=> SELECT CAST('1964-05' AS DATE);
ERROR:  invalid input syntax for type date: "1964-05"
LINE 1: SELECT CAST('1964-05' AS DATE);
                    ^
=> SELECT CAST('1964' AS DATE);
ERROR:  invalid input syntax for type date: "1964"
LINE 1: SELECT CAST('1964' AS DATE);
                    ^
=> SELECT CAST('1964-00-00' AS DATE);
ERROR:  date/time field value out of range: "1964-00-00"
LINE 1: SELECT CAST('1964-00-00' AS DATE);
                    ^
HINT:  Perhaps you need a different "datestyle" setting.
Run Code Online (Sandbox Code Playgroud)

降低精度的日期和/或时间类型的预期行为

是否有一种简单的标准方法来支持将精度降低的 ISO 8601 日期值输入到 PostgreSQL 日期和/或时间类型中?

为此创建一个类型是可能的,但我不知道如何。当然,我需要对这些值进行范围检查并处理时区,并将内置类型所做的所有其他有用的事情与其他时间值进行明智的比较。

我期望的是,正如值 '1964-05-02' 指的是当天 00:00:00 到第二天 00:00:00 之间的整个时间间隔,降低精度的值将简单地表示更大的区间:“1962-05”是指从 1962 年 5 月开始的 00:00:00 到 1962 年 6 月第一天的 00:00:00 之间的整个区间。

我想看到的一个例子:

=> SELECT CAST('1964-05-02 00:00' AS TIMESTAMP) = CAST('1964-05-02 00:00:00' AS TIMESTAMP);
 ?column? 
----------
 t
(1 row)

=> SELECT CAST('1964-05' AS TIMESTAMP) = CAST('1964-05-02' AS TIMESTAMP);
 ?column? 
----------
 t
(1 row)
Run Code Online (Sandbox Code Playgroud)

目前前者的行为如上;后者抱怨“类型时间戳的输入语法无效”。在我看来,与更精确的值相比,它们都是降低精度的值表现得更明智的情况。

的含义1964-05在ISO 8601包括越精确的值1964-05-021964-05-02 18:271964-05-23。所以这些都应该比较相等。

Mik*_*ll' 6

我过去使用过 CHAR 和 VARCHAR,用问号或破折号替换缺失的部分。问号表示“未知”,破折号表示“不适用”。事实证明,这对于用户(复杂诉讼中的秘书和律师助理)来说足够直观,对于律师来说足够灵活,并且分类合理。

"1964------"
"1964-??-??"
"1964-05---"
"1964-05-??"
"1964-05-02"
"1964-06---"
"1964-06-??"
Run Code Online (Sandbox Code Playgroud)

将您的声明和 CHECK 约束包装在 CREATE DOMAIN 或 CREATE TYPE 中,以使维护更容易。CREATE DOMAIN 不需要额外的编码。CREATE TYPE需要用低级语言编写的支持函数。

  • +1 但除非您要创建新的基本类型,否则 `create type` 不需要低级语言中的支持函数。例如,“日期时间”和精度“枚举”的复合类型。然后可以将 `create domain` 包裹在类型周围以添加 `check` 约束。我个人希望数据库处理时区、闰年等的复杂性(如果需要),因此倾向于包装提供的类型。 (3认同)
  • @bignose:不,我理解你,我知道它们不充当日期时间值。我指出他们在哪些方面做得好,哪些方面做得不好,以及哪些实施无法做到无可争议的好。如果您编写`create type` 所需的函数,则可以使形式为`(DATE '1964-??-??' < 'DATE 1964-05-??')` 的表达式始终计算为TRUE。你不能做的是让每个人相信这样做是有意义的。这可能就是 PostgreSQL 原生支持大约 3 打数据类型的原因,而不原生支持这种数据类型。 (2认同)

Jac*_*las 5

不,间隔类型支持降低精度,但其他日期/时间类型都不支持。

Postgres 允许您自己滚动,create type但不幸的是不允许将约束添加到类型中,这限制了它在这种情况下的实用性。我能想到的最好的方法要求您在使用该fuzzy类型的每个字段上重复检查约束:

create type preciseness as enum('day', 'month', 'year');
create type fuzzytimestamptz as (ts timestamptz, p preciseness);
create table t( id serial primary key,
                fuzzy fuzzytimestamptz
                    check( (fuzzy).ts is not null 
                           or ((fuzzy).ts is null and (fuzzy).p is not null) ),
                    check((fuzzy).ts=date_trunc('year', (fuzzy).ts) or (fuzzy).p<'year'),
                    check((fuzzy).ts=date_trunc('month', (fuzzy).ts) or (fuzzy).p<'month'),
                    check((fuzzy).ts=date_trunc('day', (fuzzy).ts) or (fuzzy).p<'day') );

insert into t(fuzzy) values (row(date_trunc('year', current_timestamp), 'year'));
insert into t(fuzzy) values (row(date_trunc('month', current_timestamp), 'month'));
insert into t(fuzzy) values (row(date_trunc('day', current_timestamp), 'day'));

select * from t;

 id |              fuzzy
----+----------------------------------
  1 | ("2011-01-01 00:00:00+00",year)
  2 | ("2011-09-01 00:00:00+01",month)
  3 | ("2011-09-23 00:00:00+01",day)
Run Code Online (Sandbox Code Playgroud)

--edit - 一个示例相等运算符:

create function fuzzytimestamptz_equality(fuzzytimestamptz, fuzzytimestamptz)
                returns boolean language plpgsql immutable as $$
begin
  return ($1.ts, $1.ts+coalesce('1 '||$1.p, '0')::interval)
         overlaps ($2.ts, $2.ts+coalesce('1 '||$2.p, '0')::interval);
end;$$;
--
create operator = ( procedure=fuzzytimestamptz_equality, 
                    leftarg=fuzzytimestamptz, 
                    rightarg=fuzzytimestamptz );
Run Code Online (Sandbox Code Playgroud)

示例查询:

select *, fuzzy=row(statement_timestamp(), null)::fuzzytimestamptz as equals_now,
          fuzzy=row(statement_timestamp()+'1 day'::interval, null)::fuzzytimestamptz as equals_tomorrow,
          fuzzy=row(date_trunc('month', statement_timestamp()), 'month')::fuzzytimestamptz as equals_fuzzymonth,
          fuzzy=row(date_trunc('month', statement_timestamp()+'1 month'::interval), 'month')::fuzzytimestamptz as equals_fuzzynextmonth
from t;
 id |               fuzzy                | equals_now | equals_tomorrow | equals_fuzzymonth | equals_fuzzynextmonth
----+------------------------------------+------------+-----------------+-------------------+-----------------------
  1 | ("2011-01-01 00:00:00+00",year)    | t          | t               | t                 | t
  2 | ("2011-09-01 00:00:00+01",month)   | t          | t               | t                 | f
  3 | ("2011-09-24 00:00:00+01",day)     | t          | f               | t                 | f
  4 | ("2011-09-24 11:45:23.810589+01",) | f          | f               | t                 | f
Run Code Online (Sandbox Code Playgroud)