みなさん、こんにちは。まどぎわです(・∀・)
今回は、ちょっと複雑な条件をサブクエリを使って効率よく取得する方法について書こうと思います✍
今回のケース
今回は、「公開中の投稿に紐づくコメントのみを取得する」というケースで考えてみようと思います🤔
前提事項を下記に整理したので、参考までに。
- 投稿: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が発行されてしまうので注意ですね👀