
本文详解 Laravel 5.8+ 中如何正确将含 BETWEEN column1 AND column2 的原生 MySQL 查询转换为 Eloquent/Query Builder 写法,重点解决 whereBetween() 误传字符串字段名导致查询为空的问题,并提供 DB::raw() 与 DB::select() 两种安全、可维护的替代方案。
本文详解 laravel 5.8+ 中如何正确将含 `between column1 and column2` 的原生 mysql 查询转换为 eloquent/query builder 写法,重点解决 `wherebetween()` 误传字符串字段名导致查询为空的问题,并提供 `db::raw()` 与 `db::select()` 两种安全、可维护的替代方案。
在 Laravel 应用中,当需要将复杂的原生 SQL(尤其是涉及字段间动态范围比较,如 tp.paid_on BETWEEN cr.created_at AND cr.closed_at)迁移到 Query Builder 时,一个常见却极易被忽视的错误是误用 whereBetween() 方法。
whereBetween() 设计用于比较字段值是否落在两个具体值(如字符串、数字、日期字面量)之间,而非两个数据库字段之间。例如:
->whereBetween('tp.paid_on', ['2022-06-01 00:00:00', '2022-06-01 23:59:59'])
但若你传入字段名字符串(如 'cr.created_at'),Laravel 会将其视为字面量字符串,生成的 SQL 实际为:
`tp`.`paid_on` BETWEEN 'cr.created_at' AND 'cr.closed_at'
这显然无法匹配任何真实数据——因为 paid_on 是时间戳,而 'cr.created_at' 是纯文本字符串,类型与语义均不匹配,最终导致查询返回空数组 []。
PHP中文网提供Laravel 13.2.0版本下载,Laravel框架 是基于 PHP 8.3+ 的高性能框架,官方推荐通过 Composer 安装。它内置 AI SDK、JSON:API Resources 及原生向量搜索,支持属性驱动开发与队列路由,大幅提升开发效率。相比旧版,13.2.0 优化了缓存 TTL 管理与实时通信,无需 Redis 即可横向扩展。作为现代 Web 开发首选,它兼顾安全与极速体验,助您快速构建企业级应用。
✅ 正确做法:使用 whereRaw() 执行原始 SQL 片段,让数据库直接解析字段引用:
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 替代 whereBetween,保留字段名原生语义
->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')
->orderBy('cr.user_id')
->get();
⚠️ 注意事项:
- groupBy() 支持多字段参数(Laravel 5.8+),推荐写为 ->groupBy('cr.location_id', 'cr.user_id'),更简洁且语义清晰;
- 所有 CASE WHEN 表达式中的字符串值(如 '0', 'cash')需使用单引号,与原生 SQL 一致;
- 若查询逻辑复杂、复用性高或需强类型支持,建议封装为数据库视图或使用 DB::select() 配合参数绑定,进一步提升安全性与可读性:
$results = DB::select(
"SELECT 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 = ? AND tp.method = ? THEN tp.amount ELSE 0 END) as totalCash,
SUM(CASE WHEN tp.is_return = ? AND tp.method = ? THEN tp.amount ELSE 0 END) as totalReturn,
SUM(CASE WHEN tp.is_return = ? AND tp.method = ? THEN tp.amount ELSE 0 END) as totalCard,
cr.created_at,
cr.closed_at
FROM cash_registers as cr
LEFT JOIN transaction_payments as tp ON cr.user_id = tp.created_by
LEFT JOIN users as u ON u.id = cr.user_id
LEFT JOIN business_locations as b ON b.id = cr.location_id
WHERE tp.paid_on BETWEEN cr.created_at AND cr.closed_at
AND cr.status = ?
AND cr.created_at LIKE ?
GROUP BY cr.location_id, cr.user_id
ORDER BY cr.location_id, cr.user_id",
['0', 'cash', '1', 'cash', '0', 'card', 'close', $startDate . '%']
);
该方式完全复用原生 SQL,通过参数化绑定(?)防止注入,同时避免 Query Builder 的 DSL 限制,是处理高度定制化报表查询的稳健选择。
总结:在 Laravel 中处理字段间动态范围条件时,切勿将字段名作为字符串传给 whereBetween();优先选用 whereRaw() 保持 SQL 语义完整性,或采用 DB::select() + 参数绑定实现最大灵活性与安全性。










