Laravel Eloquent查询占位符过多错误及关联数据统计优化求助
解决Eloquent查询占位符溢出与工时汇总问题
你遇到的SQLSTATE[HY000]: General error: 1390 Prepared statement contains too many placeholders错误,根源是一次性加载三级关联时,Eloquent生成的WHERE IN语句占位符数量超过了MySQL默认限制(65535个)。结合你的数据规模(600条Purchase Order、6万条Form、25万条Manpower),以下是具体优化方案和工时计算实现:
一、查询优化方案
1. 分页获取Purchase Order,控制单次关联数据量
将全量查询改为分页,减少单次加载的关联数据,从根源避免占位符溢出:
$purchaseOrders = PurchaseOrder::whereNotIn('status', [PurchaseOrder::INACTIVE]) ->orderBy('updated_at', 'desc') ->paginate(20); // 每页条数可根据业务调整 // 延迟加载关联,避免一次性生成大量占位符 $purchaseOrders->load(['form' => function ($query) { $query->with(['manpower']); }]);
2. 预聚合工时,跳过全量Manpower加载
如果核心需求是汇总工时而非获取所有Manpower明细,直接在关联查询中用SQL聚合计算,彻底减少数据量:
$purchaseOrders = PurchaseOrder::whereNotIn('status', [PurchaseOrder::INACTIVE]) ->orderBy('updated_at', 'desc') ->with(['form' => function ($query) { // 给每个Form添加总工时字段,必须包含关联外键 $query->select('id', 'purchase_order_id', DB::raw('SUM(TIMESTAMPDIFF(SECOND, manpower.in_time, manpower.out_time)/3600) as total_hours')) ->join('manpower', 'form.id', '=', 'manpower.form_id') ->groupBy('form.id'); }]) ->get();
此方案每个Form仅返回一条带汇总工时的记录,完全规避占位符问题。
3. 分批处理全量数据(若必须获取所有PO)
如果业务要求必须加载全部600条PO,可通过分批处理控制单次查询的占位符数量:
$purchaseOrders = collect(); PurchaseOrder::whereNotIn('status', [PurchaseOrder::INACTIVE]) ->orderBy('updated_at', 'desc') ->chunk(50, function ($batch) use (&$purchaseOrders) { $batch->load(['form.manpower']); $purchaseOrders = $purchaseOrders->merge($batch); });
每次处理50条PO,确保关联查询的占位符数量在MySQL限制内。
二、Manpower工时计算与汇总
1. 单条Manpower工时计算
在Manpower模型中定义访问器,快速获取单条记录的工时:
// app/Models/Manpower.php public function getHoursAttribute() { if (!$this->in_time || !$this->out_time) { return 0; } // 计算小时数并保留两位小数 return round(($this->out_time->getTimestamp() - $this->in_time->getTimestamp()) / 3600, 2); }
使用时直接调用$manpower->hours即可得到单条工时。
2. 按Form汇总工时
在Form模型中定义方法,快速获取该Form的总工时:
// app/Models/Form.php public function totalHours() { return $this->manpower() ->selectRaw('SUM(TIMESTAMPDIFF(SECOND, in_time, out_time)/3600) as total') ->first()->total ?? 0; }
调用$form->totalHours()即可得到对应Form的总工时。
3. 按Purchase Order汇总总工时
在PurchaseOrder模型中定义方法,获取整个PO的总工时:
// app/Models/PurchaseOrder.php public function totalOrderHours() { return $this->form() ->join('manpower', 'form.id', '=', 'manpower.form_id') ->selectRaw('SUM(TIMESTAMPDIFF(SECOND, manpower.in_time, manpower.out_time)/3600) as total') ->first()->total ?? 0; }
调用$purchaseOrder->totalOrderHours()即可得到对应PO的总工时。
内容的提问来源于stack exchange,提问作者Bryant Tang
相关产品推荐
相关产品推荐

