发布于2026-07-10 阅读(0)
扫一扫,手机访问
withSum() 是最轻量的预加载求和方案,一条 SQL 通过左连接 + SUM() 实现;手动 join 需注意 selectRaw 别名与 GROUP BY 匹配;sum() 返回单值,withSum() 为每模型注入属性;子查询求和仅适用于复杂聚合或分页场景。

先说几个关键判断。说到预加载求和,withSum() 算得上是当前最轻量、最符合 Eloquent 语义的解法。它底层走的是左连接加 SUM() 聚合,一条 SQL 就能把活干完,根本不用担心 N+1 的问题。
但实际用起来,容易中招的地方主要有三个:
withSum('items') 里这个 'items' 必须和模型里定义的关系方法名严格一致。比如你写的是 public function items() { return $this->hasMany(OrderItem::class); },那这里就只能传 'items',拼错了或者用了别的名字,结果就是空值。$query 参数,不能漏掉。正确写法是 withSum(['items as total' => function ($query) { $query->where('status', 'active'); }])。闭包里的 $query 是个查询构建器实例,别在里边调 select() 或 get(),那会直接把聚合逻辑搞崩,轻则报错,重则返回空值。'items as total',值是闭包。这样最终每个模型上就会多一个 total 属性。看个具体例子:$orders = Order::withSum(['items as item_total' => fn($q) => $q->whereNotNull('paid_at')])->get();。跑完之后,每个 $order->item_total 就是该订单中那些已支付项的总金额。干净利落。
不过话说回来,withSum() 也不是万能的。如果遇到跨多层关联、需要加特别复杂的过滤条件,或者想一次性取多个不同条件的和——比如“今日点击数”和“历史总点击数”同时出现——那就得老老实实退回到查询构建器层面,手动写 LEFT JOIN 加 SUM()。
这时候,核心挑战在于字段别名和 GROUP BY 的匹配:
*。不然在 MySQL 5.7 及以上的严格模式下,GROUP BY 会直接报错。selectRaw() 里 SUM() 的别名记得和后续 PHP 里访问的属性名保持一致。比如写了 SUM(clicks.total) as today_clicks,后面就用 $row->today_clicks 取值。mergeBindings(),否则 where 条件不会生效,结果自然就不对了。举个例子会更清楚:DB::table('affiliates')->leftJoin('referral_clicks as clicks', 'clicks.affiliate_id', '=', 'affiliates.id')->selectRaw('affiliates.*, SUM(clicks.clicks) as total_clicks')->groupBy('affiliates.id')->get();
这里有个容易混淆的点,Model::sum('field') 返回的是单个数字,适合用来统计全表或简单条件下的总和。而 withSum() 或 withCount() 是为每个模型实例注入一个新属性,返回的是一个模型集合。两者性质完全不同。
常见的坑有三个:
Order::withSum('items')->sum('item_total')。因为 withSum() 后的数据还没 hydrate 成属性,sum() 找不到 item_total 这个字段,结果要么返回 0,要么直接报错。$orders->sum('item_total')。但注意,这是在 PHP 层完成的,数据量大的时候性能可能会成为瓶颈。withCount() 当 withSum() 用。虽然两者语法很像,但底层生成的 SQL 完全不同。withCount(['items as total']) 只会返回一个计数,不可能给你金额总和。最后聊一下子查询求和。虽然它在写法上很灵活——比如先算每个订单的 SUM(amount),再把结果跟主表 join 起来——但这玩意儿的代价也不小。MySQL 需要物化一个临时表,数据量一上来,性能直接跳水。Lara vel 官方文档也明确建议优先用 withSum() 或 join。
只有两种情况值得考虑用子查询:
JOIN 根本写不出来。JOIN 会导致重复行,干扰 count() 的总数计算。这时候可以先在子查询里完成聚合,再跟主表分页查询。来看一个结构示例(注意:不是推荐方案,只是说明逻辑):$sub = DB::table('order_items')->selectRaw('order_id, SUM(amount) as sum_amount')->groupBy('order_id'); DB::table('orders')->select('orders.*', 'sub.sum_amount')->joinSub($sub, 'sub', 'sub.order_id', '=', 'orders.id')->get();
其实,真正头疼的不是写 SQL 本身,而是聚合之后的数据一致性问题。比如订单状态变了,但缓存里的 item_total 还没刷新;或者并发修改子表时没有加事务锁。这些地方,比怎么写 SQL 更容易出岔子。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8