手帳

  • 2025-04-01
  • IT系
DBクエリビルダーとEloquent ORMの構造比較 DBクエリビルダー Eloquent ORM DB::table(‘users’) ->where(‘active’, 1) ->orderBy(‘name’) ->get() SELECT * FROM users WHERE active = 1 ORDER BY name User Model extends Model User::where(‘active’, 1) ->orderBy(‘name’) ->get() 返り値: User Modelの コレクション いずれも同じSQLが生成される リレーションシップの扱い方の比較 users id name email posts id user_id title hasMany DBクエリビルダー // JOINを手動で書く必要がある DB::table(‘users’) ->join(‘posts’, ‘users.id’, ‘=’, ‘posts.user_id’) ->where(‘users.id’, 1) ->get(); Eloquent ORM // リレーションを使って簡潔に書ける // モデルの定義 (1度だけ) public function posts() { return $this->hasMany(Post::class); } User::find(1)->posts;

1. DBクエリビルダーとEloquent ORMの基本的な違い

DBクエリビルダー

  • SQLに近い形でクエリを構築するためのインターフェース
  • テーブル単位の操作が基本
  • DB::table()から始まる構文
  • モデルやリレーションシップの概念なし
// 基本的な使い方
$users = DB::table('users')
->where('active', 1)
->orderBy('name')
->get();

Eloquent ORM

  • データベーステーブルをPHPオブジェクトとしてモデル化
  • モデルクラスを通じてデータにアクセス
  • リレーションシップの定義と操作が簡単
  • モデル名から始まる構文(例:User::where(...)
// 基本的な使い方
$users = User::where('active', 1)
->orderBy('name')
->get();

2. 主な機能比較

機能 DBクエリビルダー Eloquent ORM
テーブル参照 DB::table('users') User::all()
レコード取得 ->first(), ->get() ->first(), ->get()
条件指定 ->where('column', 'value') ->where('column', 'value')
リレーション 手動JOINが必要 ->with('relation')
新規作成 ->insert([...]) $model->save() または ::create([...])
更新 ->update([...]) $model->save()
削除 ->delete() $model->delete()

3. リレーションシップの扱い方の違い

DBクエリビルダーでは、リレーションシップを表現するために手動でJOIN句を書く必要があります:

// JOINを手動で書く必要がある
$usersWithPosts = DB::table('users')
->join('posts', 'users.id', '=', 'posts.user_id')
->where('users.id', 1)
->get();

対して、Eloquent ORMではモデルにリレーションシップを定義しておくことで、より直感的に操作できます:

// モデルの定義 (一度だけ)
class User extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}

// 使用時
$user = User::find(1);
$posts = $user->posts; // リレーションシップ経由でアクセス

4. マニアックな利用例

DBクエリビルダーの高度な使い方

// 複雑なJOINと集計
$results = DB::table('orders')
->select(DB::raw('customers.name, SUM(order_items.price) as total'))
->join('customers', 'orders.customer_id', '=', 'customers.id')
->join('order_items', 'orders.id', '=', 'order_items.order_id')
->whereYear('orders.created_at', '=', date('Y'))
->groupBy('customers.name')
->having('total', '>', 1000)
->orderBy('total', 'desc')
->get();

// サブクエリの使用
$highValueUsers = DB::table('users')
->whereIn('id', function($query) {
$query->select('user_id')
->from('orders')
->groupBy('user_id')
->havingRaw('SUM(amount) > ?', [10000]);
})
->get();

Eloquent ORMの高度な使い方

// リレーションシップを使った複雑なクエリ
$users = User::with(['posts' => function($query) {
$query->where('published', true)
->orderBy('created_at', 'desc');
}])
->has('posts', '>=', 3)
->whereHas('profile', function($query) {
$query->where('verified', true);
})
->get();

// モデルイベントとミューテータの活用
class User extends Model
{
// 属性の自動変換
public function setPasswordAttribute($value)
{
$this->attributes['password'] = bcrypt($value);
}

// イベントハンドリング
protected static function boot()
{
parent::boot();

static::created(function($user) {
$user->profile()->create([]);
event(new UserCreated($user));
});
}
}

5. パフォーマンス比較

DBクエリビルダー

  • 一般的に処理が軽い
  • 必要な部分だけをSQLに変換
  • 大量データ処理に適している

Eloquent ORM

  • モデルのロードとハイドレーションでオーバーヘッドあり
  • Eager LoadingでN+1問題を回避する必要がある
  • メモリ使用量が多い場合がある
// N+1問題の例と解決策
// 問題のあるコード
$users = User::all(); // 1回のクエリ
foreach ($users as $user) {
$user->posts; // 各ユーザーごとに追加クエリ実行
}

// 解決策:Eager Loading
$users = User::with('posts')->get(); // 2回のクエリだけ
Eloquent N+1問題と解決策 N+1問題 解決策: Eager Loading // 問題のあるコード $users = User::all(); foreach ($users as $user) { $user->posts; // 追加クエリ } 実行されるクエリ: 1. SELECT * FROM users 2. SELECT * FROM posts WHERE user_id = ? [N回繰り返し] // 解決策: Eager Loading $users = User::with(‘posts’) ->get(); 実行されるクエリ: 1. SELECT * FROM users 2. SELECT * FROM posts WHERE user_id IN (…) メリット: 常に2回のクエリのみ!

6. N+1問題とEager Loading

Eloquent ORMを使用する際の大きな落とし穴の一つが「N+1問題」です。これは、コレクションの各アイテムに対して個別にクエリが発行されてしまう非効率な状態を指します。

N+1問題の例

// 問題のあるコード
$users = User::all(); // これで1回クエリが発行される
foreach ($users as $user) {
$user->posts; // ユーザーごとに別のクエリが発行される
}

ユーザーが10人いれば、合計11回(ユーザー取得の1回 + 各ユーザーの投稿取得10回)のクエリが実行されてしまいます。

Eager Loadingによる解決

Eager Loading(イーガーローディング)は、関連するモデルを効率的に先読みする仕組みです。

// Eager Loadingの例
$users = User::with('posts')->get(); // クエリは2回だけ

これは以下の2つのSQLクエリのみを実行します:

  1. SELECT * FROM users
  2. SELECT * FROM posts WHERE user_id IN (1, 2, 3, ...)

Eager Loadingの重要な点

  • デフォルトは遅延ローディング: Eloquentではモデル間のリレーションシップを定義していても(hasMany()など)、クエリ実行時にwith()メソッドを使ってEager Loadingを指定しない限り、デフォルトでは遅延ローディングが行われます。
  • 明示的な指定が必要: パフォーマンスを向上させるには、使用するリレーションシップをwith()メソッドで明示的に指定することが重要です。

高度なEager Loading

// 複数のリレーションシップ
$users = User::with(['posts', 'profile', 'roles'])->get();

// ネストされたリレーションシップ
$articles = Article::with('comments.author')->get();

// 条件付きEager Loading
$users = User::with(['posts' => function ($query) {
$query->where('published', true)
->orderBy('created_at', 'desc');
}])->get();

7. 使い分けの目安

  1. DBクエリビルダーが適している場合:
    • 複雑な集計やレポート生成
    • 大量データの処理
    • 複雑なJOINやサブクエリが必要
    • パフォーマンスが重要な処理
  2. Eloquent ORMが適している場合:
    • アプリケーションの基本的なCRUD操作
    • リレーションシップを多用する場合
    • モデルイベントやミューテータが必要
    • コードの可読性を優先する場合

まとめ

DBクエリビルダーはSQLに近い形で柔軟なクエリを構築するためのツールであり、Eloquent ORMはデータベース操作をオブジェクト指向的に行うための高レベルな抽象化層です。

Eloquent ORMを使用する際は、N+1問題に注意し、適切にEager Loadingを活用することが重要です。モデルでリレーションシップを定義しただけでは自動的にEager Loadingは行われず、with()メソッドでの明示的な指定が必要です。

プロジェクトでは両方を状況に応じて使い分けることで、コードの可読性とパフォーマンスのバランスを取ることができます。Laravelではどちらも簡単に切り替えて使用できるため、それぞれのメリットを活かした実装が可能です。