应优先使用 query builder 的 selectraw() + groupbyraw() 实现按日聚合统计,避免原生 sql 拼接;必须用 date(created_at) 统一分组与排序,配合 limit(30) 由数据库执行,禁用 eloquent 模型以防 group by 错误。

在 Laravel 9 中,不建议直接拼接原生 SQL 字符串来查每日数据(如按日期分组统计),而应优先使用查询构建器(Query Builder)配合 DB::raw() 或 selectRaw() 等安全方法。这样既能保留原生 SQL 的灵活性(比如用 DATE()、DATE_TRUNC()),又能防止 SQL 注入、支持链式调用和数据库适配。
用 selectRaw + groupByRaw 实现按日聚合
这是最常用也最稳妥的方式,适用于 MySQL、SQLite 等主流数据库:
- 用
selectRaw('DATE(created_at) as day, COUNT(*) as count')提取日期并计数 - 必须搭配
groupByRaw('DATE(created_at)')—— 若只写groupBy('created_at'),会按完整时间戳分组,导致同一天多条记录被拆开 - 排序推荐用
orderByRaw('DATE(created_at) DESC'),确保按自然日降序,而非按原始时间戳(避免同日多条排序不稳定) - 限制最近 30 天,应让数据库执行
limit(30),而不是get()->take(30),否则性能差且逻辑错
兼容 PostgreSQL 的写法
若项目同时支持 MySQL 和 PostgreSQL(如部署在不同环境),可用 DB::raw() 统一处理日期截断:
- MySQL:用
DATE(created_at) - PostgreSQL:用
DATE_TRUNC('day', created_at) - Laravel 不自动转换函数,所以需根据 DB 配置动态切换,或统一用
DB::raw()写具体函数,例如:selectRaw("DATE_TRUNC('day', created_at) as day, COUNT(*) as count") - 注意:PostgreSQL 对 GROUP BY 要求更严格,字段必须出现在 SELECT 或被聚合,因此
selectRaw和groupByRaw必须一致
带动态条件的每日查询
实际业务中常需按状态、类型等筛选后再按日统计,这时可结合请求参数动态追加 where:
- 用
$request->filled('status')判断是否传了有效值,再调用$query->where('status', $request->status) - 日期范围可用
whereBetween('created_at', [$start, $end]),比手写whereRaw更清晰安全 - 若需复杂条件(如“当天创建且未完成”),可用
whereRaw('DATE(created_at) = CURDATE() AND status != ?', ['done']),记得用参数绑定防注入 - 所有 where 操作都返回查询实例,可无限链式调用,无需提前拼 SQL 字符串
为什么不用 Eloquent 模型做这类查询
虽然可以用 View::selectRaw(...)->groupByRaw(...)->get(),但不推荐:
- Eloquent 默认会添加
id、created_at等字段到 SELECT,干扰 GROUP BY,容易触发ONLY_FULL_GROUP_BY错误 - 模型自带的隐藏字段、强制排序、软删除等逻辑,在纯统计场景下是冗余甚至有害的
- 查询构建器(
DB::table('views'))语义更准确:面向表的聚合操作,不是面向模型的实体操作 - 性能上无差异,但可读性和稳定性更高











