LoginSignup
0
0

More than 5 years have passed since last update.

ActiveRecordがgroupごとに orderしてくれなくて困った。

Last updated at Posted at 2018-12-01

結論

MySQLの仕様上、処理順はgroup by→order byなので、
Somemodel.order('some desc').group(:some_column) のようにしても
グループごとにソートされた値は取ってこれなかった。

MySQLでGROUP BYとORDER BYを同時に使用する場合に気をつけたいこと
https://norm-nois.com/blog/archives/1293

pluckで配列として取ってきたものを、頑張って処理すればほしい値が取れる。
頑張って配列を処理する方法がわからない方は、
最後まで見ても良いかもしれません。

どんなことがしたかったか

Blogというモデルのblogsテーブルには
id, category, user_id, text_length というカラムを持っていたとして、
user_idのcategoryごとに、text_lengthが最大のidを取得したかった。
(UserとBlogが1対多のイメージです)

最初やろうとしたこと

Blog.order('text_length desc').group(:category,:user_id).pluck(:user_id,:category)
# 出てくると思ってたクエリ
SELECT user_id, category FROM  blogs
ORDER BY text_length desc
GROUP BY category, user_id 

ところがどっこい、出てくるクエリはORDER BY とGROUP BYが逆転してて、
出てくる値も当然期待通りのものではありませんでした。
(生クエリ書いて、逆転させても、エラーが出ます)

GROUP BY が先に実行されるので、
category,user_idごとのgroup(idが一番若いものが代表になる)になってから、
それをtext_lengthでorderするので、全くuserごとのtext_lengthとは関係ないものが出てきてしまいました。

そしてこれができないのはMySQL側の問題のよう(結論の記事をご確認ください)なので、
仕方なくpluckしてRails側でgroup_byしてからorderすることにしました。

以下はただの配列操作したという話

# 本当は全カラム取るならpluckだけでいいですが、
# 順番を明示するためにカラムを明示してpluckしてます
blogs = Blog.pluck(:user_id,:category,:id,:text_length)
#=> blogs = [
# [1, 2, 1, 4], [1, 2, 2, 5], [1, 1, 3, 6], [1, 1, 4, 7], 
# [2, 2, 5, 6], [2, 2, 6, 4], [2, 1, 7, 8], [2, 1, 8, 7]]
grouped_blogs = blogs.group_by {|b| [b.first, b.second]} 
grouped_blogs.each do |_key,value|
  max_ary = value.max_by(&:fourth) # 4つ目がtext_lengthなので. max_byは便利
  p "user_id:#{max_ary[0]} category:#{max_ary[1]} id:#{max_ary[2]} text_length:#{}{max_ary[3]}"
end
#=> # 
#   "user_id:1 category:2 id:2 text_length:5"
#   "user_id:1 category:1 id:4 text_length:7"
#   "user_id:2 category:2 id:5 text_length:6"
#   "user_id:2 category:1 id:7 text_length:8"

こうして欲しい情報を得られました。
pluckした後にカラム名でいい感じにhashにしたいですが、
簡単な方法は調べても出てこなかったので諦めてます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0