動かざることバグの如し

近づきたいよ 君の理想に

find_by_sql でもN+1問題を回避したい

環境

やりたいこと

以下のようなテーブルがある

  • usersテーブル

    • id
    • name
    • age
  • postsテーブル

    • id
    • title
    • user_id

なんらか理由があって find_by_sql で実装するとする

sql = <<~SQL.squish
    SELECT posts.title, users.name
    FROM posts JOIN users ON posts.user_id = users.id
SQL

posts = Post.find_by_sql(sql)

がこれだとN+1問題が発生する。具体的には、各postに対してユーザー情報を取得するために、ユーザーテーブルに対するクエリがpostの数だけ発行される。これはデータベースへのアクセス回数が増え、パフォーマンスに影響を及ぼす。

解決策

ActiveRecord::Associations::Preloaderを使う。これはRailsが提供する機能で、指定した関連付けを事前にロードすることでN+1問題を解決する。

posts = Post.find_by_sql(sql)
ActiveRecord::Associations::Preloader.new(records: posts, associations: :user).call

上記のコードでは、find_by_sqlで取得したpostsに対して、userの関連付けを事前にロードしている。これにより、各postのuserを参照する際に新たなクエリが発行されることなく、結果を取得できる。

posts = Post.find_by_sql(sql)
ActiveRecord::Associations::Preloader.new.preload(posts, :user)

参考リンク