LoginSignup
32
19

More than 5 years have passed since last update.

Rails ActiveRecordでgroup_by countによる集計結果をrelationとして取得する

Last updated at Posted at 2018-07-22

先に結論だけ

countメソッドを利用するとhashが返却されるため
selectメソッド内でmysqlのcount処理を記述

articles =
  Article
    .joins(:comments)
    .select('articles.id, articles.category_id, count(comments.id) as comments_count')
    .group(:id)
    .order('comments_count desc')

はじめに

RailsのActiveRecordを通してmysqlでcountした結果を取得しようと思いました。
その際、countメソッドを利用すると返却値がhashとなってしまったため
ActiveRecord::relationで取得する方法を模索します。

前提

ブログサイトをイメージして、以下のようなデータモデルを想定します。

データモデル

名称未設定ファイル.png

各テーブルの説明

記事(articles)

ブログ記事を表します。

カテゴリ(categories)

記事に付与されるカテゴリです。
この投稿記事だとRailsのようなものです。
モデルをシンプルにするために、記事はカテゴリを一つだけ持つことにします。

コメント(comments)

記事に付与されるコメントです。
一つの記事に対して複数のコメントがひもづきます。

データ例

categories

id name
1 音楽
2 プログラミング
3 旅行

articles

id category_id title body
1 1 6月に言ったライブ ...
2 2 rubyについて思うこと ...
3 1 6月に聞いた新譜一覧 ...
4 2 sidekiqを触ってみた ...
5 2 enumerableを理解する ...
6 3 夏休みフランス旅行 ...

comments

id article_id body user_id
1 1 いいね 1
2 2 なるほど 2
3 1 面白い 4
4 4 深いなぁ 5
5 3 うける 9
6 3 ナイス 10
7 1 勉強になる 8

今回取得したいデータ

概要

ActiveRecordを通じて 各記事ごとのコメント数ランキング を取得します。
また、その際に

  • 記事タイトル(articles.title)
  • カテゴリ名(categories.name)

も取得します。

必要データ

id category_name title comment_count
1 音楽 6月に言ったライブ 3
3 音楽 6月に聞いた新譜一覧 2
2 プログラミング rubyについて思うこと 1
4 プログラミング sidekiqを触ってみた 1

こんなイメージです。

まずは: count・sizeメソッドを使う

ActiveRecordを通じてcount文を発行するメソッドに countやsizeがあります。(どちらを使うべきかは他の記事で多々挙げられているので、そちらを参考のこと)
今回はcountメソッドを用いて集計をしてみます。

counts = 
  Article
    .joins(:comments)
    .group(:id)
    .order('count_all desc')
    .count

発行されるクエリは以下の通りです。

SELECT  
  COUNT(*) AS count_all, 
  `articles`.`id` AS articles_id 
FROM `articles` 
INNER JOIN `comments` 
  ON `comments`.`article_id` = `articles`.`id` 
GROUP BY `articles`.`id` 
ORDER BY count_all desc

返却されるデータは以下の通りです。

{ 1=>3, 3=>2, 2=>1, 4=>1 }

ActiveRecord::relationではなくhashが返却されてしまったため、
記事タイトルおよびカテゴリ名を取得するには、
記事リレーションを取得するクエリを再発行することが必要になります。

articles = Article.find(counts.map{|id, count| id})

つぎに: selectメソッドを使う

あまり綺麗な方法ではないですが、
countメソッドを使う代わりにselectメソッドを用いて
明示的にselect文内で集約処理を実現します。

articles =
  Article
    .joins(:comments)
    .select('articles.id, articles.category_id, count(comments.id) as comments_count')
    .group(:id)
    .order('comments_count desc')

発行されるクエリは以下の通りです。

SELECT  
  articles.id, 
  articles.category_id, 
  count(comments.id) as comments_count 
FROM `articles` 
INNER JOIN `comments` 
  ON `comments`.`article_id` = `articles`.`id` 
GROUP BY `articles`.`id` 
ORDER BY comments_count desc

返却されるデータは以下の通りです。

[
  #<Article: id: 1, category_id: 1>,
  #<Article: id: 3, category_id: 1>,
  #<Article: id: 2, category_id: 2>,
  #<Article: id: 4, category_id: 2>
]

Articleモデルのrelationが返ってきました。

コメント数を取得する場合は

articles[0].comments_count
=> 3

カテゴリ名を取得する場合は

articles[0].category.name
=> "音楽"

relationとして取得することができるようになりました。

32
19
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
32
19