テーブル構造
まず、以下のようなテーブル構造を例に説明します:
usersテーブル
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255),
age INTEGER,
premium BOOLEAN,
created_at TIMESTAMP
);
postsテーブル
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
title VARCHAR(255),
content TEXT,
likes_count INTEGER DEFAULT 0,
created_at TIMESTAMP
);
commentsテーブル
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
post_id INTEGER REFERENCES posts(id),
user_id INTEGER REFERENCES users(id),
content TEXT,
created_at TIMESTAMP
);
サンプルデータ
usersテーブルのデータ
INSERT INTO users (id, name, email, age, premium, created_at) VALUES
(1, '山田太郎', 'yamada@example.com', 25, true, '2024-01-01 10:00:00'),
(2, '佐藤花子', 'sato@example.com', 30, false, '2024-01-02 11:00:00'),
(3, '鈴木一郎', 'suzuki@example.com', 20, true, '2024-01-03 12:00:00'),
(4, '田中次郎', 'tanaka@example.com', 35, false, '2024-01-04 13:00:00');
postsテーブルのデータ
INSERT INTO posts (id, user_id, title, content, likes_count, created_at) VALUES
(1, 1, '初めての投稿', 'こんにちは!', 5, '2024-01-01 10:30:00'),
(2, 1, '2番目の投稿', 'よろしくお願いします', 10, '2024-01-02 10:30:00'),
(3, 2, '私の投稿', 'はじめまして!', 3, '2024-01-02 12:30:00'),
(4, 3, 'テスト投稿', 'テストです', 15, '2024-01-03 14:30:00');
commentsテーブルのデータ
INSERT INTO comments (id, post_id, user_id, content, created_at) VALUES
(1, 1, 2, 'いいね!', '2024-01-01 11:00:00'),
(2, 1, 3, '素晴らしい!', '2024-01-01 12:00:00'),
(3, 2, 4, '参考になりました', '2024-01-02 13:00:00'),
(4, 3, 1, 'ありがとう!', '2024-01-02 14:00:00');
クエリ例と実行結果
1. 基本的な検索
主キーでの検索
# ActiveRecord
user = User.find(1)
# 生成されるSQL
SELECT * FROM users WHERE id = 1;
# 結果
# {
# id: 1,
# name: "山田太郎",
# email: "yamada@example.com",
# age: 25,
# premium: true,
# created_at: "2024-01-01 10:00:00"
# }
条件での検索
# ActiveRecord
users = User.where(age: 20..30)
# 生成されるSQL
SELECT * FROM users WHERE age BETWEEN 20 AND 30;
# 結果
# [
# {
# id: 1,
# name: "山田太郎",
# age: 25,
# ...
# },
# {
# id: 3,
# name: "鈴木一郎",
# age: 20,
# ...
# }
# ]
2. 関連テーブルとの結合
ユーザーと投稿を結合
# ActiveRecord
posts_with_users = Post.joins(:user).select('posts.*, users.name as author_name')
# 生成されるSQL
SELECT posts.*, users.name as author_name
FROM posts
INNER JOIN users ON users.id = posts.user_id;
# 結果
# [
# {
# id: 1,
# title: "初めての投稿",
# content: "こんにちは!",
# author_name: "山田太郎",
# ...
# },
# ...
# ]
3. 集計とグループ化
ユーザーごとの投稿数
# ActiveRecord
post_counts = Post.group(:user_id).count
# 生成されるSQL
SELECT user_id, COUNT(*) as count
FROM posts
GROUP BY user_id;
# 結果
# {
# 1 => 2, # 山田太郎さんの投稿数
# 2 => 1, # 佐藤花子さんの投稿数
# 3 => 1 # 鈴木一郎さんの投稿数
# }
4. 複雑な条件
いいねが5件以上で、コメントがついている投稿を検索
# ActiveRecord
popular_posts = Post.joins(:comments)
.where('posts.likes_count >= ?', 5)
.distinct
# 生成されるSQL
SELECT DISTINCT posts.*
FROM posts
INNER JOIN comments ON comments.post_id = posts.id
WHERE posts.likes_count >= 5;
# 結果
# [
# {
# id: 1,
# title: "初めての投稿",
# likes_count: 5,
# ...
# },
# {
# id: 2,
# title: "2番目の投稿",
# likes_count: 10,
# ...
# }
# ]
5. Eagerロードの例
投稿とそのコメントを一度に取得
# ActiveRecord
posts = Post.includes(:comments).where(user_id: 1)
# 生成されるSQL
SELECT * FROM posts WHERE user_id = 1;
SELECT * FROM comments WHERE post_id IN (1, 2);
# 結果
# [
# {
# id: 1,
# title: "初めての投稿",
# comments: [
# {
# id: 1,
# content: "いいね!",
# ...
# },
# {
# id: 2,
# content: "素晴らしい!",
# ...
# }
# ]
# },
# {
# id: 2,
# title: "2番目の投稿",
# comments: [
# {
# id: 3,
# content: "参考になりました",
# ...
# }
# ]
# }
# ]
6. スコープの実践例
class Post < ApplicationRecord
# 人気の投稿を取得するスコープ
scope :popular, -> { where('likes_count >= ?', 5) }
# 特定の期間の投稿を取得するスコープ
scope :recent, -> { where('created_at >= ?', 1.week.ago) }
end
# ActiveRecord
popular_recent_posts = Post.popular.recent
# 生成されるSQL
SELECT * FROM posts
WHERE likes_count >= 5
AND created_at >= '2024-01-24 00:00:00';
# 結果
# [
# {
# id: 1,
# title: "初めての投稿",
# likes_count: 5,
# ...
# },
# {
# id: 4,
# title: "テスト投稿",
# likes_count: 15,
# ...
# }
# ]
パフォーマンス最適化のポイント
- インデックスの活用
# マイグレーションファイル
class AddIndexesToPosts < ActiveRecord::Migration[7.0]
def change
add_index :posts, :user_id # 外部キーには必ずインデックスを
add_index :posts, :likes_count # 検索条件によく使うカラムにインデックスを
add_index :posts, [:user_id, :created_at] # 複合インデックス
end
end
- N+1問題の回避
# 悪い例(N+1問題が発生)
posts = Post.all
posts.each { |post| puts post.user.name } # 各投稿ごとにSQLが実行される
# 良い例(Eagerロードを使用)
posts = Post.includes(:user)
posts.each { |post| puts post.user.name } # SQLは2回だけ実行される