Rails/Postgresで年代を計算する
この記事は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回目)