php 按多列对 Eloquent 集合进行排序的语法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25451019/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the syntax for sorting an Eloquent collection by multiple columns?
提问by Don't Panic
I know that when using the query builder, it is possible to sort by multiple columns using
我知道在使用查询构建器时,可以使用多个列进行排序
...orderBy('column1')->orderBy('column2')
but now I am dealing with a collectionobject. Collections have the sortBy
method, but I have not been able to figure out how to make it work for multiple columns. Intuitively, I initially tried to use the same syntax as orderBy
.
但现在我正在处理一个集合对象。集合具有该sortBy
方法,但我无法弄清楚如何使其适用于多列。直觉上,我最初尝试使用与orderBy
.
sortBy('column1')->sortBy('column2)
but this apparently just applies the sorts sequentially and it ends up sorted by column2, disregarding column1. I tried
但这显然只是按顺序应用排序,最终按 column2 排序,而忽略 column1。我试过
sortBy('column1', 'column2')
but that throws the error "asort() expects parameter 2 to be long, string given". Using
但这会引发错误“asort() 期望参数 2 很长,给出的字符串”。使用
sortBy('column1, column2')
doesn't throw an error, but the sort appears to be pretty random, so I don't really know what that actually does. I looked at the code for the sortBy method, but unfortunately I am having a hard time understanding how it works.
不会抛出错误,但排序似乎非常随机,所以我真的不知道它实际上做了什么。我查看了 sortBy 方法的代码,但不幸的是我很难理解它是如何工作的。
回答by Mark Baker
sortBy()
takes a closure, allowing you to provide a single value that should be used for sorting comparisons, but you can make it a composite by concatenating several properties together
sortBy()
接受一个闭包,允许您提供一个用于排序比较的值,但您可以通过将多个属性连接在一起来使其成为一个组合
$posts = $posts->sortBy(function($post) {
return sprintf('%-12s%s', $post->column1, $post->column2);
});
If you need the sortBy against multiple columns, you probably need to space pad them to ensure that "ABC" and "DEF" comes after "AB" and "DEF", hence the sprint right padded for each column up to the column's length (at least for all but the last column)
如果您需要对多列进行 sortBy,您可能需要对它们进行空格填充以确保“ABC”和“DEF”出现在“AB”和“DEF”之后,因此冲刺右填充每列直到列的长度(至少对于最后一列之外的所有内容)
Note that it's generally a lot more efficient if you can use an orderBy in your query so the collection is ready-sorted on retrieval from the database
请注意,如果您可以在查询中使用 orderBy,那么通常会更有效率,以便在从数据库检索时准备好对集合进行排序
回答by derekaug
I found a different way to do this using sort()
on the eloquent Collection. It may potentially work a bit better or at least be a bit easier to understand than padding the fields. I'd be interested to see which performs better, as this one has more comparisons but i'm not doing the sprintf()
for every item.
我sort()
在 eloquent Collection 上找到了一种不同的方法来做到这一点。与填充字段相比,它可能会更好地工作,或者至少更容易理解。我很想看看哪个表现更好,因为这个有更多的比较,但我没有sprintf()
为每个项目都做。
$items->sort(
function ($a, $b) {
// sort by column1 first, then 2, and so on
return strcmp($a->column1, $b->column1)
?: strcmp($a->column2, $b->column2)
?: strcmp($a->column3, $b->column3);
}
);
回答by Hirnhamster
As @derekaug mentioned, the sort
method allows us to enter a custom closure for sorting the collection. But I thought his solution was somewhat cumbersometo write and it woulde be nice to have something like this:
正如@derekaug 提到的,该sort
方法允许我们输入一个自定义闭包来对集合进行排序。但我认为他的解决方案写起来有点麻烦,有这样的东西会很好:
$collection = collect([/* items */])
$sort = ["column1" => "asc", "column2" => "desc"];
$comparer = $makeComparer($sort);
$collection->sort($comparer);
In fact, this can be easily archived by the following $makeComparer
wrapper to generate the compare closure:
事实上,这可以通过以下$makeComparer
包装器轻松存档以生成比较闭包:
$makeComparer = function($criteria) {
$comparer = function ($first, $second) use ($criteria) {
foreach ($criteria as $key => $orderType) {
// normalize sort direction
$orderType = strtolower($orderType);
if ($first[$key] < $second[$key]) {
return $orderType === "asc" ? -1 : 1;
} else if ($first[$key] > $second[$key]) {
return $orderType === "asc" ? 1 : -1;
}
}
// all elements were equal
return 0;
};
return $comparer;
};
Examples
例子
$collection = collect([
["id" => 1, "name" => "Pascal", "age" => "15"],
["id" => 5, "name" => "Mark", "age" => "25"],
["id" => 3, "name" => "Hugo", "age" => "55"],
["id" => 2, "name" => "Angus", "age" => "25"]
]);
$criteria = ["age" => "desc", "id" => "desc"];
$comparer = $makeComparer($criteria);
$sorted = $collection->sort($comparer);
$actual = $sorted->values()->toArray();
/**
* [
* ["id" => 5, "name" => "Hugo", "age" => "55"],
* ["id" => 3, "name" => "Mark", "age" => "25"],
* ["id" => 2, "name" => "Angus", "age" => "25"],
* ["id" => 1, "name" => "Pascal", "age" => "15"],
* ];
*/
$criteria = ["age" => "desc", "id" => "asc"];
$comparer = $makeComparer($criteria);
$sorted = $collection->sort($comparer);
$actual = $sorted->values()->toArray();
/**
* [
* ["id" => 5, "name" => "Hugo", "age" => "55"],
* ["id" => 2, "name" => "Angus", "age" => "25"],
* ["id" => 3, "name" => "Mark", "age" => "25"],
* ["id" => 1, "name" => "Pascal", "age" => "15"],
* ];
*/
$criteria = ["id" => "asc"];
$comparer = $makeComparer($criteria);
$sorted = $collection->sort($comparer);
$actual = $sorted->values()->toArray();
/**
* [
* ["id" => 1, "name" => "Pascal", "age" => "15"],
* ["id" => 2, "name" => "Angus", "age" => "25"],
* ["id" => 3, "name" => "Mark", "age" => "25"],
* ["id" => 5, "name" => "Hugo", "age" => "55"],
* ];
*/
Now, since we're talking Eloquent here, chances are high that you're also using Laravel. So we might even bind the $makeComparer()
closure to the IOC and resolve it from there:
现在,既然我们在这里谈论的是 Eloquent,那么您很有可能也在使用 Laravel。所以我们甚至可以将$makeComparer()
闭包绑定到 IOC 并从那里解决它:
// app/Providers/AppServiceProvider.php
// in Laravel 5.1
class AppServiceProvider extends ServiceProvider
{
/**
* ...
*/
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind("collection.multiSort", function ($app, $criteria){
return function ($first, $second) use ($criteria) {
foreach ($criteria as $key => $orderType) {
// normalize sort direction
$orderType = strtolower($orderType);
if ($first[$key] < $second[$key]) {
return $orderType === "asc" ? -1 : 1;
} else if ($first[$key] > $second[$key]) {
return $orderType === "asc" ? 1 : -1;
}
}
// all elements were equal
return 0;
};
});
}
}
Now you can use it everywhere you need to like so:
现在你可以在任何你需要的地方使用它:
$criteria = ["id" => "asc"];
$comparer = $this->app->make("collection.multiSort",$criteria);
$sorted = $collection->sort($comparer);
$actual = $sorted->values()->toArray();
回答by dtbarne
A simple solution is to chain sortBy() multiple times in reverse order of how you want them sorted. Downside is this is likely to be slower than sorting at once in the same callback, so use at your own risk on large collections.
一个简单的解决方案是以与您希望排序方式相反的顺序将 sortBy() 链接多次。缺点是这可能比在同一个回调中一次排序要慢,因此在大型集合中使用风险自负。
$collection->sortBy('column3')->sortBy('column2')->sortBy('column1');