Laravel | PHP -将结果数组分割为前10名



我是PHP新手,所以我的解决方案可能非常低效,所以请在这里找到更高效/更好的方法。

考虑一个对象数组:
[
['sku' => 'AAA', 'amount' => ###],
['sku' => 'BBB', 'amount' => ###],
['sku' => 'CCC', 'amount' => ###],
['sku' => 'DDD', 'amount' => ###],
['sku' => 'EEE', 'amount' => ###],
['sku' => 'FFF', 'amount' => ###],
['sku' => 'GGG', 'amount' => ###],
['sku' => 'HHH', 'amount' => ###],
['sku' => 'III', 'amount' => ###],
['sku' => 'JJJ', 'amount' => ###],
['sku' => 'KKK', 'amount' => ###],
['sku' => 'LLL', 'amount' => ###],
['sku' => 'MMM', 'amount' => ###],
]

我们希望保持前9个不变,但将其余的合并到'sku' => 'Other'和一些金额下。

下面是代码的工作版本:

$data = 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();
$dataArray = $data->toArray();
$topNine = array_slice($dataArray, 0, 9);
$other = array_slice($dataArray, 9);
if (count($other)) {
$otherSum = array_reduce($other, function ($carry, $item) {
return $carry += moneyStringToFloat($item->cogs);
}, 0);
$otherObj = new stdClass();
$otherObj->sku = 'Other';
$otherObj->cogs = floatToMoneyString($otherSum);
$topNine[] = $otherObj;
}

最后的结果看起来像这样:

[
['sku' => 'AAA', 'amount' => ###],
['sku' => 'BBB', 'amount' => ###],
['sku' => 'CCC', 'amount' => ###],
['sku' => 'DDD', 'amount' => ###],
['sku' => 'EEE', 'amount' => ###],
['sku' => 'FFF', 'amount' => ###],
['sku' => 'GGG', 'amount' => ###],
['sku' => 'HHH', 'amount' => ###],
['sku' => 'III', 'amount' => ###],
['sku' => 'Other', 'amount' => ###],
]

有更好的方法来做这件事吗?有办法直接在QueryBuilder吗?

谢谢你,

Laravel是关于使用LaravelCollection及其提供的所有方法。

首先,您不必将其转换为数组,直接处理数据即可。Collections有一个slice方法,而不是减少它有一个sum方法,它和你所做的一样。所以不要在数组、PHP函数等之间徘徊,保持简单、简短和简洁。在PHP中创建默认对象,可以通过多种方式完成,我喜欢创建数组并将其转换为对象,所有方法都可以,但我觉得这是最干净和最短的。

$analytics = DB::table('analytics')->...;
$topNine = $analytics->slice(0, 9);
$otherSum = $analytics->slice(9)->sum(function ($analytic) {
return moneyStringToFloat($item->cogs);
});
$topNine->push((object)[
'sku' => 'Other',
'cogs' => floatToMoneyString($otherSum),
]);
return $topNine;

你的代码很好,我清理了它,并使用了一个更Laravel的方法,希望你能从中得到灵感。

作为奖励,您可以使用分析作为模型,创建Eloquent访问器。这可以使用Collection方法上的高阶函数使sum变成这个漂亮的语法糖。

class Analytic extends Model
{
protected function cogsPrice(): Attribute
{
return Attribute::make(
get: fn ($value) => moneyStringToFloat($this->cogs),
);
}
}
$topNine = $analytics->slice(0, 9);
$topNine->push((object)[
'sku' => 'Other',
'cogs' => floatToMoneyString($analytics->slice(9)->sum->cogsPrice),
]);
return $topNine;

根据你返回的数据的大小和你打算用这些数据做什么(即你以后需要它吗?)你可以返回所有的数据,然后在内存中操作它。

$all = Analytics::orderBy('amount', 'DESC');
$merged = collect($all->take(9)->get(['sku', 'amount'])->toArray())
->merge(collect(array(['sku' => 'other', 'amount' => $all->skip(9)->sum('amount')])));

或者,如果你只是感兴趣的第一个9个人记录,从10日开始记录的不感兴趣,你以后不需要它们的逻辑,你可以第一个9,然后一切:

$top9 = Analytics::orderBy('amount', 'DESC')->take(9);
$other = collect(array(['sku' => 'other', 'amount' => Analytics::whereNotIn('sku', $top9->pluck('sku'))->sum('amount')]));
$merged = collect($top9->get(['sku', 'amount'])->toArray())
->merge($other);

上面的选项意味着不将可能很大的数据集加载到内存中,并在数据库上执行限制和求和操作,但确实需要对数据库进行一些额外的调用。因此,要考虑到这些权衡。

您可以像这样使用take()limit():

$data = DB::table('analytics')
->whereBetween('order_date', [$start, $end])
->whereIn('source', $suppliers)->select()
->orderBy('amount', 'DESC')
->groupBy('sku')
->get()
->take(9);

$data = DB::table('analytics')
->whereBetween('order_date', [$start, $end])
->whereIn('source', $suppliers)->select()
->orderBy('amount', 'DESC')
->groupBy('sku')
->limit(9)
->get();
<标题>

的区别尽管它们的作用基本相同,但它们之间还是有一些值得了解的区别。

限制()

limit()仅适用于雄辩ORM或查询生成器对象。这意味着数字nlimit(n)中指定,一旦查询找到与此相等的记录数,它将简单地停止执行,使查询运行得更快。

语法:

limit(9)->get() // correct
get()->limit(9) // incorrect

采取()

take()take将简单地让查询运行,直到获取记录,然后它简单地提取指定的记录数量,使其比limit()慢,但它有它自己的用途,例如有所有记录的计数,但在你的情况下只取少数记录。

语法:

take(9)->get() // correct
get()->take(9) // correct
<标题>你的案子h1> 为你想要所有的记录,这里你可以做的是,一旦数据从$data中获取,你可以简单地:
$topNine = $data->take(9)->toArray()
$other = $data->skip(9)->take(PHP_INT_MAX)->toArray() // Some very large number to take all the remaining records PHP_INT_MAX in that case

这里skip()基本上跳过了你想要在$other中排除的任何数量的元素。你可以skip(count($topNine))。希望这能让你容易理解。

最新更新