Laravelでは、リレーション先の件数や合計値を効率よく取得するために withCount や withSum といった便利な集計メソッドが用意されています。
この記事では、withCount・withSumの使い方とSQLパフォーマンスに配慮した実装方法、注意すべき落とし穴までを詳しく解説します。
withCountとは?
withCount
は、リレーション先の件数を取得し、モデルのプロパティとして扱えるようにするメソッドです。
基本的な使い方
たとえば、User
モデルがposts
リレーションを持っているとき、ユーザーごとの投稿数を取得するには以下のようにします:
User::withCount('posts')->get();
この場合、取得したユーザーモデルには posts_count
プロパティが追加されます。
$user->posts_count;
複数リレーションの件数をまとめて取得
User::withCount(['posts', 'comments'])->get();
このようにすれば、posts_count
と comments_count
が同時に取得できます。
withSumとは?
withSum
は、リレーション先の合計値(sum)を集計して取得するためのメソッドです。主に数値フィールドの合計が必要なケースで活用します。
例:注文ごとの合計金額を取得
User::withSum('orders', 'total_price')->get();
この場合、$user->orders_sum_total_price
の形で合計金額が取得できます。
複数項目の合計を取得したいとき
User::withSum([
'orders as total_items_sum' => function ($query) {
$query->selectRaw('SUM(quantity)');
},
])->get();
as
句を使えば、任意のプロパティ名で取得することも可能です。
条件付きのカウント・集計
リレーションに条件を加えたい場合は、配列形式でクロージャを指定できます。
User::withCount([
'posts' => function ($query) {
$query->where('status', 'published');
}
])->get();
このようにすると、「公開済みの記事数」のみがカウントされます。
パフォーマンス面のメリット
withCount・withSumは、N+1問題を回避しつつ、サブクエリで集計を行うため非常に効率的です。
select users.*,
(select count(*) from posts where posts.user_id = users.id) as posts_count
from users;
このようなSQLが生成され、集約関数が1クエリで実行される点がポイントです。
注意点:with()との併用との違い
with('posts')
でリレーション全体を読み込むのとは異なり、withCount('posts')
は件数のみを取得します。
両方が必要な場合は、以下のように併用できます:
User::with('posts')->withCount('posts')->get();
ただし、同時に大量のリレーションを読み込むとメモリ使用量が増えるため、必要に応じて最適化を意識しましょう。
withAvg・withMax・withMinも使える
Laravel 8以降では withAvg
・withMax
・withMin
も使えます。
User::withAvg('orders', 'score')->get();
User::withMax('orders', 'total_price')->get();
User::withMin('orders', 'delivery_days')->get();
統計値を1クエリで取得したい場面では積極的に活用できます。
まとめ
withCount / withSum / withAvg などの集計系メソッドは、リレーションの情報を軽量かつ高速に取得できる強力な手段です。
withCount
で件数を取得(posts_count)withSum
で合計値を取得(orders_sum_total_price)- 条件付き集計や as句で柔軟にプロパティ名を変更可能
ビュー側でループして集計するのではなく、クエリレベルで集計処理を完結させることで、処理の高速化と保守性の向上が実現できます。
一覧表示・管理画面・APIレスポンスなど、集計が求められる場面で積極的に活用していきましょう。