web-dev-qa-db-ja.com

ISO 8601で許可されている、不正確な日付値の単一のデータ型

精度の低下で日付と時刻の値を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.

精度が低下した日付および/または時刻タイプの予想される動作

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)

現在、前者は上記のように動作します。後者は、「タイムスタンプ型の無効な入力構文」について文句を言います。私の目には、どちらも精度の低い値と比較して精度の低い値が適切に動作するケースです。

の意味 1964-05 ISO 8601ではincludesより正確な値1964-05-02および1964-05-02 18:27および1964-05-23。したがって、それらはすべて同等に比較する必要があります。

7
bignose

私は過去にCHARとVARCHARを使用して、欠けている部分を疑問符またはダッシュで置き換えました。疑問符は「不明」を意味し、ダッシュは「該当なし」を意味します。これは、ユーザー(複雑な訴訟における秘書とパラリーガル)にとって十分直感的であり、弁護士にとって十分な柔軟性があることが証明され、賢明にソートされました。

"1964------"
"1964-??-??"
"1964-05---"
"1964-05-??"
"1964-05-02"
"1964-06---"
"1964-06-??"

宣言とCHECK制約をCREATE DOMAINまたはCREATE TYPEでラップして、メンテナンスを容易にします。 CREATE DOMAINは追加のコーディングを必要としません。 CREATE TYPE は、低水準言語で記述されたサポート関数を必要とします。

いいえ、 間隔 タイプ 精度の低下をサポート ですが、他の日付/時刻タイプはサポートしていません。

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)

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

サンプルクエリ:

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