Laravel队列任务中复用$monthlyInvoices查询结果异常是什么原因?
问题根本原因
核心是Laravel查询构造器为可变对象,你没有克隆初始的查询构造器实例,每次追加查询条件都会直接修改原$monthlyInvoices对象的查询规则,导致后续统计叠加了前面的过滤条件,出现逻辑冲突。
原代码错误逻辑拆解
你在循环内定义的$monthlyInvoices始终是同一个查询构造器实例,实际执行顺序如下:
- 初始查询条件:仅按年月过滤,匹配当月所有发票
- 计算
$total_sales:基于初始条件统计,结果正常 - 计算
$offline_sales:给原构造器追加where('type','offline')条件,此时原$monthlyInvoices已经自带线下类型过滤规则 - 计算
$online_sales:在已有type=offline的条件基础上,再追加where('type','online'),最终查询条件变成type既要等于offline又要等于online,自然没有匹配数据,返回结果为空。
这完全对应你观察到的现象:调换online和offline的统计顺序后,后执行的统计会叠加前一个的类型过滤条件,导致无结果;你重新定义$monthlyInvoices相当于重置了查询构造器的条件,所以功能恢复。
修复方案
推荐两种写法,第一种逻辑直观,第二种性能更优:
方案1:每次统计克隆基础查询构造器
foreach ($period as $date) { // 仅定义一次基础查询 $baseQuery = Invoice::whereRaw('YEAR(created_at) = ?', $date->format('Y')) ->whereRaw('MONTH(created_at) = ?', $date->format('m')); // 每次统计都克隆基础查询,避免修改原对象 $total_sales = (clone $baseQuery)->sum('net_price'); $offline_sales = (clone $baseQuery)->where('type', '=', 'offline')->sum('net_price'); $online_sales = (clone $baseQuery)->where('type', '=', 'online')->sum('net_price'); InvoiceMonthlyStatistics::create([ 'total_sales' => $total_sales, 'online_sales' => $online_sales, 'offline_sales' => $offline_sales, 'month' => $date->format('m'), 'year' => $date->format('y') ]); }
方案2:单次分组查询统计所有数据
仅查询一次数据库即可拿到所有统计结果,性能更高:
foreach ($period as $date) { $stats = Invoice::whereRaw('YEAR(created_at) = ?', $date->format('Y')) ->whereRaw('MONTH(created_at) = ?', $date->format('m')) ->selectRaw('type, sum(net_price) as sale_amount') ->groupBy('type') ->pluck('sale_amount', 'type'); InvoiceMonthlyStatistics::create([ 'total_sales' => $stats->sum(), 'online_sales' => $stats->get('online', 0), 'offline_sales' => $stats->get('offline', 0), 'month' => $date->format('m'), 'year' => $date->format('y') ]); }
额外说明:Laravel的
create方法本身会直接写入数据库,你原代码循环外的$invoiceStats->save()是多余操作,只会保存最后一次循环的统计数据,没有实际作用可以直接删除。
内容的提问来源于stack exchange,提问作者user12355909
相关产品推荐
相关产品推荐

