
本文详解如何在 laravel eloquent 查询中,通过 addselect() 或 selectraw() 一次性为每本书籍记录附加整个 ratings 表的总行数(如 12 条),避免 n+1 查询或额外数据库往返,同时兼容 withcount() 和关系预加载。
本文详解如何在 laravel eloquent 查询中,通过 addselect() 或 selectraw() 一次性为每本书籍记录附加整个 ratings 表的总行数(如 12 条),避免 n+1 查询或额外数据库往返,同时兼容 withcount() 和关系预加载。
在构建书籍列表 API 时,常需同时返回两个维度的计数:
✅ 每本书各自的评分数量(rating_count,由 withCount('rating') 提供);
✅ 整个评分表的全局总数(如全部 12 条评分记录),并希望它作为同一条 SQL 查询结果中的固定字段(如 total_ratings)出现在每一行中。
直接使用 Rating::count() 并传入 addSelect(['total_books' => Rating::count()]) 是错误的——因为 addSelect() 接收的是 SQL 子查询表达式,而非 PHP 计算值。你传入的整数 12 会被当作字面量列名处理,导致类似 Unknown column '105' 的 SQL 错误(Laravel 将数字误解析为列别名)。
✅ 正确做法:使用子查询或参数化 selectRaw
方案一:addSelect() + 子查询(推荐,纯 SQL,高效)
$books = Books::query()
->withCount('rating') // 添加每本书的 rating_count
->with([
'author:id,user_id',
'author.user:id,name,email'
])
->addSelect([
'total_ratings' => Rating::selectRaw('count(*)')
])
->get();
生成的 SQL 类似:
SELECT `books`.*, (SELECT COUNT(*) FROM `ratings` WHERE `books`.`id` = `ratings`.`book_id`) AS `rating_count`, (SELECT COUNT(*) FROM `ratings`) AS `total_ratings` FROM `books`;
✅ 优势:一次查询完成,total_ratings 在每行中值相同(如 12),无额外查询开销;
⚠️ 注意:Rating::selectRaw('count(*)') 是一个 Eloquent 子查询对象,Laravel 会自动将其转为 (SELECT count(*) FROM ratings) 子句。
方案二:selectRaw() + 预计算值(适合静态总数)
若总数极少变动,且你已知其值(如 Rating::count() 调用成本可接受),可用参数化方式:
$totalRatings = Rating::count(); // 执行一次,返回整数 12
$books = Books::query()
->withCount('rating')
->with(['author:id,user_id', 'author.user:id,name,email'])
->selectRaw('*, ? as total_ratings', [$totalRatings])
->get();
✅ 优势:逻辑清晰,避免嵌套子查询;
⚠️ 注意:此方式将 total_ratings 作为常量注入,不参与 SQL 执行计划优化,但对小数据量完全可靠。
方案三:带 whereIn 的子查询(兼容业务逻辑,非必需)
原文中 Rating::whereIn('book_id', Books::select('id')) 实际冗余(因外键约束保证 book_id 必属有效书籍 ID),但若需显式限定,可写为:
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
$books = Books::query()
->withCount('rating')
->with(['author:id,user_id', 'author.user:id,name,email'])
->addSelect([
'total_ratings' => Rating::selectRaw('count(*)')
->whereIn('book_id', Books::select('id'))
])
->get();
生成子查询:(SELECT count(*) FROM ratings WHERE book_id IN (SELECT id FROM books)) —— 结果与 Rating::count() 完全一致,仅语义更明确。
? 关键总结与最佳实践
- 命名建议:字段应命名为 total_ratings 而非 total_books,因其统计的是 ratings 表总行数,与书籍数量无关;
- 性能对比:方案一(子查询)和方案二(预计算)均优于在循环中调用 Rating::count(),后者会导致 N 次重复查询;
- 模型关联修正:检查 Books 模型中 rating() 关系定义,确保外键名准确(当前为 'book_id',与表结构一致,正确);
- 扩展性提示:如需统计多个全局指标(如总用户数、总作者数),可链式追加多个 addSelect(),保持单次查询优势。
最终响应示例(JSON 片段):
[
{
"id": 1,
"title": "vel",
"rating_count": 5,
"total_ratings": 12,
"author": { /* ... */ }
},
{
"id": 2,
"title": "atque",
"rating_count": 7,
"total_ratings": 12,
"author": { /* ... */ }
}
]
所有书籍行共享同一 total_ratings: 12,满足“单次查询、全局统计、每行携带”的核心需求。










