如何用Eloquent复现含Join与Sum的指定SQL查询?
用Eloquent复现目标查询的最优方案
首先得指出原SQL里一个小问题:你用了inner join,这会导致没有关联表B记录的表A行被完全排除,而你的having条件里包含了sum is null,这在inner join下永远不会触发。所以正确的关联应该用left join,才能包含那些没有对应表B记录的表A行。
下面是几种符合Eloquent风格的最优方案,按可读性和简洁度排序:
方案1:使用withSum快捷方法(推荐)
Laravel提供的withSum方法可以快速计算关联模型字段的总和,并且默认用left join处理无关联的情况,代码最简洁易读:
首先确保你的模型关联已经定义好:
// app/Models/Import.php(对应表A) namespace App\Models; use Illuminate\Database\Eloquent\Model; class Import extends Model { protected $table = 'A'; // 如果表名不是默认的imports,需要指定 public function partials() { return $this->hasMany(PartialImport::class, 'a_id', 'id'); } } // app/Models/PartialImport.php(对应表B) namespace App\Models; use Illuminate\Database\Eloquent\Model; class PartialImport extends Model { protected $table = 'B'; // 如果表名不是默认的partial_imports,需要指定 }
然后就可以写出查询:
use App\Models\Import; use Illuminate\Support\Facades\DB; $results = Import::query() // 计算关联表B的partial_import总和,结果会存到partials_sum_partial_import字段 ->withSum('partials', 'partial_import') // 过滤条件:总和小于total_import,或者总和为null(即无关联表B的记录) ->where(function ($query) { $query->where('partials_sum_partial_import', '<', DB::raw('total_import')) ->orWhereNull('partials_sum_partial_import'); }) ->get();
优点:
- 完全遵循Eloquent的关联风格,代码可读性极强
- 自动处理left join,无需手动编写关联逻辑
- 不需要手动处理group by,Laravel会自动处理聚合计算
方案2:使用leftJoin + groupBy + havingRaw(贴近原SQL)
如果更贴近你原有的SQL逻辑,可以用手动join的方式,注意要改成left join:
use App\Models\Import; use Illuminate\Support\Facades\DB; $results = Import::query() ->select('A.*', DB::raw('SUM(B.partial_import) as sum')) ->leftJoin('B', 'A.id', '=', 'B.a_id') ->groupBy('A.id') // 注意:如果数据库开启了ONLY_FULL_GROUP_BY,需要groupBy所有表A的字段,或者用selectRaw包含所有字段 ->havingRaw('SUM(B.partial_import) < A.total_import OR SUM(B.partial_import) IS NULL') ->get();
注意事项:
- 必须用
leftJoin替代原SQL的innerJoin,否则无法获取无关联表B的表A行 - 如果你的数据库开启了
ONLY_FULL_GROUP_BY(MySQL默认开启),需要确保groupBy包含所有非聚合的选中字段,或者修改查询为selectRaw列出表A的所有字段
方案3:子查询 + whereDoesntHave(适合大数据量场景)
如果表B的数据量很大,子查询可能会有更好的性能(取决于索引情况),可以用这种方式:
use App\Models\Import; $results = Import::query() ->where(function ($query) { // 子查询计算当前表A行对应的表B总和,判断是否小于total_import $query->whereRaw('(SELECT SUM(partial_import) FROM B WHERE a_id = A.id) < total_import') // 处理无关联表B的情况 ->orWhereDoesntHave('partials'); }) ->get();
优点:
- 不需要group by,避免了ONLY_FULL_GROUP_BY的限制
- 子查询可以利用
B.a_id的索引,在大数据量下可能有更好的性能
内容的提问来源于stack exchange,提问作者Paolo Cantoni
相关产品推荐
相关产品推荐

