推荐用 fromsub 或 selectsub 配合原始日期分组实现每日汇总统计,fromsub 适合关联多表聚合,selectsub 适合主查询嵌入统计字段,结合作用域可避免 n+1。

在 Laravel 12 中,使用子查询统计每日汇总信息(如每日订单数、销售额等)推荐用 fromSub 或 selectSub 配合原始日期分组,避免 N+1 或内存溢出。核心是把「按天聚合的子查询」作为临时表参与主查询,既保持 Eloquent 风格,又兼容复杂统计逻辑。
用 fromSub 构建每日聚合子查询
适合需要关联其他表(如用户、商品)或做多维度筛选的场景。例如统计「每天的订单总数和总金额」:
$dailyStats = DB::table(DB::raw("(SELECT
DATE(created_at) as date,
COUNT(*) as order_count,
SUM(total_price) as total_amount
FROM orders
WHERE created_at >= ?
GROUP BY DATE(created_at)
) as daily_summary"))->select('date', 'order_count', 'total_amount')
->orderBy('date', 'desc')
->get();
注意:DATE(created_at) 在 MySQL 中提取日期部分;Laravel 12 的 DB::table(DB::raw(...)) 是标准写法,? 占位符自动绑定参数,防 SQL 注入。
用 selectSub 在主查询中嵌入统计字段
适合在查用户、店铺等主表时,顺带带上该实体「当天/当月的统计数据」。例如查每个店铺及其「昨日订单数」:
$stores = Store::select('id', 'name')
->selectSub(function ($sub) {
$sub->from('orders')
->whereColumn('orders.store_id', 'stores.id')
->whereDate('created_at', now()->subDay())
->selectRaw('COUNT(*)');
}, 'yesterday_orders')
->get();
关键点:selectSub 第二个参数是别名;whereColumn 实现主子表关联;whereDate 精确到天,比 whereBetween 更简洁安全。
结合 Eloquent 关系 + 子查询预加载(避免 N+1)
如果已有模型关系(如 Store → hasMany(Order)),但想高效加载「每日汇总」而非全部订单,可自定义访问器配合子查询作用域:
- 在
Store模型中添加作用域:
public function scopeWithDailyOrders($query, $days = 7) {
return $query->withCount(['orders' => function ($q) use ($days) {
$q->whereBetween('created_at', [
now()->subDays($days)->startOfDay(),
now()->endOfDay()
]);
}]);
}
- 调用:
Store::withDailyOrders(30)->get(),生成单条 SQL 含COUNT子查询,返回orders_count属性; - 若需明细(非仅计数),改用
selectSub+leftJoin关联聚合结果表。
时间处理与数据库兼容性提醒
Laravel 12 默认适配 MySQL 8.0+、PostgreSQL、SQLite,但日期函数有差异:
- MySQL:用
DATE(created_at)或DATE_FORMAT(created_at, '%Y-%m-%d'); - PostgreSQL:用
created_at::date或DATE_TRUNC('day', created_at); - 为跨库兼容,建议封装为查询构造器宏或使用
Carbon::parse($row->created_at)->format('Y-m-d')在 PHP 层归并(仅数据量小时适用)。
生产环境务必对 created_at 字段加索引,尤其在 WHERE 和 GROUP BY 中高频使用时。











