📘

Rails の ActiveRecord で relation 先のレコードがないときの親レコードのみを抽出する

2024/04/19に公開

タイトルのような短い文字数でばしっと表現することが難しかったのですが、最終的には以下のようなことがやりたかったので、そのために以下のような SQL を発行したかったのです。

結論

  • missing が一番シンプルで便利

やりたいこと

宿題を提出していない生徒一覧を抽出したい、RDB の操作に落とし込むと・・・

  1. Students テーブルに SubmittedHomeworks テーブルを外部結合する
  2. 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

さいごに

ないかなとおもったら大体ある

参考

https://thinkami.hatenablog.com/entry/2023/02/12/222027
https://techracho.bpsinc.jp/hachi8833/2021_02_17/104067
https://railsguides.jp/active_record_querying.html#where-associatedとwhere-missing

Discussion