【问题标题】:What is the syntax for sorting an Eloquent collection by multiple columns?按多列对 Eloquent 集合进行排序的语法是什么?
【发布时间】:2014-08-22 15:55:24
【问题描述】:

我知道在使用查询生成器时,可以使用多个列进行排序

...orderBy('column1')->orderBy('column2')

但现在我正在处理一个collection 对象。集合具有sortBy 方法,但我无法弄清楚如何使其适用于多个列。直觉上,我最初尝试使用与orderBy 相同的语法。

sortBy('column1')->sortBy('column2)

但这显然只是按顺序应用排序,最终按 column2 排序,而忽略 column1。我试过了

sortBy('column1', 'column2')

但这会引发错误“asort() 期望参数 2 很长,给定字符串”。使用

sortBy('column1, column2')

不会抛出错误,但排序似乎很随机,所以我真的不知道它实际上做了什么。我查看了the code for the sortBy method,但不幸的是我很难理解它是如何工作的。

【问题讨论】:

    标签: php laravel-4 eloquent


    【解决方案1】:

    sortBy() 采用闭包,允许您提供一个用于排序比较的单个值,但您可以通过将多个属性连接在一起来使其成为一个复合值

    $posts = $posts->sortBy(function($post) {
        return sprintf('%-12s%s', $post->column1, $post->column2);
    });
    

    如果您需要对多个列进行排序,您可能需要对它们进行空格填充以确保“ABC”和“DEF”出现在“AB”和“DEF”之后,因此每个列的 sprint 右填充直到列的长度(至少对于除最后一列之外的所有列)

    请注意,如果您可以在查询中使用 orderBy,则通常效率会更高,以便集合在从数据库检索时准备好排序

    【讨论】:

    • 我相信,如果您将属性名称作为字符串而不是闭包传递,它将调用$this->valueRetriever($callback) 创建一个闭包以使用该属性名称.....当然,该属性名称必须存在
    • @Leith 哪个效率更高?了解 sprintf 函数在做什么也很好。
    • @Jonathan - 我不一定觉得需要解释 什么 documented PHP core function does;但在这种情况下,我已经解释了为什么我使用它 (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))
    • @Jonathan sprintf() 可能稍微更有效率(即,如果您同时运行 10000 次,您可能会看到几微秒的差异)。我评论的原因是,虽然在某些情况下很有用,但我认为 sprintf() 是 C 的后遗症——它的语法不直观,甚至可能使简单的代码更难阅读,除非你非常熟悉它。在这种情况下,作者使用它来填充任何列值到 12 个字符,因此行为与我的稍微不同。您需要进行“自然排序”以避免这种“带空格的排序”黑客攻击。
    • @YevgeniyAfanasyev 这完全取决于你想在回调中加入多少逻辑;我展示的简单回调只能用于简单的 asc 或 desc;你可以让它更复杂,但这是另一个问题....虽然 derekaug 答案中的回调更适合按列执行 asc/desc
    【解决方案2】:

    我在 eloquent Collection 上找到了一种使用 sort() 的不同方法。与填充字段相比,它可能会更好地工作,或者至少更容易理解。我很想看看哪个表现更好,因为这个有更多的比较,但我没有为每个项目都做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);
        }
    );
    

    【讨论】:

    • 在 Laravel 5.3 上运行良好。谢谢!
    • 即使在 5.5 中也可以使用 – 如果您使用的是 PHP 7 并且想要比较数字:我们现在有了新的 spaceship 运算符! $a->id <=> $b->id
    【解决方案3】:

    正如@derekaug 提到的,sort 方法允许我们输入自定义闭包来对集合进行排序。但我认为他的解决方案写起来有点麻烦,如果有这样的东西会很好:

    $collection = collect([/* items */])
    $sort = ["column1" => "asc", "column2" => "desc"];
    $comparer = $makeComparer($sort);
    $collection->sort($comparer);
    

    实际上,这可以通过以下$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;
    };
    

    示例

    $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"],
    * ];
    */
    

    现在,既然我们在这里谈论 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;
                    };
            });
        }
    }
    

    现在您可以在任何需要的地方使用它:

    $criteria = ["id" => "asc"];
    $comparer = $this->app->make("collection.multiSort",$criteria);
    $sorted = $collection->sort($comparer);
    $actual = $sorted->values()->toArray();
    

    【讨论】:

    • 您可以使用 data_get 助手来获得“点符号”的功能。非常有用: if (data_get($first, $key) data_get($second, $key)) { return $orderType === "asc" ? 1:-1; }
    【解决方案4】:

    一个简单的解决方案是按您希望它们排序的相反顺序多次链接 sortBy()。缺点是这可能比在同一个回调中一次排序要慢,因此在大型集合中使用风险自负。

    $collection->sortBy('column3')->sortBy('column2')->sortBy('column1');
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-14
    • 2014-01-16
    • 2013-09-10
    • 2017-04-05
    • 1970-01-01
    相关资源
    最近更新 更多