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
,每个collection
个issues
属于一个magazine
。 collection 按每个 collection 的最新一期日期排序,每个 collection 内的问题也按日期排序。因此,数组中的第一个 collection 将是 table issues
中的最新一期,第二个 collection 将是第二个最新的一期在同一个 table 等等。
因为我将获得一组用户订阅,其中包括 product codes
,如下所示:
$productCodes = ['aa1', 'bb2', 'cc3'];
我需要扩展此查询并根据我将获得的 $productCodes
数组进一步对 collection 进行排序。我需要检查 table magazines
中 productCodes
数组的代码,其中我有 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;
}
我有两个 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
,每个collection
个issues
属于一个magazine
。 collection 按每个 collection 的最新一期日期排序,每个 collection 内的问题也按日期排序。因此,数组中的第一个 collection 将是 table issues
中的最新一期,第二个 collection 将是第二个最新的一期在同一个 table 等等。
因为我将获得一组用户订阅,其中包括 product codes
,如下所示:
$productCodes = ['aa1', 'bb2', 'cc3'];
我需要扩展此查询并根据我将获得的 $productCodes
数组进一步对 collection 进行排序。我需要检查 table magazines
中 productCodes
数组的代码,其中我有 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;
}