ActiveRecord だけでは物足りないこともあります。複数の JOIN を含む複雑なクエリが必要な場合や、データベースのパフォーマンスを限界まで引き出したい場合です。そんなときに役立つのが生の SQL です。
ここでは、Rails で生の SQL を実行するために知っておくべきことを、基本からよくある落とし穴の回避まで、すべて解説します。
生の SQL が本当に必要になるとき
ほとんどの場合、ActiveRecord で十分です。しかし、実際の現場で生の SQL が適しているのは次のようなケースです。
- 複雑な集計 - ウィンドウ関数、CTE、複雑な GROUP BY 操作が必要な場合
- パフォーマンスが重要なクエリ - ActiveRecord が生成する SQL が非効率な場合
- データベース固有の機能 - PostgreSQL の JSONB 演算子や MySQL の FULLTEXT 検索などを使う場合
- 一括操作 - ActiveRecord を個別に呼び出すと遅くなる大量更新
では、実践的な内容に入りましょう。
実例:ユーザー分析
ユーザー分析機能を作っていて、過去7日間に登録したユーザーとその投稿数を取得する必要があるとします。その場合は次のようになります。
生の SQL を実行する3つの方法
Rails には生の SQL を実行する方法がいくつかあります。主なものは次のとおりです。
1. connection.execute - 生の結果を取得する
データベースの結果をそのまま取得したい場合に使います。
sql = <<~SQL
SELECT
u.id, u.name, u.email,
COUNT(p.id) as post_count
FROM users u
LEFT JOIN posts p ON p.user_id = u.id
WHERE u.created_at >= NOW() - INTERVAL '7 days'
GROUP BY u.id, u.name, u.email
ORDER BY post_count DESC
SQL
result = ActiveRecord::Base.connection.execute(sql)
result.each do |row|
puts "#{row['name']}: #{row['post_count']} posts"
end
2. find_by_sql - モデルオブジェクトを取得する
実際の ActiveRecord オブジェクトを返してほしい場合に使います。
users = User.find_by_sql([
"SELECT * FROM users WHERE created_at >= ? AND status = ?",
7.days.ago, 'active'
])
users.each { |user| puts user.email }
3. Arel と pluck - ハイブリッドなアプローチ
シンプルなケースでは、ActiveRecord と組み合わせて生の SQL を部分的に使うこともできます。
User.where("created_at >= ?", 7.days.ago)
.pluck("name, email, DATE(created_at) as signup_date")
ハッキングを防ぐ:SQL インジェクション対策
これは非常に重要です。ユーザー入力を SQL 文字列に直接埋め込むことは絶対にしないでください。間違った例はこちらです。
# DON'T DO THIS - vulnerable to SQL injection
email = params[:email]
User.find_by_sql("SELECT * FROM users WHERE email = '#{email}'")
代わりに、パラメータ化クエリを使いましょう。
# Safe approach with parameters
email = params[:email]
User.find_by_sql(["SELECT * FROM users WHERE email = ?", email])
# Or use connection.quote for raw execute
email = params[:email]
sanitized_email = ActiveRecord::Base.connection.quote(email)
result = ActiveRecord::Base.connection.execute("SELECT * FROM users WHERE email = #{sanitized_email}")
パラメータは自動的にエスケープされるため安全です。必ず使うようにしましょう。
パフォーマンスのヒント
生の SQL を使う目的はパフォーマンスであることが多いので、プロのヒントをいくつか紹介します。
Beekeeper Studio でクエリを分析する - Rails アプリに組み込む前に SQL をテスト・最適化するのに最適です。
フィルタや JOIN に使うカラムにはインデックスを追加しましょう。
# In a migration
add_index :users, :created_at
add_index :posts, [:user_id, :status]
EXPLAIN を使ってクエリのパフォーマンスを把握しましょう。
ActiveRecord::Base.connection.execute("EXPLAIN ANALYZE #{your_sql}")
複雑で再利用するクエリは、生の SQL をあちこちで繰り返す代わりにデータベースビューの利用を検討しましょう。
まとめ
Rails における生の SQL は、鋭いナイフのようなものです。必要なときには強力ですが、取り扱いには注意が必要です。ActiveRecord では対応できない複雑なクエリ、パフォーマンス最適化、データベース固有の機能に使いましょう。
覚えておきたいポイント:
- SQL インジェクションを防ぐため、必ずパラメータ化クエリを使う
- まずは Beekeeper Studio などのツールでクエリをテストする
- ActiveRecord を完全に捨てず、生の SQL は戦略的に使う
関連資料
- Rails ガイド:Active Record クエリインターフェイス
- PostgreSQL パフォーマンスのヒント
- MySQL クエリ最適化
- SQL 開発に Beekeeper Studio を試す - SQL クエリの作成とテストに最適な方法です
データベース管理や SQL の最適化について疑問がありますか?Beekeeper Studio なら、デプロイ前にデータを簡単に探索し、クエリを磨き上げることができます。
Beekeeper Studioは無料のオープンソースのデータベースGUIです
これまで使った中で最高のSQLクエリ&エディタツールです。データベースの管理に必要なものがすべて揃っています。 - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studioは高速で直感的、そして使いやすいツールです。多数のデータベースに対応し、Windows、Mac、Linuxで快適に動作します。
Beekeeper Studio 利用者の声
「Beekeeper Studio で以前の SQL ワークフローが完全に置き換わりました。高速で直感的で、データベース作業がまた楽しくなりました。」
「多くのデータベース GUI を試してきましたが、Beekeeper は機能とシンプルさのバランスが完璧です。とにかくちゃんと動きます。」