- 更新日: 2014年4月30日
- Rails
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 を使います。
1 |
ReputationSystem::Evaluation.select(:target_id).uniq |
以下の SQL が発行される。
1 2 3 |
SELECT DISTINCT target_id FROM `rs_evaluations` |
INNER JOIN には joins を使う
has_many, belongs_to が設定されているモデル同士の場合は、以下のように joins の API を使うだけで INNER JOIN できます。便利!
1 |
User.joins(:posts) |
↓
1 2 3 |
SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` |
has_many, belongs_to がない場合は、joins メソッドにクエリの内容を渡せます。LEFT OUTER JOIN などもこれで指定。
1 |
ReputationSystem::Evaluation.joins('INNER JOIN posts ON posts.id = rs_evaluations.target_id') |
↓
1 2 3 |
SELECT `rs_evaluations`.* FROM `rs_evaluations` INNER JOIN posts ON posts.id = rs_evaluations.target_id |
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 |
ActiveRecord::Base.connection.select("SELECT posts.category FROM rs_evaluations INNER JOIN posts ON posts.id = rs_evaluations.target_id WHERE rs_evaluations.reputation_name = 'likes' AND rs_evaluations.source_type = 'User' AND rs_evaluations.target_type = 'Post' GROUP BY posts.category ORDER BY MAX(rs_evaluations.created_at) DESC LIMIT 10") |
以下の内容のクエリです。
1. rs_evaluations に INNER JOIN で posts を結合
2. WHERE 句でレコードの条件を指定
3. posts の category カラムの値でグループ化
4. グループ毎の rs_evaluations.created_at の最大値、つまり一番新しいレコードを用いてソート
5. 10件取得
ややこしいクエリが必要な場合は、Rails で ActiveRecord を使っていても、生SQLを使う場面もわりとありますね。
- – 参考リンク –
- Active Record Query Interface — Ruby on Rails Guides
- ActiveRecord::QueryMethods#joins のメモ – willnet.in
- ActiveRecordでDISTINCTを使う – sessanの日記
- Ruby-on-Rails: Selecting distinct values from the model – Stack Overflow
- strix01: [Rails]reverse_orderで降順ソート
- MySQLのLEFT JOIN, RIGHT JOIN, INNER JOINの自分用まとめ – (゚∀゚)o彡 sasata299’s blog
- 重複行を除外(ALL, DISTINCT) – データの取得 – MySQLの使い方
- [Q&A] SQL文「DISTINCT」の「ORDER BY」について 【OKWave】
- Rails の関連記事
- RailsでMySQLパーティショニングのマイグレーション
- Rails ActiveRecordでdatetime型カラムのGROUP BY集計にタイムゾーンを考慮する
- RailsプラグインGemの作成方法、RSpecテストまで含めたrails pluginの作り方
- RailsでAMPに対応するgemをリリースしました
- Railsでrequest.urlとrequest.original_urlの違い
- Railsでwheneverによるcronバッチ処理
- Google AnalyticsのRails Turbolinks対応
- Railsアプリにソーシャル・シェアボタンを簡単設置
- Rails監視ツール用にErrbitをHerokuで運用
- Facebook APIバージョンのアップグレード手順(Rails OmniAuth)
Leave Your Message!