Laravel Eloquent按价格分组筛选每组最新created_at数据问题
Laravel中按价格连续区间分组取每组最新记录的解决方案
你的核心需求不是全局按price分组取最新,而是要捕捉连续相同price的批次分组——当价格从1010切换到1007再切回1010时,这两个1010属于不同的价格阶段,各自阶段的最后一条记录都需要保留。之前的方法失效,是因为它们都做了全局price去重,自然会丢失id=2的条目。
以下是两种可行的解决方案:
方法一:数据库层面用窗口函数(推荐,适用于MySQL 8.0+)
利用LAG()窗口函数识别价格变化节点,为连续相同price的记录分配分组ID,再取每组的最后一条:
$alerts = DB::table(function ($query) { $query->selectRaw('*, SUM(CASE WHEN price != LAG(price, 1, -1) OVER (ORDER BY created_at) THEN 1 ELSE 0 END) OVER (ORDER BY created_at) AS group_id') ->from('alerts_logs'); }, 'with_groups') ->selectRaw('MAX(id) as id, price, MAX(created_at) as created_at') ->groupBy('group_id', 'price') ->orderBy('created_at') ->get();
逻辑说明:
LAG(price, 1, -1)获取当前记录的上一条price,默认值设为-1确保第一条记录触发分组- 当当前price与上一条不同时,
SUM()累加生成新的group_id,连续相同price的记录会被分到同一组 - 最后按
group_id分组,取每组的最大created_at(对应最新记录)
方法二:Collection层面遍历处理(兼容旧版本数据库)
如果数据库不支持窗口函数,可通过遍历Collection实现连续价格分组:
$alerts = AlertsLog::orderBy('created_at')->get(); $uniqueAlerts = collect(); $currentPrice = null; $currentGroup = collect(); foreach ($alerts as $alert) { if ($alert->price !== $currentPrice) { // 切换价格时,把上一组的最后一条加入结果 if (!$currentGroup->isEmpty()) { $uniqueAlerts->push($currentGroup->sortByDesc('created_at')->first()); } $currentPrice = $alert->price; $currentGroup = collect(); } $currentGroup->push($alert); } // 加入最后一组的最新记录 if (!$currentGroup->isEmpty()) { $uniqueAlerts->push($currentGroup->sortByDesc('created_at')->first()); } // 重置索引 $uniqueAlerts = $uniqueAlerts->values();
逻辑说明:
- 先按
created_at正序排序,保证遍历顺序是时间先后 - 遍历过程中把连续相同price的记录归为一组,切换价格时保存上一组的最新记录
- 最后处理剩余的最后一组
为什么之前的方法失效?
- groupBy子查询方式:全局按
price分组,只会取每个price的最大id(对应全局最新的记录),因此1010的最大id是5,自然丢失id=2 - Collection::unique('price'):按price全局去重,由于你先按
created_at倒序,每个price只会保留第一条出现的最新记录(1010保留id=5),同样丢失id=2
内容的提问来源于stack exchange,提问作者Yashgupta
相关产品推荐
相关产品推荐

