postgreSQLでgroup byを使ったらエラー吐かれた話
前回の実装を本番環境のherokuに上げたら、以下のようなエラーを吐かれた。
ActionView::Template::Error (PG::GroupingError: ERROR: column "messages.id" must appear in the GROUP BY clause or be used in an aggregate function
「messagesテーブルのidは、GROUP BY節に現れるか集計関数で使われなければならない」って意味だと思うんだけど、これだけだと良く分からない…
色々調べて考えたのは「GROUP BY節によって分けられたグループ一つに対する出力は、集計関数などを使って、一つにしなければならない」って感じだと思う(間違ってたらすみません)。
グループ毎の一つの出力を、rails(sqlite3)側では、グループ内で最後に取ってきたレコードをこちらが何の指定もしなくても予測してくれてて、heroku(postgreSQL)側では、グループ内のどのレコードを選んで良いのか分からないってエラーが出ているのかなと。
なので、GROUP BY節を使っていた以下の実装(Messageモデル)を書き換えてみた。
class Message < ApplicationRecord ︙ scope :recent_messages, ->(user_id) { where("dialog_ids like ? or dialog_ids like ?", "%-#{user_id}", "#{user_id}-%").group(:dialog_ids).order(created_at: :desc) } ︙ end
書き換えた実装は以下。
class Message < ApplicationRecord ︙ scope :recent_messages, ->(user_id) { where("id in (?)", Message.recent_dialog(user_id)).order(created_at: :desc) } ︙ private # ユーザが参加しているメッセージの内、最新のメッセージのidを取得する def Message.recent_dialog(user_id) Message.where("dialog_ids like ? or dialog_ids like ?", "%-#{user_id}", "#{user_id}-%").group(:dialog_ids).maximum(:id).values end end
Message.recent_dialog(user_id)で、user_idが参加しているMessageのやり取り(dialog_idsの値)毎に、値が一番大きいid(dialog_idsの値毎に、出力は一つのid)を配列にして渡す→配列をscopeに渡し、配列内のidでレコードを取ってきて、それを最新順に並び替える。
ひとまず、これで上手く行きました。ただ、これだと、テーブルに対して二回検索を掛けてしまっているので凄く不格好…。何か他に良い手があったら教えて下さい^_^;
分かった知識一覧
ハッシュ配列.values
: ハッシュ配列の値のみの配列を返す。レコードオブジェクト.maximum(:カラム名)
: レコード内のカラムの値の内、最大値を返す。group(:カラム2名)
と組み合わせると{ カラム2名=>カラム名, ... }
のようなハッシュ配列として帰ってくる。