【Laravel】withCount・withSumの使い方と注意点|集計クエリを高速化するテクニック

【Laravel】withCount・withSumの使い方と注意点|集計クエリを高速化するテクニック Laravel

Laravelでは、リレーション先の件数や合計値を効率よく取得するために withCountwithSum といった便利な集計メソッドが用意されています。

この記事では、withCount・withSumの使い方とSQLパフォーマンスに配慮した実装方法、注意すべき落とし穴までを詳しく解説します。

スポンサーリンク

withCountとは?

withCount は、リレーション先の件数を取得し、モデルのプロパティとして扱えるようにするメソッドです。

基本的な使い方

たとえば、Userモデルがpostsリレーションを持っているとき、ユーザーごとの投稿数を取得するには以下のようにします:

User::withCount('posts')->get();

この場合、取得したユーザーモデルには posts_count プロパティが追加されます。

$user->posts_count;

複数リレーションの件数をまとめて取得

User::withCount(['posts', 'comments'])->get();

このようにすれば、posts_countcomments_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以降では withAvgwithMaxwithMin も使えます。

User::withAvg('orders', 'score')->get();
User::withMax('orders', 'total_price')->get();
User::withMin('orders', 'delivery_days')->get();

統計値を1クエリで取得したい場面では積極的に活用できます。

よくある質問(FAQ)

Q. withCountとwithSumをwithと同時に使えますか?
A. $posts = Post::with('tags')->withCount('comments')->withSum('likes', 'count')->get()のようにメソッドチェーンで組み合わせて使えます。
Q. withCountで条件(WHERE)を付けるには?
A. withCount(['comments as active_comments_count' => function($q){ $q->where('is_active', true); }])のようにクロージャを渡せます。
Q. N+1問題を防ぐためにwithCountは有効ですか?
A. はい、withCountは集計クエリをEager Loadingで1回のJOINにまとめるため、N+1問題を防げます。
Q. withSumがnullを返す場合はどう対処しますか?
A. 関連レコードがない場合、withSumはnullを返します。$model->column_sum ?? 0のようにnull合体演算子を使います。
Q. withCountとselectに同名の列がある場合は?
A. withCountは自動的に{リレーション名}_countという名前で列を追加します。withCount(['comments as custom_count'])のようにエイリアスを指定できます。

関連記事

まとめ

withCount / withSum / withAvg などの集計系メソッドは、リレーションの情報を軽量かつ高速に取得できる強力な手段です。

  • withCount で件数を取得(posts_count)
  • withSum で合計値を取得(orders_sum_total_price)
  • 条件付き集計や as句で柔軟にプロパティ名を変更可能

ビュー側でループして集計するのではなく、クエリレベルで集計処理を完結させることで、処理の高速化と保守性の向上が実現できます。

一覧表示・管理画面・APIレスポンスなど、集計が求められる場面で積極的に活用していきましょう。