ActiveRecordでグループ中の特定のカラムに着目してレコードを選択する


「男女別で最年長のユーザを選びたい」など、グループ化した際にどのレコードを選ぶのかを特定のカラムにより決めたいことが時々ある。

たとえば以下のusersテーブルで男女ごとに最年長の人を選ぶ時、次のように書く。

id name age sex
1 suzuki 20 male
2 sato 25 male
3 tanaka 35 female
4 hayashi 30 female
# sexカラムのグループ中で最大のageを持つレコードのidを収集する
sql = 'select id from users as m where age = (select max(age) from users as s where m.sex = s.sex);'
user_ids = User.find_by_sql(sql).map(&:id)
# User Load (0.5ms)  select id from users as m where age = (select max(age) from users as s where m.sex = s.sex);
# => [2, 3]

users = User.where(id: user_ids)
# User Load (0.2ms)  SELECT "users".* FROM "users"  WHERE "users"."id" IN (2, 3)
# => #<ActiveRecord::Relation [
#      #<User id: 2, name: "sato", age: 25, sex: "male", created_at: "2015-05-01 10:27:54", updated_at: "2015-05-01 10:27:54">,
#      #<User id: 3, name: "tanaka", age: 35, sex: "female", created_at: "2015-05-01 10:28:04", updated_at: "2015-05-01 10:28:04">
#    ]>

余談

この方法を見つけるまで同じような問題に対して次のコードのようにordergroupで取り組んでいたが、この方法では上手くできない。

User.order('age desc').group(:sex)

そのため、これまではorderでソートだけ行って同じグループのレコードを繰り返し表示しないように制御するというグダグダな方法をとっていた。

調べたところSQLによる解決方法を紹介しているページはいくつか見つかったが、ActiveRecordの例は見つからなかったのでここに書いてみた。
もっとスマートな方法を知っている人がいれば、ぜひ教えてほしい:)

参考記事