withCount() 是 Laravel 专为查询关联数量设计的方法,只执行 COUNT(*) 而不加载关联数据,比 has() + 手动 COUNT 更简洁、比 with() + count() 更高效;支持嵌套、条件计数、自定义字段名及 distinct 去重统计,但需注意避免重复子查询、慎用 distinct 和确保索引优化。

用 withCount() 实现关联存在性预加载计数
直接上结论:Laravel 的 withCount() 就是专为「查模型同时拿到关联数量」设计的,它不加载关联数据本身,只查 COUNT(*),比 has() + 手动 SELECT COUNT 更简洁,也比 with() + count() 在循环里调用更高效。
典型场景:列出所有用户,并显示每人有多少条订单;或者筛选「至少有 1 条评论的文章」——这时你要的不是评论内容,只是「有没有」或「有几个」。
-
withCount('orders')会在查询结果的模型上挂一个orders_count属性(默认命名规则) - 支持嵌套关联:
withCount(['posts.comments']),会生成posts_comments_count - 如果只需要判断「是否存在」,用
withCount(['comments' => fn ($q) => $q->whereNotNull('id')])配合> 0判断即可,避免全表 COUNT - 注意:
withCount()是 Eloquent 查询构造器方法,必须在get()或paginate()前调用,不能链在first()后面再加
has() 和 withCount() 混用时的常见错误
很多人想「先筛出有订单的用户,再统计订单数」,于是写成 User::has('orders')->withCount('orders')->get()。这看起来合理,但实际会触发两次子查询:一次 EXISTS,一次 COUNT,性能没优化反而更差。
- 真正要筛「有订单」且「带数量」,直接
withCount('orders')就够了,后续用$user->orders_count > 0过滤即可 - 若必须在 SQL 层过滤(比如分页时不想让无订单用户占位),改用
whereHas('orders')+withCount('orders'),它只产生一次 JOIN + COUNT,比has()更可控 -
has('orders', '>=', 5)这类带操作符的用法,底层仍是EXISTS+ 子查询,无法复用withCount的 COUNT 结果,别指望它和withCount共享计算
自定义计数字段名与条件计数
默认的 xxx_count 字段名不够语义化?或者只想统计「未删除的评论」?withCount() 支持闭包和别名,但写法容易错。
- 改字段名:用数组键值对,
withCount(['comments as active_comments_count' => fn ($q) => $q->where('status', 'active')]) - 多个条件:闭包里可链式调用
where、whereIn,但不能用select()或groupBy(),否则报错SQLSTATE[42000]: Syntax error or access violation - 关联本身带约束时(如
comments()方法里已写了where('deleted_at', null)),withCount('comments')会自动继承该约束,不用重复写 - 慎用
distinct:如需去重计数(例如统计「发过订单的不同商品种类数」),得手动写withCount(['orders as unique_product_count' => fn ($q) => $q->selectRaw('COUNT(DISTINCT product_id)')]),此时必须用selectRaw替换默认COUNT(*)
与 loadCount() 的区别和误用时机
loadCount() 是懒加载版本,只对已有模型集合生效,常被当成「补查数量」的救急方案,但容易引发 N+1。
- 正确用法:查出一批用户后,再统一补查他们的订单数 ——
$users->loadCount('orders'),它会合并成一条 IN 查询 - 错误用法:在 foreach 里对每个模型单独调
$user->loadCount('orders'),这就退化成 N 次查询 - 和
withCount()不兼容:已用withCount()加载过的模型,再调loadCount()会覆盖原字段,但不会重新查库(Eloquent 缓存了结果) - 复杂关联下优先选
withCount():比如分页列表页,必须用withCount();而详情页里临时想补一个统计,才考虑loadCount()
最易忽略的一点:当关联表有大量数据且没建好索引时,withCount() 的 COUNT 查询可能变慢,尤其嵌套多层或加了复杂 where。上线前记得看 DB::enableQueryLog() 抓出实际 SQL,确认是否命中索引。











