web-dev-qa-db-ja.com

PostgreSQLで日付系列を生成するにはどうすればよいですか?

時系列を生成したい場合は、この質問を参照してください

2つの日付の間に一連の日付を生成したいとします。関数 generate_series のみ提供

Function                                    Argument Type                         Return Type                                                               Description
generate_series(start, stop, step interval) timestamp or timestamp with time zone setof timestamp or setof timestamp with time zone (same as argument type) Generate a series of values, from start to stop with a step size of step

だから私はこれをどうやってやりますか?

8
Evan Carroll

これには_generate_series_を使用できますが、必ず引数を「タイムゾーンなしのタイムスタンプ」に明示的にキャストしてください。そうしないと、デフォルトで「タイムゾーン付きのタイムスタンプ」。 PostgreSQLは、両方の入力に対して_generate_series_をオーバーロードします。

_timestamp with timezone_の問題

ここで欠点を確認できます。

_SET timezone = 'America/Santiago';
SELECT generate_series(date '2016-08-15', date '2016-08-15', '1 day');    
SELECT generate_series(date '2016-08-14', date '2016-08-15', '1 day');
_

上記のどちらも同じ日数を返します。ここでもう一度見ることができます。

_SET timezone = 'America/Sao_Paulo';
SELECT generate_series(date '2016-10-16', date '2016-10-17', '1 day');
SELECT generate_series(date '2016-10-17', date '2016-10-17', '1 day');
_

上記は、1日の2つの範囲を示しています。

この動作の理由は、これらのタイムゾーンの「DST境界が、小時間のより適切な時間ではなく、真夜中にある」ことです。

では、「正しく行う」とはどのようなものですか、

_SELECT generate_series(
  timestamp without time zone '2016-10-16',
  timestamp without time zone '2016-10-17',
  '1 day'
);
_

今すぐキャストできます。

_SELECT d::date
FROM generate_series(
  timestamp without time zone '2016-10-16',
  timestamp without time zone '2016-10-17',
  '1 day'
) AS gs(d);
_

この質問と回答は、IRC(irc://irc.freenode.net/#postgresql)でのRhodiumToadとの会話に触発されました。彼は私をこの問題に変えましたソリューションを提供しました。

更新:2つの潜在的な修正

オプション1:generate_series(date,date,interval)

遊んでみたところ、generate_series(date,date,interval)をオーバーロードすることで、明示的に_timestamp without time zone_にキャストする必要を省くことができることに気づきました

これが私の機能です

_CREATE FUNCTION generate_series( t1 date, t2 date, i interval )
RETURNS setof date
AS $$
  SELECT d::date
  FROM generate_series(
    t1::timestamp without time zone,
    t2::timestamp without time zone,
    i
  )
    AS gs(d)
$$
LANGUAGE sql
IMMUTABLE;
_

これで、上記のテストケースを再実行できるようになりました。この2つはどちらも同じものを返します。

_SET timezone = 'America/Santiago';
SELECT d::date
FROM generate_series(date '2016-08-15', date '2016-08-15', '1 day')
  AS gs(d);

SELECT d::date
FROM generate_series(
  timestamp without time zone '2016-08-15',
  timestamp without time zone '2016-08-15',
  '1 day'
)
  AS gs(d);
_

これら2つと同様に、

_SELECT d::date
FROM generate_series(date '2016-08-14', date '2016-08-15', '1 day')
  AS gs(d);

SELECT d::date
FROM generate_series(
  timestamp without time zone '2016-08-14',
  timestamp without time zone '2016-08-15',
  '1 day'
)
  AS gs(d);
_

オプション2:generate_series(date,date,int)

別のオプションは、新しい関数generate_series(date,date,int)を作成することです ここで述べた理由により、両方を持つことはできません 。これらの1つを選んで、

_generate_series(date,date,interval)
generate_series(date,date,int)
_

2番目のオプションが必要な場合は、次の方法を試してください。

_CREATE FUNCTION generate_series( t1 date, t2 date, i int )
RETURNS setof date
AS $$
  SELECT d::date
  FROM generate_series(
    t1::timestamp without time zone,
    t2::timestamp without time zone,
    i * interval '1 day'
  )
    AS gs(d)
$$
LANGUAGE sql
IMMUTABLE;
_

注意事項

Ircのレビューで、これらのアイデアにはいくつかの問題があります。

<johto>generate_series(date, date, unknown)はすでに機能しています。 intバージョン(generate_series(date, date, '1 day')など)で完全に壊さない場合は、戻り値の型をtimestamptzからdateに変更します。 _(date, date, interval)_の場合、壊れるケースは少なくなりますが、出力タイプは変更します。 (また、現在「正常に機能」している_(date, date, '1 hour')_で何が起こるかは明らかではありません)

15
Evan Carroll