🐰
Rails の ActiveRecord で relation 先のレコードがないときの親レコードのみを抽出する
タイトルのような短い文字数でばしっと表現することが難しかったのですが、最終的には以下のようなことがやりたかったので、そのために以下のような SQL を発行したかったのです。
結論
- missing が一番シンプルで便利
やりたいこと
宿題を提出していない生徒一覧を抽出したい、RDB の操作に落とし込むと・・・
- Students テーブルに SubmittedHomeworks テーブルを外部結合する
- SubmittedHomeworks が結合されていない student_id を抽出する
SQL で表現すると以下のようになります。
SELECT
students.id,
students.name
FROM
students
LEFT OUTER JOIN submitted_homeworks ON submitted_homeworks.student_id = students.id
WHERE
submitted_homeworks.id IS NULL
※今回のような例では、課された宿題を管理するための宿題マスタのテーブル(Homeworks)が必要ですが、前提の説明を簡略化するために省いています
このように SQL を書いて実行するのもいいですが ActiveRecord でシンプルに管理したいです。
ActiveModel
今回使用するモデルのコードだけおいておきます。
上記の SQL からも推測できると思いますが、Students と SubmittedHomeworks は 一対多の関係です。
class Student < ApplicationRecord
has_many :submitted_homeworks
end
class SubmittedHomeworks < ApplicationRecord
belongs_to :student
end
left_joins(or left_outer_joins) と where で抽出する
上記の SQL を ActiveRecord のメソッドに置き換えてチェーンしていく方法です。
Student.left_joins(:submitted_homeworks).where(submitted_homeworks: {id: nil})
発行される SQL は以下のとおり、期待通りの SQL が発行されているのであとは select や pluck なりでカラムを抽出していけばよさそうです。
SELECT
"students".*
FROM
"students"
LEFT OUTER JOIN "submitted_homeworks" ON "submitted_homeworks"."student_id" = "students"."id"
WHERE "submitted_homeworks"."id" IS NULL
missing で抽出する
Rails 6.1 から追加されたメソッドで missing を使用すれば 関連先のテーブルに関連付けされた
レコードが存在しないもののみを抽出することができます。
where メソッドにチェーンして関連先テーブルを指定することで使用できます。
Student.where.missing(:submitted_homeworks)
発行される SQL は以下のとおり、期待通りの SQL が発行されています。
SELECT "students".*
FROM
"students"
LEFT OUTER JOIN "submitted_homeworks" ON "submitted_homeworks"."student_id" = "students"."id"
WHERE "submitted_homeworks"."id" IS NULL
さいごに
ないかなとおもったら大体ある
参考
Discussion