[Redshift] 誕生日から年齢を計算する(閏年対応版)

2019.08.14

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Redshiftで使うSQLを作成していて年齢計算や会社の期などを計算する必要があったのでその方法を調べました。 RedshiftではAge関数が使えない(リーダーノード専用関数)のでDATEDIFFとDATE_PART(doy)を使う方法が当エントリにあったのでこちらを利用してみました。

改善前

SELECT
user_name,
birthday
FROM public.users;
user_name | birthday
---------+-----------
takashi | 1990-08-06
izumi | 1990-08-07
hanako | 1990-08-08
ken | 1992-08-06
yui | 1992-08-07
ai | 1992-08-08

これらのデータを用いて計算したところ

SELECT
user_name,
birthday,
DATEDIFF (YEAR, birthday, GETDATE ()) + (CASE WHEN DATE_PART (doy, birthday) > DATE_PART (doy, GETDATE ()) THEN - 1 ELSE 0 END) as age,
TO_CHAR(getdate(),'YYYY-MM-DD') as today
FROM public.users;
user_name | birthday | age | today
---------+-----------+-----------+-----------
takashi | 1990-08-06 | 29 | 2019-08-07
izumi | 1990-08-07 | 29 | 2019-08-07
hanako | 1990-08-08 | 28 | 2019-08-07
ken | 1992-08-06 | 27 | 2019-08-07
yui | 1992-08-07 | 26 | 2019-08-07
ai | 1992-08-08 | 26 | 2019-08-07

一見正しく計算できているようですが、計算日が8月7日なのでyuiの年齢が正しく計算できていないようです。 これは以下のSQLで原因がわかります。

SELECT
user_name,
birthday,
TO_CHAR(getdate(),'YYYY-MM-DD') as today,
DATE_PART (doy, birthday) as doy_of_birthyear,
DATE_PART (doy, GETDATE ()) as doy_of_thisyear

FROM public.users;
user_name | birthday | age | today | doy_of_birthyear | doy_of_birthyear
---------+-----------+-----------+-----------+-----------+-----------
takashi | 1990-08-06 | 29 | 2019-08-07 | 218 | 219
izumi | 1990-08-07 | 29 | 2019-08-07 | 219 | 219
hanako | 1990-08-08 | 28 | 2019-08-07 | 220 | 219
ken | 1992-08-06 | 27 | 2019-08-07 | 219 | 219
yui | 1992-08-07 | 26 | 2019-08-07 | 220 | 219
ai | 1992-08-08 | 26 | 2019-08-07 | 221 | 219

doy(day of year)は年間積算日を出してくれますが、1992年は閏年なので一日ずれてしまっているのです。 従ってこの方法ですと誕生日もしくは年齢を求める年が閏年の場合で誕生日当日に計算すると正しく年齢が計算されません。

解決方法

単純に月日の比較で対応します。

TO_CHAR(birthday, 'MMDD') > TO_CHAR(getdate(), 'MMDD')

で月日を抽出してその比較で誕生日を迎えたかどうかを判断します。

SELECT
user_name,
birthday,
DATEDIFF (YEAR, birthday, GETDATE ()) + (CASE WHEN DATE_PART (doy, birthday) > DATE_PART (doy, GETDATE ()) THEN - 1 ELSE 0 END) as age,
DATEDIFF(YEAR, birthday, getdate()) +
(CASE WHEN TO_CHAR(birthday, 'MMDD') > TO_CHAR(getdate(), 'MMDD') THEN - 1 ELSE 0 END) as aeg_2,
TO_CHAR(getdate(),'YYYY-MM-DD') as today
FROM public.users;
user_name | birthday | age | age_2 | today
---------+-----------+-----------+-----------
takashi | 1990-08-06 | 29 | 29 | 2019-08-07
izumi | 1990-08-07 | 29 | 29 | 2019-08-07
hanako | 1990-08-08 | 28 | 28 | 2019-08-07
ken | 1992-08-06 | 27 | 27 | 2019-08-07
yui | 1992-08-07 | 26 | 27 | 2019-08-07
ai | 1992-08-08 | 26 | 26 | 2019-08-07

これで閏年でも問題なく日付が計算できるようになりました。

まとめ

非常に細かい内容でしたが厳密に年齢を計算するには必要なことだと思いますし、これをベースに会社の期を求める際にもたった一日とはいえバグの温床になるので気をつけたいです。

最後まで読んで頂いてありがとうございました。