
本文讲解如何在 laravel 中正确构建查询,使记录满足“大于等于开始时间、且仅当结束时间存在时才小于等于结束时间”的动态日期范围条件。
本文讲解如何在 laravel 中正确构建查询,使记录满足“大于等于开始时间、且仅当结束时间存在时才小于等于结束时间”的动态日期范围条件。
在 Laravel 开发中,常遇到类似 start_at(必填)与 end_at(可空)并存的业务场景。例如优惠分组、有效期配置或权限策略等,要求:
- 若 end_at 为 NULL,则只要 effectiveDate >= start_at 即匹配;
- 若 end_at 存在,则需同时满足 start_at <= effectiveDate <= end_at。
直接使用 whereBetween() 并不适用——它要求两端均为非空值,而此处是条件性区间约束,需借助逻辑分组(where(...)->orWhere(...))实现“或”关系的动态判断。
✅ 正确写法如下(使用闭包 + 条件分支):
$effectiveDate = '2022-04-06 23:34:00';
GroupExtras::query()
->select('group_extras.*')
->join('groups', 'group_extras.group_id', '=', 'groups.id')
->where('group_extras.start_at', '<=', $effectiveDate)
->where(function ($query) use ($effectiveDate) {
// 满足任一条件即通过:
$query->whereNull('group_extras.end_at') // end_at 为空 → 无截止限制
->orWhere('group_extras.end_at', '>=', $effectiveDate); // 或者不为空且 ≥ effectiveDate
})
->orderBy('group_extras.value')
->first();? 关键点解析:
- 主 where('start_at', '<=', $date) 确保所有结果至少已生效;
- 内层 where(function) 将两个互斥条件用 orWhere 组合,语义为:“结束时间为空 或 结束时间晚于等于当前时间”;
- 使用 whereNull() 显式判断可空字段,比 IS NULL 原生写法更安全、兼容各数据库驱动;
- 注意 join 中的列关联需使用 =(而非原示例中的隐式逗号语法),避免潜在 SQL 错误。
⚠️ 常见误区提醒:
- ❌ 不要写成 whereBetween('start_at', ['...', '...']) —— whereBetween 作用于单字段,无法表达本场景的双字段混合逻辑;
- ❌ 避免在 where 中混用 AND/OR 而不加括号分组,易导致运算优先级错误(如原问题中硬编码 end_at >= ? 会过滤掉所有 end_at IS NULL 记录);
- ✅ 推荐对 $effectiveDate 做格式校验(如 Carbon::parse($effectiveDate)->toDateTimeString()),防止非法字符串引发 SQL 异常。
该模式具备良好扩展性,亦可用于其他“起始必填 + 结束可选”的时间范围筛选场景,如会员有效期、课程开课周期、API 访问令牌时效等。


















