如何用Laravel Eloquent one()->ofMany()获取下一个或最后一个排期?
问题:如何用关联关系替代属性实现获取下一个或最后一个排期事件?
我有一个Schedule模型用于记录项目(如Movie电影)的发布日期,一部电影可被排期0次或多次。我需要获取:
- 1)下一次排期时间(若无则获取上一次);
- 2)上一次排期时间。
目前Movie模型中已定义如下关联关系:
public function lastScheduleEvent(): HasOne { return $this->scheduleEvents()->one()->ofMany('starts_at','max'); } public function nextScheduleEvent(): HasOne { return $this->scheduleEvents()->one()->ofMany([ 'starts_at' => 'max' ], function (Builder $query) { $query->where('starts_at','>',now()); } ); }
同时通过属性获取下一个或最后一个排期:
protected function nextOrLastScheduleEvent(): Attribute { return Attribute::make( get: fn ($value) => $this->nextScheduleEvent ?? $this->lastScheduleEvent ); }
请问能否创建一个one()->ofMany(...)关联关系(替代属性)来获取nextOrLastScheduleEvent?即是否存在可结合min/max聚合函数的查询来实现获取下一个或最后一个排期事件?
回答
可以通过自定义排序逻辑结合ofMany闭包实现这个关联关系,核心是给排期事件设定优先级:优先选择未来的最近排期,无未来排期时选择过去的最近排期(最后一次)。
实现方案1:带优先级字段的排序
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\HasOne; public function nextOrLastScheduleEvent(): HasOne { return $this->scheduleEvents()->one()->ofMany( [ 'starts_at' => function ($query) { // 先按是否为未来排期设优先级,再按时间差绝对值排序 $query->selectRaw('CASE WHEN starts_at > NOW() THEN 0 ELSE 1 END as priority, starts_at') ->orderBy('priority') ->orderByRaw('ABS(TIMESTAMPDIFF(SECOND, starts_at, NOW()))'); } ] ); }
实现方案2:简化的直接排序表达式
如果不想额外选择字段,可直接在排序逻辑中用表达式:
public function nextOrLastScheduleEvent(): HasOne { return $this->scheduleEvents()->one()->ofMany( 'starts_at', function (Builder $query) { $query->orderByRaw('CASE WHEN starts_at > NOW() THEN 0 ELSE 1 END') ->orderByRaw('ABS(TIMESTAMPDIFF(SECOND, starts_at, NOW()))'); } ); }
逻辑说明
- 优先级排序:未来排期(
starts_at > NOW())优先级设为0,过去排期设为1,确保未来排期优先被选中。 - 时间差排序:按当前时间与排期时间的秒数差绝对值排序,保证未来排期取最近的,过去排期取最后一次。
ofMany会自动获取排序后的第一条记录,完全符合「有下一次则取下一次,无则取上一次」的需求。
注意:如果使用非MySQL数据库(如PostgreSQL),需要替换
TIMESTAMPDIFF为对应时间差函数,比如PostgreSQL可用EXTRACT(EPOCH FROM (starts_at - NOW()))计算秒数差。
内容的提问来源于stack exchange,提问作者wivku
相关产品推荐
相关产品推荐

