​ ​

Postgresqlの日付として無限大(Infinity)/無限小(-Infinity)を利用して,RubyOnRails4.2.xからどう見えるのか調べました

北村です.コンサドーレ札幌は小野選手の怪我が治りつつあり,今週(5/24 vs徳島戦 @ 鳴門・大塚スポーツパーク ポカリスエットスタジアム)か来週(6/1 vsC大阪戦 @札幌ドーム)くらいからベンチ入りしそうです. 現在ボランチでものすごいパフォーマンスを発揮している稲本選手との共演も楽しみですね.

Postgresqlでは日付/時刻データ型を受け入れるカラムに「特殊な値」として infinity / -infinity などを利用できます.

  • infinity: 他のすべてのタイムスタンプより将来
  • -infinity: 他のすべてのタイムスタンプより過去

を表すようです.今回,この infinity を使った場合,PostgresqlとRails(4.2系)でどのような挙動を示すのか調べました.

(2016-07-20追記: Rails5系でどのような挙動を示すのか調べた記事を書きました)

SQLからの扱い

まずはRailsプロジェクトを準備します.

/tmp% psql --version
psql (PostgreSQL) 9.4.1
/tmp% rails -v
Rails 4.2.1
/tmp% rails new infplayground --database postgresql
/tmp% bin/rake db:create
/tmp% bin/rails g model Event date:date
/tmp% bin/rake db:migrate

これで Rails には Event というモデル,DB には events というテーブルができたはずです.

登録

まず psql infplayground_development して,DB へと繋ぎます.

早速登録してみましょう.今回は date の内容が

  • 2015-05-18
  • 2015-05-19
  • infinity
  • -infinity
  • null

となるような 5 つのレコードを用意してみます.

infplayground_development=# INSERT INTO events(date, created_at, updated_at) VALUES('2015-05-18', now(), now());
infplayground_development=# INSERT INTO events(date, created_at, updated_at) VALUES('2015-05-19', now(), now());
infplayground_development=# INSERT INTO events(date, created_at, updated_at) VALUES('infinity', now(), now());
infplayground_development=# INSERT INTO events(date, created_at, updated_at) VALUES('-infinity', now(), now());
infplayground_development=# INSERT INTO events(date, created_at, updated_at) VALUES(null, now(), now());
infplayground_development=# SELECT * FROM events;
 id |    date    |         created_at         |         updated_at
----+------------+----------------------------+----------------------------
  1 | 2015-05-18 | 2015-05-19 10:33:58.747022 | 2015-05-19 10:33:58.747022
  2 | 2015-05-19 | 2015-05-19 10:34:07.177788 | 2015-05-19 10:34:07.177788
  3 | infinity   | 2015-05-19 10:34:13.833993 | 2015-05-19 10:34:13.833993
  4 | -infinity  | 2015-05-19 10:34:23.098011 | 2015-05-19 10:34:23.098011
  5 |            | 2015-05-19 10:34:30.361427 | 2015-05-19 10:34:30.361427
(5 rows)

表示

  1. ORDER BY

    まずは ORDER BY を使って順番に並べます.

    infplayground_development=# SELECT id,date FROM events ORDER BY date ASC;
     id |    date
    ----+------------
      4 | -infinity
      1 | 2015-05-18
      2 | 2015-05-19
      3 | infinity
      5 |
    (5 rows)
    
    
    infplayground_development=# SELECT id,date FROM events ORDER BY date DESC;
     id |    date
    ----+------------
      5 |
      3 | infinity
      2 | 2015-05-19
      1 | 2015-05-18
      4 | -infinity
    (5 rows)
    

    なるほど. -infinity < 普通の日付 < infinity < null の順番になっていますね. DESC の場合は逆になっています.

  2. NULLS FIRST/LAST

    infinity とは直接関係ありませんが, 行の並び替え にあるとおり NULLS FIRSTNULLS LAST を使うと null 値を最初か最後に寄せることができます.ついでに試してみましょう.

    infplayground_development=# SELECT id,date FROM events ORDER BY date ASC NULLS FIRST;
     id |    date
    ----+------------
      5 |
      4 | -infinity
      1 | 2015-05-18
      2 | 2015-05-19
      3 | infinity
    (5 rows)
    
    
    infplayground_development=# SELECT id,date FROM events ORDER BY date ASC NULLS LAST;
     id |    date
    ----+------------
      4 | -infinity
      1 | 2015-05-18
      2 | 2015-05-19
      3 | infinity
      5 |
    (5 rows)
    
    
    infplayground_development=# SELECT id,date FROM events ORDER BY date DESC NULLS FIRST;
     id |    date
    ----+------------
      5 |
      3 | infinity
      2 | 2015-05-19
      1 | 2015-05-18
      4 | -infinity
    (5 rows)
    
    
    infplayground_development=# SELECT id,date FROM events ORDER BY date DESC NULLS LAST;
     id |    date
    ----+------------
      3 | infinity
      2 | 2015-05-19
      1 | 2015-05-18
      4 | -infinity
      5 |
    (5 rows)
    
  3. WHERE条件をつける

    条件次第で最初に寄せたり,最後に寄せたりできるので,なんだか null の方が扱いやすそうですね.

    でも WHERE 句をつけたときには null は取れず,何らかの工夫をして取らないといけなさそうです. infinity は意図通り取得できますね.

    infplayground_development=# SELECT id,date FROM events WHERE date > '2015-05-18';
     id |    date
    ----+------------
      2 | 2015-05-19
      3 | infinity
    (2 rows)
    
    
    infplayground_development=# SELECT id,date FROM events WHERE date <= '2015-05-18';
     id |    date
    ----+------------
      1 | 2015-05-18
      4 | -infinity
    (2 rows)
    
    
    infplayground_development=# SELECT id,date FROM events WHERE date = 'infinity';
     id |   date
    ----+----------
      3 | infinity
    (1 row)
    
    
    infplayground_development=# SELECT id,date FROM events WHERE date = '-infinity';
     id |   date
    ----+-----------
      4 | -infinity
    (1 row)
    
    
    infplayground_development=# SELECT id,date FROM events WHERE date > '-infinity';
     id |    date
    ----+------------
      1 | 2015-05-18
      2 | 2015-05-19
      3 | infinity
    (3 rows)
    

Rails(4.2系)から試す

infinity は便利そうですね.それでは Rails ではどのように取得できるか試してみましょう. bin/rails c で console を立ち上げます.

irb(main):001:0> Event.pluck(:id, :date)
   (0.4ms)  SELECT "events"."id", "events"."date" FROM "events"
=> [[1, Mon, 18 May 2015], [2, Tue, 19 May 2015], [3, nil], [4, nil], [5, nil]]

おやっ…… datenil になってしまっていますね.

探してみると ActiveRecord retrieves infinity dates from postgres as nil · Issue #18957 · rails/rails に同じような話がありました. そこからたどると Support infinity in PostgreSQL date columns by mrnugget · Pull Request #17365 · rails/railsFloat::INFINITY を返すように変更されたようです.

このコミット 5ff08055194963394c24742ae89f69e4e43567a4 がどのリリースから含まれているか調べてみましょう.

/Users/kitamura/rails% git tag --list --contains 5ff08055194963394c24742ae89f69e4e43567a4
/Users/kitamura/rails% git branch --list --contains 5ff08055194963394c24742ae89f69e4e43567a4
 * master

今のところ,どのタグにも含まれておらず(つまりまだリリースされていない),また master 以外のどのブランチにも含まれていない(つまり 4.2 系で出る予定がない?)ので,Rails5 が出るまで?はしばらく使えなさそうでした.

まとめ

  • Postgresqlで日付を扱うときに便利そうな infinity について動作を調べました
  • Rails4.2 系ではリリースされなさそうなので,Rails を使っているなら,5 系まで待つ必要があるかもしれません

Rails について詳しくないので,もし気になる点があればフィードバックいただけるとありがたいです.(たぶん twitter などをチェックします)

このエントリーをはてなブックマークに追加