先に結論だけ
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で取得する方法を模索します。
前提
ブログサイトをイメージして、以下のようなデータモデルを想定します。
データモデル
各テーブルの説明
記事(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として取得することができるようになりました。