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

Laravel查询构造器与原生MySQL查询返回结果不一致排查

问题:Laravel Query Builder与原生SQL结果不一致

需求背景

筛选尚未产生结果的exercise,且每个exercise仅保留最新的评分记录(取ratings表中ID最大的条目)。原生SQL执行后得到预期的55条结果,但转换为Laravel Query Builder写法后返回384条(包含所有旧评分记录)。已确认数据库环境一致,且通过$query->toSql()输出的SQL在MySQL控制台执行结果正确。

原生SQL实现

$rawquery = <<<SQL
    SELECT exercises.id AS id, r1.rating, ABS(CAST(r1.rating AS SIGNED) - :rating) AS diff
    FROM exercises
    INNER JOIN exercise_theme ON exercises.id = exercise_theme.exercise_id AND theme_id = 12
    INNER JOIN ratings r1 ON exercises.id = r1.exercise_id
    LEFT JOIN ratings r2 ON exercises.id = r2.exercise_id AND r1.id < r2.id
    LEFT JOIN results ON exercises.id = results.exercise_id AND results.user_id = :uid
    WHERE exercises.status = 'enabled' AND r2.id IS NULL AND results.id IS NULL
    ORDER BY diff;
SQL;

$results = DB::select($rawquery, ['rating' => 1500, 'uid' => Auth::user()->id]);
dd(count($results));

Laravel Query Builder初始实现

$query = DB::table('exercises')
    ->selectRaw('exercises.id as id, r1.rating, ABS(CAST(r1.rating as SIGNED) - ?) as diff', [$rating])
    ->join('exercise_theme', function ($join) {
        $join->on('exercises.id', '=', 'exercise_theme.exercise_id')
            ->where('theme_id', '=', 12);
    })
    ->join('ratings as r1', 'exercises.id', '=', 'r1.exercise_id')
    ->leftJoin('ratings as r2', function ($join) {
        $join->on('exercises.id', '=', 'r2.exercise_id')
            ->where('r1.id', '<', 'r2.id');
    })
    ->leftJoin('results', function ($join) {
        $join->on('exercises.id', '=', 'results.exercise_id')
            ->where('results.user_id', Auth::user()->id);
    })
    ->where('exercises.status', 'enabled')
    ->whereNull('r2.id')
    ->whereNull('results.id')
    ->orderBy('diff');

问题原因与修复方案

核心问题

在leftJoin('ratings as r2')的闭包中,使用where('r1.id', '<', 'r2.id')会被Laravel解析为将字符串'r2.id'作为绑定参数,而非比较两个字段的值。这导致实际执行的SQL条件变为r1.id < 'r2.id',MySQL会将字符串转为数字0,使得r2.id IS NULL的筛选条件失效,最终返回所有评分记录。

修复代码

将比较字段的where方法替换为whereColumn方法,明确指定比较的是数据库列:

$query = DB::table('exercises')
    ->selectRaw('exercises.id as id, r1.rating, ABS(CAST(r1.rating as SIGNED) - ?) as diff', [$rating])
    ->join('exercise_theme', function ($join) {
        $join->on('exercises.id', '=', 'exercise_theme.exercise_id')
            ->where('theme_id', '=', 12);
    })
    ->join('ratings as r1', 'exercises.id', '=', 'r1.exercise_id')
    ->leftJoin('ratings as r2', function ($join) {
        $join->on('exercises.id', '=', 'r2.exercise_id')
            // 替换为whereColumn比较列值
            ->whereColumn('r1.id', '<', 'r2.id');
    })
    ->leftJoin('results', function ($join) {
        $join->on('exercises.id', '=', 'results.exercise_id')
            ->where('results.user_id', Auth::user()->id);
    })
    ->where('exercises.status', 'enabled')
    ->whereNull('r2.id')
    ->whereNull('results.id')
    ->orderBy('diff');

额外验证

修复后执行$query->get(),即可得到与原生SQL一致的55条结果。若仍有疑问,可通过$query->getBindings()查看实际绑定的参数,确认字段比较逻辑是否正确。

内容的提问来源于stack exchange,提问作者Kenneth Venken

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 19:25:31