Laravel 的 withCount() 不支持跨两级关联(如 'posts.comments'),因只识别一级关联方法,错误写法会抛出 RelationNotFoundException 或返回 0;正确方式需用 join() 或子查询实现嵌套聚合,并注意 DISTINCT、GROUP BY、软删除过滤及 NULL 处理。
嵌套关联(比如 User → Posts → Comments)不能直接用或跨两级聚合,必须显式或子查询;否则会报错、漏数据或返回空。
为什么 withCount('posts.comments') 会报错或无效
因为
只支持一级关联。传入字符串路径如
会被 Laravel 当作一个叫
的关联方法名去查找,而模型里根本不存在这个方法,最终抛出
或静默返回 0。
错误写法:
→ 找不到关联,
始终为 0
正确思路:要么拆成两级预加载(
),但这只返回计数的计数,不是用户维度的总评论数
真正要的是「每个用户的全部评论总数」,就得跳出
系列,进 SQL 层
用 join() 实现 User → Posts → Comments 的总评论数
核心是手动把三张表连起来,再用
或
聚合。注意别漏掉
,否则一用户多文章会导致评论重复计数。
关键点:在
里用
,而不是
,避免因左连接引入的 NULL 行干扰
必须加
,否则 MySQL 严格模式下报错
字段冲突风险:如果
和
都有
,
会失败,得显式列出字段或加别名
用子查询实现更清晰的嵌套聚合(比如每个用户平均每篇文章的评论数)
当聚合逻辑变复杂(如 AVG、ROUND、条件过滤),子查询比长
更易读、更可控,也避免
字段爆炸。
Laravel 13.2.0
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
下载
外层查用户,内层子查询算该用户的评论统计,用
关联
子查询里可自由加
过滤评论状态(如只算
的)
注意:子查询返回单值时,Laravel 允许直接塞进
;若返回多行,要用
包裹
容易被忽略的 NULL 和软删除陷阱
嵌套聚合最常翻车的地方不在语法,而在数据状态——尤其是软删除和空关联。
或
表启用了软删除?那
会把已删除的记录也拉进来,必须在
里过滤:
用户没发过文章?用
后
是 0,但用
就直接丢掉该用户——按业务选
想排除无评论的用户,又不想丢数据?改用
,但得确保
已存在
嵌套越深,SQL 层的控制权就越重要;别指望 Eloquent 自动帮你跨级聚合,该写
就写,该拆子查询就拆。
withCount()withSum()join()withCount()'posts.comments'posts.commentsRelationNotFoundExceptionUser::withCount('posts.comments')->get()posts_comments_countwithCount('posts')->withCount(['posts' => fn($q) => $q->withCount('comments')])with*count()sum()distinctcount()count('comments.id')count(*)groupBy('users.id')Expression #1 of SELECT list is not in GROUP BY clauseuserscommentsidselect('users.*')$users = DB::table('users')
->leftJoin('posts', 'users.id', '=', 'posts.user_id')
->leftJoin('comments', 'posts.id', '=', 'comments.post_id')
->select('users.id', 'users.name', DB::raw('COUNT(DISTINCT comments.id) as total_comments'))
->groupBy('users.id', 'users.name')
->get();
join()GROUP BYwhere('users.id', 'sub.user_id')WHEREapproved = trueselect()DB::raw("(SELECT ...) AS field")$users = User::select('id', 'name', DB::raw('(
SELECT ROUND(AVG(c_per_post), 2)
FROM (
SELECT COUNT(comments.id) as c_per_post
FROM posts
LEFT JOIN comments ON posts.id = comments.post_id
WHERE posts.user_id = users.id
GROUP BY posts.id
) AS per_post_counts
) as avg_comments_per_post'))
->get();
postscommentsleftJoin()on()->on('comments.post_id', '=', 'posts.id')->on('comments.deleted_at', null)leftJoin()count('comments.id')innerJoin()havingRaw('COUNT(comments.id) > 0')GROUP BYDB::raw()