
本文详解 Laravel 5.8+ 中使用 Query Builder 构建含 BETWEEN column1 AND column2 条件的复杂联表查询时的常见陷阱,重点解决 whereBetween() 无法引用数据库字段导致空结果的问题,并提供 Query Builder 和原生 DB::select() 两种可靠实现方案。
本文详解 laravel 5.8+ 中使用 query builder 构建含 `between column1 and column2` 条件的复杂联表查询时的常见陷阱,重点解决 `wherebetween()` 无法引用数据库字段导致空结果的问题,并提供 query builder 和原生 `db::select()` 两种可靠实现方案。
在 Laravel 中将原生 MySQL 查询转换为 Eloquent 或 Query Builder 语法时,一个极易被忽视却致命的错误是:误用 whereBetween() 处理动态字段范围比较。例如,原始 SQL 中的条件:
WHERE (tp.paid_on BETWEEN cr.created_at AND cr.closed_at)
若写成:
->whereBetween('tp.paid_on', ['cr.created_at', 'cr.closed_at'])
Laravel 会将其解析为字符串字面量(即 'cr.created_at' 和 'cr.closed_at'),最终生成的 SQL 实际为:
`tp`.`paid_on` BETWEEN 'cr.created_at' AND 'cr.closed_at' -- ❌ 错误:字段名被当成了字符串常量
这显然无法匹配任何真实数据,导致查询返回空数组 [],即使原生 SQL 在 phpMyAdmin 中运行完全正确。
✅ 正确做法是使用 whereRaw() 显式声明字段级比较:
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
->whereRaw('tp.paid_on BETWEEN cr.created_at AND cr.closed_at')
该写法确保 cr.created_at 和 cr.closed_at 作为数据库列名参与计算,生成符合预期的 SQL。
以下是修正后的完整 Query Builder 示例(Laravel 5.8+):
use Illuminate\Support\Facades\DB;
$startDate = '2022-06-01';
$results = DB::table('cash_registers as cr')
->select(DB::raw('
b.name as business_location_name,
u.first_name as cashier_first_name,
u.last_name as cashier_last_name,
cr.location_id,
SUM(CASE WHEN tp.is_return = "0" AND tp.method = "cash" THEN tp.amount ELSE 0 END) as totalCash,
SUM(CASE WHEN tp.is_return = "1" AND tp.method = "cash" THEN tp.amount ELSE 0 END) as totalReturn,
SUM(CASE WHEN tp.is_return = "0" AND tp.method = "card" THEN tp.amount ELSE 0 END) as totalCard,
cr.created_at,
cr.closed_at
'))
->leftJoin('transaction_payments as tp', 'tp.created_by', '=', 'cr.user_id')
->leftJoin('users as u', 'u.id', '=', 'cr.user_id')
->leftJoin('business_locations as b', 'b.id', '=', 'cr.location_id')
->whereRaw('tp.paid_on BETWEEN cr.created_at AND cr.closed_at') // ✅ 关键修正
->where('cr.status', 'close')
->where('cr.created_at', 'like', $startDate . '%')
->groupBy('cr.location_id', 'cr.user_id') // 可合并为单个 groupBy 调用
->orderBy('cr.location_id', 'asc')
->orderBy('cr.user_id', 'asc')
->get();
⚠️ 注意事项:
- groupBy() 支持多字段链式调用(如 ->groupBy('a')->groupBy('b')),也可一次性传入数组:->groupBy(['cr.location_id', 'cr.user_id']);
- 所有 CASE WHEN 表达式中的字符串值(如 "0"、"cash")在 DB::raw() 中需保持与原 SQL 一致的引号风格(双引号或单引号均可,但需转义内部引号);
- whereRaw() 中的 SQL 片段不自动转义参数,若需注入变量(如动态日期范围),应改用参数绑定方式(见下方 DB::select() 方案)以防止 SQL 注入。
? 替代推荐:使用 DB::select() 执行带参数绑定的原生查询
当查询逻辑极其复杂、可读性优先或需复用现有 SQL 时,直接使用 DB::select() 更安全、更直观:
$startDate = '2022-06-01'; $results = DB::select(<p>此方式既保留了原生 SQL 的灵活性与性能,又通过 ? 占位符和参数数组 $startDate . '%' 实现了安全的值绑定,规避了 SQL 注入风险。</p><p>✅ 总结: </p>
- whereBetween() 仅适用于固定值范围(如 ['2022-01-01', '2022-12-31']),不可用于字段间比较;
- 字段级范围判断(BETWEEN col_a AND col_b)必须使用 whereRaw();
- 高度复杂的报表类查询,推荐 DB::select() + 参数绑定,兼顾可维护性与安全性。










