SQL サブセレクト(Rails Tutorial 14章)

またつらつらと書き始めますヽ(•̀ω•́ )ゝ✧ gvimのguifontwideをゆたぽんにしたのだ。かわいいぽん。

サブクエリ

  • フォローしてるユーザの投稿(と,自分自身の投稿)を取得したい。
def feed
  Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
end

は遅い。 なぜなら

following_idsでフォローしているすべてのユーザーをデータベースに問い合わせし、さらに、フォローしているユーザーの完全な配列を作るために再度データベースに問い合わせしている

データが数千件あると死ぬ。

解決策

サブセレクトを使う。 集合のロジックをDB内に保存する。結果的に問い合わせ回数が減る。

  • まずfollowing_idsをSQLに置き換える
# レシーバのユーザがフォローしてるユーザを全て選択
following_ids = "SELECT followed_id FROM relationships
                 WHERE  follower_id = :user_id"
  • これを既存のSQLに内包させる
def feed
  following_ids = "SELECT followed_id FROM relationships
                     WHERE follower_id = :user_id"
  Micropost.where("user_id IN (#{following_ids})
                     OR user_id = :user_id", user_id: id)
end

そもそもサブセレクトって?

[SQL] 7. サブクエリ 1 | TECHSCORE(テックスコア)

入れ子にして内側のクエリが生成した値を外側のクエリが評価する。らしい。

イマイチピンとこないから実際に違いを観る

条件
  • User.firstが999人のユーザをフォロー
  • ひとりのユーザにつき50件のMicropost(投稿)を保持
# びふぉー
def feed
    Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
end

# ちょい冗長だけど勉強のためにSQL文も記録しとく。
 User Load (2.9ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (7.1ms)  SELECT "users".id FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ?  [["follower_id", 1]]
  Micropost Load (888.7ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id IN (3,4,5,6,7,8,...

2.9 + 7.1 + 888.7 = 898.7ms
# あふたー
def feed
  following_ids = "SELECT followed_id FROM relationships
                     WHERE follower_id = :user_id"
  Micropost.where("user_id IN (#{following_ids})
                     OR user_id = :user_id", user_id: id)
end

[1] pry(main)> User.first.feed
  User Load (2.7ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Micropost Load (627.2ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id IN (SELECT followed_id FROM relationships WHERE follower_id = 1) OR user_id = 1) ORDER BY "microposts"."created_at" DESC

2.7 + 627.2= 629.9ms

ひえーこれだけでも3割程度違っちゃうのかー うーんすごい。 SQL周りの基本的なところ抑えないとだ・・・。