ActiveRecord で、関連先のテーブルの条件で絞り込む
はじめに
ActiveRecord で、少し複雑なクエリを発行したかったのだが、書式が分からなかったので、自分用に調べためも。
状況
アソシーションは以下の User:Book = 1:N の関係。カラムなどは以下の状態とする(created_at など関係ないカラムは省いた)。
User モデル
カラム | 型 |
---|---|
id | int |
name | string |
class User < ApplicationRecord
has_many :books
end
Book モデル
カラム | 型 |
---|---|
id | int |
user_id | int |
book_type | int |
passed | boolean |
class Book < ApplicationRecord
belongs_to :user
end
紐づくBookで、type別の最新がすべて合格(passed: true) となっているUserの取得
User は Book を複数登録しており、各Bookにはスタッフが内容審査をし、合格したかどうか判別するpassedカラムがあるとする。
結論
以下のクエリで取得できる
books = Book.where(id: Book.group(:user_id, :book_type).select('max(id)').having(passed: false))
users = User.joins(:books).merge(books).distinct
発行されるSQLは以下。
SELECT DISTINCT "users".*
FROM "users"
INNER JOIN "books" ON "books"."user_id" = "users"."id"
WHERE "books"."id"
IN
(SELECT max(id)
FROM "books"
GROUP BY "books"."user_id", "books"."book_type"
HAVING "books"."passed" = ?) [["passed", 0]]
ポイント
関連テーブルでの絞り込み
関連テーブルの条件による絞り込みは、merge メソッドで以下の書式で行える。
// 例
// merge の引数には、関連テーブル側での条件を記述する
users = User.joins(:books).merge(Book.where(passed: true)).distinct
// 発行されるSQL
SELECT DISTINCT "users".*
FROM "users"
INNER JOIN "books" ON "books"."user_id" = "users"."id"
WHERE "books"."passed" = ? [["passed", 1]]
これにより、Aと紐づくBの条件でAを絞り込み、取得できる。
merge 前に joins しておかないと、関連テーブルは読み込めないので注意。
また、他にincludesとwhereを使う方法もある。
users = User.includes(:books).where(books: {passed: true})
// 発行されるSQL
SELECT
"users"."id" AS t0_r0, "users"."name" AS t0_r1, "users"."created_at" AS t0_r2, "users"."updated_at" AS t0_r3, "users"."status" AS t0_r4,
"books"."id" AS t1_r0, "books"."title" AS t1_r1, "books"."user_id" AS t1_r2, "books"."created_at" AS t1_r3, "books"."updated_at" AS t1_r4,
"books"."price" AS t1_r5, "books"."book_type" AS t1_r6, "books"."passed" AS t1_r7
FROM "users"
LEFT OUTER JOIN "books" ON "books"."user_id" = "users"."id"
WHERE "books"."passed" = ? [["passed", 1]]
発行される SQL は異なるが、取得されるカラムは同じ。パフォーマンスなどと相談して決める。
merge の方は、joins で内部結合するので、distinct 句が必要な点に注意。
複数回のgroup化
今回、Book を user_id でグループ化し、そのグループそれぞれに対してさらにtypeでグループ化→最新のものを取得、という要件であった。
そのため、まず user_id についてグループ化し、続いてtypeでグループ化する必要があった。これは、group メソッドに複数引数を渡すことで実現できる。
books = Book.where(
id: Book.group(:user_id, :book_type)
.select('max(id)')
.having(passed: false)
)
// 発行されるSQL
SELECT "books".*
FROM "books"
WHERE "books"."id"
IN (
SELECT max(id)
FROM "books"
GROUP BY "books"."user_id", "books"."book_type"
HAVING "books"."passed" = ?) [["passed", 0]]
しばらくこれにたどり着かず迷った。
group の公式document はこちら。ここにも書いてた。
select
ActiveRecord の select は、SELECT句に直接クエリ文字列を挿入することができる。
こちらも、公式ドキュメントに書いてたので要参照。
感想
やはり理解できていない原因を落ち着いて切り分ける&ある程度問題が明確になったら、公式ドキュメントベースに動かしながら一つずつ確認、が大事だなぁ。。。と思いましたまる。
Author And Source
この問題について(ActiveRecord で、関連先のテーブルの条件で絞り込む), 我々は、より多くの情報をここで見つけました https://qiita.com/seimaru/items/020e10e12415f5055a91著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .