Laravel - 根据 object 的 属性 关系对 collections 的数组进行排序

Laravel - sorting an array of collections by the property of their relationship object

我有两个 tables magazines 和一个字段 product_code,另一个 table issues。他们有 belongsToMany 关系。

杂志型号:

 public function issues()
 {
     return $this->hasMany('App\Issue');
 }

发行模型:

public function magazine()
{
    return $this->belongsTo('App\Magazine');
}

目前我有一个查询,其中我得到 collections 按杂志 ID 分组并按上一期日期排序的问题。

$issues = Issue::orderBy('date', 'desc')->get()->groupBy('magazine_id');

我的查询结果如下所示:

Collection {#431 ▼
  #items: array:23 [▼
    103 => Collection {#206 ▼
      #items: array:52 [▶]
    }
    106 => Collection {#216 ▶}
    124 => Collection {#452 ▶}
    112 => Collection {#451 ▶}
    115 => Collection {#450 ▶}
    123 => Collection {#449 ▶}
    107 => Collection {#448 ▶}
    113 => Collection {#447 ▶}
    117 => Collection {#446 ▶}
    109 => Collection {#445 ▶}
    110 => Collection {#444 ▶}
    121 => Collection {#443 ▶}
    120 => Collection {#442 ▶}
    114 => Collection {#441 ▶}
    116 => Collection {#440 ▶}
    118 => Collection {#439 ▶}
    126 => Collection {#438 ▶}
    125 => Collection {#437 ▶}
    119 => Collection {#436 ▶}
    122 => Collection {#435 ▶}
    105 => Collection {#434 ▶}
    111 => Collection {#433 ▶}
    104 => Collection {#432 ▶}
  ]
}

所以,由于我有24本杂志,所以数组中有24个collection个issues,每个collectionissues属于一个magazine。 collection 按每个 collection 的最新一期日期排序,每个 collection 内的问题也按日期排序。因此,数组中的第一个 collection 将是 table issues 中的最新一期,第二个 collection 将是第二个最新的一期在同一个 table 等等。

因为我将获得一组用户订阅,其中包括 product codes,如下所示:

$productCodes = ['aa1', 'bb2', 'cc3'];

我需要扩展此查询并根据我将获得的 $productCodes 数组进一步对 collection 进行排序。我需要检查 table magazinesproductCodes 数组的代码,其中我有 product_code 字段。按 magazine 分组的 collection 问题应进行排序,以便第一个 collection 是它们所属的 magazine 具有相同 [=20] 的问题=] 作为数组 productCodes 中的代码,其中第一个将是其 collection 具有最新一期的日期。然后 collection 的其余部分应该按日期排序。如何进行此类查询?

更新

我已经尝试在答案中使用@Paul Spiegel 建议的代码,现在我得到了一个 collection 数组,以及 collection 杂志问题。每个杂志 collection 中的问题按日期排序,与 $productCodes 数组中具有相同 product_code 的杂志 collection 位于数组的开头,但是杂志数组 collections 仍未按每本杂志 collections.

的最新一期日期排序

首先是 with 需要是一个数组,当使用子查询时,不要调用 get() 因为它会为每个 Issue 复制查询给你一个很好的小 N+1 问题。对于 groupBy 只需将 id 作为第二个参数传递以帮助解决 only_full_group_by 错误。

它应该是这样的:

$issues = Issue::groupBy('magazine_id', 'id')
    ->with('magazine')
    ->orderBy('date', 'desc')
    ->get();

这将按日期对您的 Issue collection 进行排序,然后在每个 $issues->magazine 中按 product_code 进行排序,如果您想在一个查询中按 date 到那时 product_code 你需要像这样加入:

$issues = Issue::select('issues.*')
    ->groupBy('magazine_id', 'id')
    ->join('magazine', 'magazine.id', '=', 'issues.magazine_id')
    ->with('magazine')
    ->orderBy('issues.date', 'desc')
    ->orderBy('magazine.product_code', $productCodes)
    ->get();

更新

下面应该给你分组的杂志 collections 按日期排序,每本杂志 collection 按产品代码按照 $productCodes 数组中指定的顺序排序:

$issues = Issue::groupBy('magazine_id', 'id')
    ->with('magazine')
    ->orderBy('date', 'desc')
    ->get()
    ->groupBy('magazine_id')
    ->sortBy(function($issue) use ($productCodes) {
        $index = array_search($issue->first()->magazine->product_id, $productCodes);

        return $index !== false ? $index : $issue->magazine_date;
    });

首先:除了使用 Collection::groupBy() 你可以只使用关系:

$magazines = Magazine::with(['issues' => function($query) {
    $query->orderBy('date', 'desc');
}]);

这将为您提供一系列杂志,其中包括相关问题。 issues 数组按 date desc 排序。

现在您可以使用具有不同条件的两个查询拆分结果:

$baseQuery = Magazine::with(['issues' => function($query) {
    $query->orderBy('date', 'desc');
}]);

$query1 = clone $baseQuery;
$query2 = clone $baseQuery;

$query1->whereIn('product_code', $productCodes);
$query2->whereNotIn('product_code', $productCodes);

$query1 将从数组中 return 所有带有 product_code 的杂志。 $query2 将 return 所有其他杂志。

您现在有两种方法可以合并这两个结果:

1) 使用Eloquent::unionAll()

$unionQuery = $query1->unionAll($query2);
$magazines = $unionQuery->get();

2) 使用Collection::merge()

$magazines1 = $query1->get();
$magazines2 = $query2->get();
$magazines = $magazines1->merge($magazines2);

在这两种情况下,您会得到相同的结果:杂志合集。 $productCodes 数组中带有 product_code 的杂志排在最前面。每个 Magazine 对象都包含一个数组 issues 以及相关的 Issue 个对象。

如果你想摆脱 Magazine 对象并且确实需要结果看起来像你的问题,你仍然可以这样做:

$issues = $magazines->keyBy('id')->pluck('issue');

但可能没有必要。

更新

如果你真的需要 Magazine 集合的两个部分按最新的 Issue 排序。我看不出有什么办法不使用 JOIN 或在 [=64 中排序=] 关闭。使用连接对杂志进行排序也需要聚合。所以我会切换回你原来的查询,用一个连接扩展它,并将它分成两部分,如上所示:

$baseQuery = Issue::join('magazines', 'magazines.id', '=', 'issues.magazine_id');
$baseQuery->orderBy('issues.date', 'desc');
$baseQuery->select('issues.*');

$query1 = clone $baseQuery;
$query2 = clone $baseQuery;

$query1->whereIn('magazines.product_code', $productCodes);
$query2->whereNotIn('magazines.product_code', $productCodes);

然后

$issues1 = $query1->get()->groupBy('magazine_id');
$issues2 = $query2->get()->groupBy('magazine_id');

$issues = $issues1->union($issues2);

$issues = $query1->unionAll($query2)->get()->groupBy('optionGroupId');

先上代码,最后一步步解释:

Magazine::all()
    ->load('issues')
    ->keyBy('id')
    ->sort(function ($a, $b) use ($product_codes) {
        return
            (in_array($b->product_code, $product_codes) - in_array($a->product_code, $product_codes))
            ?:
            (strtotime($b->latestIssueDate) - strtotime($a->latestIssueDate));
    })
    ->map(function ($m) {
        return $m->issues->sortByDesc('created_at');
    });

这让你:

  • 所有杂志
  • 急切加载他们的每个问题
  • 在一个集合中,其中的键是每个杂志 id
  • 按 2 个标准排序:首先 product_code 是否在请求的代码中,然后是最新一期的日期 (*)
  • 最后,对于每本杂志,您都需要它的各期合集,按日期降序排列。

如果我正确理解了您的要求,这将满足您的需求。

(假设日期列称为 created_at。)


(*) 为方便起见,我使用的是属性访问器:

public function getLatestIssueDateAttribute()
{
    // this breaks for Magazines with no Issues. Fix if necessary
    return $this->issues->sortByDesc('created_at')->first()->created_at;
}