You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:57:29