Madogiwa Blog

主に技術系の学習メモに使っていきます。

RubyonRails:whereでサブクエリを使って効率よく対象を絞って取得する方法👀

みなさん、こんにちは。まどぎわです(・∀・)
今回は、ちょっと複雑な条件をサブクエリを使って効率よく取得する方法について書こうと思います✍

今回のケース

今回は、「公開中の投稿に紐づくコメントのみを取得する」というケースで考えてみようと思います🤔
前提事項を下記に整理したので、参考までに。

  • 投稿:Post、コメント:Comment
  • 投稿とコメントは 1:N(has_many)の関係
  • 公開済みの投稿はopened: true

対応案を考えてみる

公開中の投稿を変数に入れて紐づくコメントを取得する(いまいち😭)

これはあんまり良くなさそうだなーというのを書いてみました😥
公開中の投稿を取得してpostsに格納したあとに、それに紐づくコメントを取得しています。 これは、コード行数も2行でコメントを取得する際に毎回SQLが発行されしまうため非効率です。。。

$ posts = Post.where(opened: true)
# => (0.7ms)  SELECT "posts"."id" FROM "posts" WHERE "posts"."opened" = "true"
$ comments = posts.map(&:comments)
=> # 公開中の投稿の数だけ、コメントを取得するSQLが発行される・・・。

pluckを使ってみる(スッキリ書けるけど、効率いまいち🤔)

では、ActiveRelation#pluckを使ってみるのはどうでしょうか?🤔
pluckを使って公開中の投稿のidの配列を取得し、コメントを取得条件に渡します。 1行でスッキリ書けて、一見良さそうに見えますが公開中の投稿のidを取得するSQL公開中の投稿に紐づくコメントを取得するSQLの2回、SQLが発行されてしまっています😥

$ PostComment.where(post_id: Post.where(opened: true).pluck(:id))
=> (0.7ms)  SELECT "posts"."id" FROM "posts" WHERE "posts"."opened" = "true"
   PostComment Load (9.9ms)  SELECT "post_comments".* FROM "post_comments" WHERE "post_comments"."post_id" IN ($1, $2, $3, $4)  [["id", 1], ["id", 2], ["id", 3], ["id", 4]]

※👇pluckの詳しい説明はこちら
Active Record クエリインターフェイス #pluck

selectを使う(スッキリ書けて効率よい🙌)

こういうケースは、ActiveRecord:: QueryMethods#selectを使うと、きれいに効率よく書くことが出来ます💡
書き方はpluckのときとほとんど一緒です。selectを使うことによって、公開中の投稿に紐づくコメントを取得するSQLのIN句に公開中の投稿のidを取得するサブクエリが発行されるようになり一回のSQLで取得することが出来ます!スッキリかけて効率的ですね🙌

$ PostComment.where(post_id: Post.where(opened: true).select(:id))
=> PostComment Load (2.2ms)  SELECT "post_comments".* FROM "post_comments" WHERE "post_comments"."post_id" IN (SELECT "posts"."id" FROM "posts" WHERE "posts"."opened" = "true")

※👇selectの詳しい説明はこちら
Active Record クエリインターフェイス #select

おわりに

ActiveRecord:: QueryMethods#selectをwhereで使うとサブクエリを使って効率よくスッキリ目的のレコードを取得することが出来ます🙌
今まで結構書いてしまっていた気もしますが、pluckだと毎回SQLが発行されてしまうので注意ですね👀