はじめに
ActiveRecordのメソッドだけでは表現できないような書き方についてまとめます。
きっかけは、Railsのselectメソッドで、SQLを直接書く場面があったことです。
その時にSQLの重要性がより理解できたので共有させて頂きます。
前提知識
selectメソッド
データベースから取得するカラムを指定するためのメソッドです。
selectメソッドは、SQLのSELECT句に相当します。
基本的な書き方
# 全てのカラムを取得
User.select("*")
# 特定のカラムのみを取得
User.select(:name, :email)
複数のテーブルを結合して取得
# UserモデルとPostモデルを結合して、ユーザー名と投稿タイトルを取得
User.joins(:posts).select("users.name, posts.title")
joins
によるテーブル結合についてはこちらの記事をご参考ください。
このようなメソッドを利用することで、生のSQLを書かずにデータベースへの特定のクエリを実行するための引数を渡せるようになります。
本題
目的
ユーザーの最新の投稿(posts)の更新日時と、現在の日時の差を日数で計算する。
実装したコード
users.joins(:posts)
.select(
users.*,
COALESCE(
DATEDIFF(
CURDATE(),
(SELECT MAX(posts.update_at) FROM posts)
), 0)
)
)
解説
COALESCE
複数の引数を受け取り、NULL ではない最初の引数を返すMySQLの関数です。
全ての引数がNULLの場合は、NULLを返します。
COALESCE(expression1, expression2, ..., expression_n)
今回の例で言うと、最初の引数はDATEDIFF()
の部分です。
引数の値がNULLのレコードは、0を返すように第2引数で設定しています。
DATEDIFF
ある日付から別の日付までの日数を返します。値の日付部分のみが計算に使用されます。
時間または日付時間式ですが、両方の日付とも同じ型にする必要があります。
DATEDIFF( <date_or_time_part>, <date_or_time_expr1>, <date_or_time_expr2> )
似たような関数でTIMESTAMPDIFF関数があります。
こちらはTIMEDIFF関数と違って、式の一方が日付の型で、他方が日付時間の型にすることもできます。
サブクエリ(副問い合わせ)
DATEDIFF
関数で、サブクエリ(SELECT MAX(posts.update_at) FROM posts)
を書いてます。
これによってpostsテーブルの最新レコードのupdated_at
カラムの値を取得しています。
サブクエリの基本についてはこちらの記事が分かりやすいと思います。
サブクエリを書いた理由
理由は、日数の計算をするためにどのテーブルの、いつのレコードの更新日時(updated_atカラム)を参照するのかを明確にするためです。
MySQLのやり取りを日常的な人間の会話に置き換えると、
人間「updated_atカラムの値を参照して!」
MySQL「いや、なんのっ?(どのテーブルのいつのレコードの??)」
質問は具体的に...(特大ブーメラン🪃)
CURDATE()
現在の日付を'YYYY-MM-DD'
またはYYYYMMDD
形式の値として返します。
mysql> SELECT CURDATE();
-> '2008-06-13'
mysql> SELECT CURDATE() + 0;
-> 20080613
おわりに
MySQL関数を使うことで複雑な処理をより簡潔に書けるので有り難いなと思いました。
MySQL 8.0 リファレンスマニュアルという公式ドキュメントも今回の件で知ったので、時間ある時に目を通しておきたいと思います。