Rails ActiveRecord で DISTINCT, JOIN, GROUP BY, ORDER BYなど

ちょっと複雑な SQL を組み立てたかったので、ActiveRecord で DISTINCT や JOIN などを使う方法を調べました。DISTINCT は select で指定したカラム値の重複を取り除いてレコードを取得する。ActiveRecord では uniq という API で DISTINCT を使えます。JOIN は Rails では joins という API で、INNER JOIN に相当する。

スポンサーリンク

— 環境 —
rails (4.0.1)
activerecord (4.0.1)

ActiveRecord Reputation System という gem で作成されるDBテーブルをいじっていた時の作業だったので、以下それで使う ReputationSystem::Evaluation モデル(DBテーブルは rs_evaluations)に対する操作の例です。

ActiveRecord Reputation System で「いいね!」機能を導入する | EasyRamble

DISTINCT には uniq を使う

select で指定した属性(カラム)の値の重複を取り除くには、uniq を使います。

以下の SQL が発行される。

INNER JOIN には joins を使う

has_many, belongs_to が設定されているモデル同士の場合は、以下のように joins の API を使うだけで INNER JOIN できます。便利!

 ↓

has_many, belongs_to がない場合は、joins メソッドにクエリの内容を渡せます。LEFT OUTER JOIN などもこれで指定。

 ↓

ActiveRecord Reputation System の rs_evaluations テーブルでは、target_id カラムに Post モデルの id が 格納されます。なので、ON posts.id = rs_evaluations.target_id で、親子関係にある posts テーブル(POSTモデル)と rs_evaluations テーブル(ReputationSystem::Evaluationモデル)を JOIN しています。

GROUP BY と ORDER BY

重複レコードのグループを1つにまとめた後、グループの特定のカラム値の最大値・最小値などでソートする場合は、SQL では GROUP BY と ORDER BY を使います。これを ActiveRecord API の group と order を使ってやろうとしたのですけど、なぜか上手く行きませんでした。なので最終的に ActiveRecord::Base.connection.select を使い生SQLを発行して解決しました・・・ が、なにか方法がある気がします。

以下の内容のクエリです。

1. rs_evaluations に INNER JOIN で posts を結合
2. WHERE 句でレコードの条件を指定
3. posts の category カラムの値でグループ化
4. グループ毎の rs_evaluations.created_at の最大値、つまり一番新しいレコードを用いてソート
5. 10件取得

ややこしいクエリが必要な場合は、Rails で ActiveRecord を使っていても、生SQLを使う場面もわりとありますね。

スポンサーリンク
パーフェクト Ruby on Rails は、最近読んだ Rails 本の中では一番役に立った本です。Chef や Capistrano など Rails と共によく使用される技術にも触れてあります。Ruby on Rails 4 アプリケーションプログラミングは、入門的な内容で Rails の機能全体を網羅されています。
スポンサーリンク
 
Twitterを使っていますのでフォローお願いたします!ブログの更新情報もつぶやいてます^^
(英語学習用)

Leave Your Message!