在 Laravel Eloquent 中实现带 SUM 函数的分组聚合查询
在 Laravel 项目开发中,我们经常需要对数据库中的数据进行统计分析,比如统计不同分类下的商品总销量、不同用户的订单总金额等。这类需求通常需要结合数据库的 GROUP BY 分组和 SUM 聚合函数来实现,Laravel 的 Eloquent ORM 提供了便捷的查询构造方法,让我们可以不用手写原生 SQL 就能完成这类操作。
基础场景:单字段分组求和
假设我们有一个订单表 orders,表结构包含以下核心字段:id(订单ID)、user_id(用户ID)、amount(订单金额)、status(订单状态)。现在需要统计每个用户的总订单金额,就可以使用分组聚合查询实现。
首先定义对应的 Order 模型:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
// 指定关联的表名,若模型名和表名符合 Laravel 约定可省略
protected $table = 'orders';
// 允许批量赋值的字段
protected $fillable = ['user_id', 'amount', 'status'];
}接下来编写查询逻辑,使用 select 方法指定查询的字段,其中聚合函数需要用 DB::raw 包裹,再通过 groupBy 方法指定分组的字段:
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use Illuminate\Support\Facades\DB;
class OrderController extends Controller
{
public function getUserTotalAmount()
{
// 查询每个用户的总订单金额
$result = Order::select(
'user_id',
DB::raw('SUM(amount) as total_amount')
)
->groupBy('user_id')
->get();
return response()->json($result);
}
}这段代码的执行逻辑是:先选择 user_id 字段,再通过 DB::raw 执行原生 SQL 片段 SUM(amount) as total_amount,计算每组 amount 的总和并命名为 total_amount,最后按照 user_id 分组,返回所有分组的结果。
多条件分组求和
如果我们需要同时按照多个字段分组,比如统计不同用户、不同订单状态下的总订单金额,只需要在 groupBy 中传入多个字段,同时在 select 中加上对应的分组字段即可:
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use Illuminate\Support\Facades\DB;
class OrderController extends Controller
{
public function getUserStatusTotalAmount()
{
// 查询每个用户、每种订单状态下的总订单金额
$result = Order::select(
'user_id',
'status',
DB::raw('SUM(amount) as total_amount')
)
->groupBy('user_id', 'status')
->get();
return response()->json($result);
}
}这里的分组逻辑是先按 user_id 分组,再在同一用户下按 status 细分分组,最终得到每个用户每种订单状态的总金额。
带筛选条件的分组求和
实际场景中我们往往需要先筛选数据再分组,比如只统计已支付订单(假设 status = 1 代表已支付)的用户总金额,可以结合 where 条件使用:
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use Illuminate\Support\Facades\DB;
class OrderController extends Controller
{
public function getPaidUserTotalAmount()
{
// 查询已支付订单每个用户的总金额
$result = Order::select(
'user_id',
DB::raw('SUM(amount) as total_amount')
)
->where('status', 1)
->groupBy('user_id')
->get();
return response()->json($result);
}
}如果需要筛选分组后的结果,比如只保留总金额大于 1000 的用户数据,需要使用 having 方法,因为 where 是针对原始数据行的筛选,having 是针对分组后的聚合结果筛选:
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use Illuminate\Support\Facades\DB;
class OrderController extends Controller
{
public function getHighConsumeUser()
{
// 查询总订单金额大于1000的用户
$result = Order::select(
'user_id',
DB::raw('SUM(amount) as total_amount')
)
->groupBy('user_id')
->having('total_amount', '>', 1000)
->get();
return response()->json($result);
}
}关联模型中的分组求和
如果我们需要关联其他表进行分组求和,比如用户表 users 有 name 字段,要同时查出用户名称,可以通过 join 关联用户表实现:
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use Illuminate\Support\Facades\DB;
class OrderController extends Controller
{
public function getUserTotalAmountWithName()
{
// 关联用户表,查询每个用户的名称和总订单金额
$result = Order::select(
'orders.user_id',
'users.name as user_name',
DB::raw('SUM(orders.amount) as total_amount')
)
->join('users', 'orders.user_id', '=', 'users.id')
->groupBy('orders.user_id', 'users.name')
->get();
return response()->json($result);
}
}这里需要注意,groupBy 中需要包含所有非聚合的查询字段,否则部分数据库会报错。
注意事项
- 使用
DB::raw时如果涉及用户输入,要注意 SQL 注入风险,非必要场景尽量使用 Eloquent 提供的内置方法。 - 不同数据库对
GROUP BY的严格模式要求不同,MySQL 5.7+ 默认开启严格模式,要求select中的非聚合字段必须出现在GROUP BY中,开发时要注意字段匹配。 - 如果分组字段较多,
groupBy可以传入数组,也可以链式调用多次groupBy,比如->groupBy('user_id')->groupBy('status')效果和传入多个参数一致。