Rails/Postgresで年代を計算する | Dev Driven 開発・デザインチーム Rails/Postgresで年代を計算する | 働くひとと組織の健康を創る iCARE

BLOG

Rails/Postgresで年代を計算する

shogofukuden
2021/12/12

この記事はiCARE Dev Advent Calendar 2021の12日目です。
てっきり担当が13日目だと思っていたら今日でした...
今回はユーザーデータの年代の集計に着いて書きたいと思います

なお、versionは
Rails: 5.2
PostgreSQL: 12.3
にて検証しています。

年齢計算について

個人情報を扱うシステムで、ユーザーの年齢を使う時があると思います。
大抵は処理時点の年齢を出したいことが多く、
年齢ではなく生年月日をDBに保存して計算すると思います。
Railsを使う場合、それらの計算はRails側ですることが多いと思いますが
PostgreSQL側ですると負荷軽減が出来ることもあるので、その紹介です。

Railsでするなら

例えば、customers(顧客)テーブルにborn_on(生年月日)がある場合、
以下のようなメソッドで年齢計算ができそうです

def age(date)
    date_format = "%Y%m%d"
    (date.strftime(date_format).to_i - born_on.strftime(date_format).to_i) / 10000
end

customer = Customer.find(x)
# 生年月日
 customer.born_on
=> Mon, 30 Jan 1984
# 年齢計算
 customer.age(Time.zone.today)
=> 37
# 年代の計算も
 customer.age(Time.zone.today).floor(-1)
=> 30

上記は単体なら良いのですが、
customerが複数ありIDと年齢を取りたいときなどは、
mapなどでゴニョゴニョする必要があります
Customer.where(~~~).map { |c| [c.id, c.age] }
これをすると、メモリ展開や計算量が増えるので、できれば避けたいですね...

PostgreSQLでするなら

そのまんまなage関数があります笑
これに少し手を加えて、こんな感じでしょうか?

select_query = <<SQL
    customers.id,
    "cast(date_part('year', age('#{Time.zone.today}', customers.born_on)) as int) as customer_age"
SQL
Customer.select(select_query)

もし年代を取りたいなら...こんな感じでしょうか?

select_query = <<-SQL
  customers.id,
  cast(trunc(cast(date_part('year', age('#{Time.zone.today}', customers.born_on)) as int ), -1) as int) as customer_age
SQL
Customer.select(select_query)

DB側に処理を寄せることが出来ました。Railsでゴニョゴニョしなくて良くなりましたね!
gorup by → count とかしたいときも、select句のものでgorupするだけです

しかし分かりにくいし、めんどくさいですね!!!

どう使い分けると?

レコードが少ない(数十や数百ぐらい)なら、Rails側で素直にしてしまったほうが良いと思います。
しかい、何万件も集計する場合、Rails側で1レコードずつ年代などを集計するのは(機能によりますが)厳しい可能性があります。
その場合DB側に寄せると改善する場合もあります。
とはいえDBになんでも寄せれば良いというわけでは当然ないので
EXPLAINなど各種方法で計測をしましょう!

...本当に計測をしましょう!!(大事なことなので2回)

最後に

大抵のWebアプリケーションでしたいことはRailsで簡単に出来ます。
が、その分キレイ、負荷を適切にシステムを維持するのは非常に難しいです。
常にどの実装が、設計的にも付加的にも適切が考え、
よりbetterな実装をしたいです。
なので、計測をしましょう!!(念押し3回目)