Laravel/PHP取Top9合并剩余项为Other的高效实现方案
优化实现方案
完全可以把核心聚合逻辑下推到数据库层完成,大幅减少PHP和数据库之间的数据传输量、PHP内存占用,SKU量级越大性能提升越明显。
方案1:数据库层直接输出最终结果(推荐,适用于MySQL 8.0+、PostgreSQL、SQLite 3.25+等支持窗口函数的数据库)
核心逻辑是先用窗口函数按金额降序给每个聚合后的SKU打上排名,排名前9的保留原SKU,排名9之后的统一归为Other分组求和,最终数据库直接返回10条结果,不需要PHP做二次数组加工。
Laravel查询构造器实现代码如下:
// 拼接和原逻辑一致的金额计算表达式 $amountExpr = DB::raw('SUM(analytics.price' . ($costs ? ' + ' . $costs : '') . ') as amount'); // 第一步:先按SKU聚合金额,和你原逻辑的基础查询一致 $skuAggSubquery = DB::table('analytics') ->whereBetween('order_date', [$start, $end]) ->whereIn('source', $suppliers) ->groupBy('sku') ->select('sku', $amountExpr); // 第二步:打排名、分组合并Other、排序 $finalResult = DB::query() ->fromSub(function ($query) use ($skuAggSubquery) { $query->fromSub($skuAggSubquery, 'sku_agg') ->selectRaw('sku, amount, ROW_NUMBER() OVER (ORDER BY amount DESC) as rank'); }, 'ranked_sales') ->selectRaw(" CASE WHEN rank <= 9 THEN sku ELSE 'Other' END as sku, SUM(amount) as amount ") ->groupBy(DB::raw("CASE WHEN rank <= 9 THEN sku ELSE 'Other' END")) // 保证Other固定在最后一位,前9条保持金额降序 ->orderByRaw("CASE WHEN sku = 'Other' THEN 2 ELSE 1 END, amount DESC") ->get();
如果你用的是不支持窗口函数的老版本数据库(比如MySQL 5.7),不推荐硬写复杂SQL实现这个逻辑,可维护性太差,直接优化PHP层处理逻辑即可,性能损失可以忽略。
方案2:PHP层逻辑精简(兼容所有数据库版本)
你当前的实现逻辑本身没有功能性问题,只是有冗余的数组转换、手动求和操作,可以直接用Laravel集合的内置方法简化,代码更简洁,性能也略有提升:
$skuAgg = DB::table('analytics') ->whereBetween('order_date', [$start, $end]) ->whereIn('source', $suppliers) ->select([ 'sku', DB::raw('SUM(analytics.price' . ($costs ? ' + ' . $costs : '') . ') as amount'), ]) ->orderBy('amount', 'DESC') ->groupBy('sku') ->get(); // 直接取前9条 $result = $skuAgg->take(9); // 直接对剩余条目求和,不需要手动转数组、写reduce $otherTotal = $skuAgg->skip(9)->sum('amount'); if ($otherTotal > 0) { $result->push((object)[ 'sku' => 'Other', 'amount' => $otherTotal ]); }
注意:你贴的原代码里查询时金额别名是amount,后面求和时取的是cogs字段,属于笔误,实际使用时记得统一字段名,避免报错。
内容的提问来源于stack exchange,提问作者Riza Khan
相关产品推荐
相关产品推荐

