記事一覧表示

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名=>カラム名, ... } のようなハッシュ配列として帰ってくる。