動機
特定カラムが同じレコードを、そのどれでもいい一つのみを代表して重複を一つにまとめ、レコードと対応するインスタンスとして取得する...というクエリを組むとき苦労したので備忘録です。
当初distinctメソッドを利用してみようとしていましたが、primary_keyが取得できず(selectメソッドにidを組み込むと、idが異なることで重複していないとみなされるため)、レコードと対応するインスタンス化できませんでした。
特定カラムのみをselectしてdistinctすると、idがnil、カラムがまとめた値、という形になります。
distinctの参考:
上記記事のdistinctはselectしなくても問題ないか?
のような操作がやりたかったイメージです。
グルーピングという解決策
以下の記事を参考に、解決に一歩近づくことができました。
parent_hoge_id
カラムでグルーピングして、グループごとに最大のIDの1レコードを取得しています。
HogeModel.where(id: { HogeModel.group(:parent_hoge_id).select('max(id)') })
scope化
これを毎回書くのはわかりにくいし面倒なのでscope化しようと思いました。
まず以下のようにしました。
scope :uniq_by_parent_hoge_id, -> { where(id: HogeClass.group(:parent_hoge_id).select('max(id)')) }
しかし後日気付いたのですが、当然これを利用するシーンでは以下のように他の条件を重ねることになります。
HogeModel.where(ex_condition).uniq_by_parent_hoge_id
この場合、グルーピングのためのHogeClass.group(:parent_hoge_id).select('max(id)')
での絞り込みは、前者のex_conditionを満たすとは限りません。
where条件で絞り込んだものをグルーピングしたいので、以下のようなチェーンにしたいのですが、上のscopeではそのようにはできません。
HogeModel.where(id: { HogeModel.where(ex_condition).group(:parent_hoge_id).select('max(id)') })
ということで、修正したscopeが以下のようになりました。
scope :uniq_by_parent_hoge_id, -> { HogeModel.where(id: group(:parent_hoge_id).select('max(id)')) }
groupメソッドは、selfから呼ばれています。
scope内でのselfは、このscopeメソッドを呼んだ、クラスやwhereチェーンなどになっています。つまり今回の例ではHogeModel.where(ex_condition)
です。
これにより、目標のクエリの発行が可能になりました。
まとめと注意
SQLクエリから考える、いい勉強になりました。
注意点としては、このscopeを呼んだ際は呼び出し元のクエリに基づいてグルーピングを行うので、やりたい絞り込みをチェーンでつないだ最後に呼ぶ必要があるということになります。
以上、何かの役に立てば幸いです。