[Rails] 特定の条件でデータを取得する時のサブクエリの書き方

2024/05/23

Rails

こんにちは。

今回は、Railsで使えるTipsとして、特定の条件でデータを取得する時のサブクエリの書き方について紹介します。

こんなケースを想定しています。

  • ユーザーが投稿した記事の中で、いいね数が最も多い記事を取得したい
  • 記事の中で、最新のコメントのみを取得したい
  • 週次で更新されるPV数のスナップショットの中で、最新のレコードのみを取得したい

いいね数が最も多い記事を取得する

いいね数が最も多い記事を取得するには、以下のようにサブクエリを使います。

# app/models/article.rb
class Article < ApplicationRecord
  scope :most_liked, -> {
    where(id: Like.group(:article_id).order('count(article_id) desc').limit(1).pluck(:article_id))
  }
end

このようにすることで、いいね数が最も多い記事を取得することができます。

最新のコメントのみを取得する

最新のコメントのみを取得するには、以下のようにサブクエリを使います。

# app/models/comment.rb
class Comment < ApplicationRecord
  scope :latest, -> {
    where(id: Comment.group(:article_id).order('max(created_at) desc').pluck(:id))
  }
end
# app/models/article.rb
class Article < ApplicationRecord
  has_many :comments
  has_one :latest_comment, -> { latest }, class_name: 'Comment'
end

このようにすることで、最新のコメントのみを取得することができます。

スナップショットの中で、最新のレコードのみを取得したい

基本的に最新のコメントと一緒と思われるかもしれませんが、こちらはshot_atカラムなどで週次で更新されるPV数のスナップショットの中で、最新のレコードのみを取得する場合です。

# app/models/pv_snapshot.rb
class PvSnapshot < ApplicationRecord
  scope :latest, -> {
    latest_snapshots = select(
      'pv_snapshots.*',
      'ROW_NUMBER() OVER (PARTITION BY pv_snapshots.article_id ORDER BY pv_snapshots.shot_at DESC) AS row_number'
    ).to_sql

    from("(#{latest_snapshots}) AS pv_snapshots").where(row_number: 1)
  }
end

こちらはrow_numberを使って、各記事ごとに最新のレコードのみを取得しています。
idやcreated_atなどのカラムで確実に最新のものと言い切れる場合はいいですが、後から古いデータを取り込み直すケースが発生する可能性がある場合には、このような方法も使えるかもしれません。

このように、特定の条件でデータを取得する時にサブクエリを使うことで、スッキリとしたコードを書くことができます。
ぜひ参考にしてみてください。