LoginSignup
4
3

More than 1 year has passed since last update.

サブクエリと闘うActiveRecord(JOIN先の最新や最大値のレコードを取得する)

Last updated at Posted at 2021-12-31

0. 背景・こんな時に使いたいなど

ユーザー毎の最高得点や、ユーザー毎の最新の投稿などを取得・表示したいケースというのはよくあると思う。

そんな場合のSQL(ActiveRecord)は以下に記すようにある程度パターンとして決まっている気がするので、便利用に記事にしておく。

1. ユーザ毎の投稿の最新日時を降順で表示する

1-1. WHEREのサブクエリパターン

> User.joins(:posts).where(posts: { created_at: Post.group(:user_id).select('MAX(created_at)') }).order('posts.created_at DESC')
# => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" 
#    WHERE "posts"."created_at" IN (SELECT MAX(created_at) FROM "posts" GROUP BY "posts"."user_id")
#    ORDER "posts"."created_at"

解説

  • WHERE のサブクエリ( WHERE "posts"."created_at" IN (SELECT MAX(created_at) FROM "posts" GROUP BY "posts"."user_id") )で、外部キーである user_idGROUP BY を行い、その最新の投稿を取るために MAX(created_at) をしている。

参考リンク

1-2. JOINのサブクエリパターン

> User.joins(%|
    INNER JOIN (
      SELECT
        "posts"."user_id" AS user_id,
        MAX(created_at) AS max_created_at
      FROM
        "posts"
      GROUP BY
        "posts"."user_id"
    ) AS posts_max_created_at
    ON users.id = posts_max_created_at.user_id
  |).order("max_created_at DESC, users.id ASC")
# => SELECT "users".* FROM "users"
#    INNER JOIN (SELECT "posts"."user_id" AS user_id, MAX(created_at) max_created_at FROM "posts" GROUP BY "posts"."user_id") AS posts_max_created_at
#    ON users.id = posts_max_created_at.user_id
#    ORDER BY max_created_at DESC, users.id ASC

解説

  • INNER JOIN のサブクエリ (SELECT "posts"."user_id" AS user_id, MAX(created_at) AS max_created_at FROM "posts" GROUP BY "posts"."user_id") を使用する方法。

参考リンク

1-3. NOT EXISTSのサブクエリパターン

> User.joins(:posts).where(%|
    NOT EXISTS (
      SELECT
        1
      FROM
        posts AS p2
      WHERE p2.user_id = posts.user_id
        AND p2.created_at > posts.created_at
    )
  |).order('posts.created_at DESC')
# => SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" 
#    WHERE NOT EXISTS (SELECT 1 FROM "posts" AS "p2" WHERE "p2"."user_id" = "posts"."user_id" AND "p2"."created_at" > "posts"."created_at")
#    ORDER BY "posts"."created_at" DESC

解説

  • サブクエリで定義したエイリアス付きの posts(p2) で擬似的にグルーピングを行い、それより大きな created_at が存在しないものを NOT EXISTS 句で抽出する。

参考リンク

2. その他応用的なサブクエリの使い方

あとで読んでまとめたい。

4
3
1

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
4
3