Laravel - 按关系对象的属性对集合进行排序
我有两个表magazines
,其中一个字段为product_code
,另一个表为issues
。他们有belongsToMany
的关系。Laravel - 按关系对象的属性对集合进行排序
杂志模特:
public function issues()
{
return $this->hasMany('App\Issue');
}
发行模式:
public function magazine()
{
return $this->belongsTo('App\Magazine');
}
目前,我有一个查询从哪里获得的由杂志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本杂志,还有数组中的issues
24个集,每collection
的issues
属于一个magazine
。集合按每个集合的最新发布日期排序,并且每个集合中的问题也按日期排序。因此,阵列中的第一个集合将是表issues
中具有最新问题的集合,第二个集合将是具有同一表中的第二个最新问题的集合,依此类推。
因为我会得到用户订阅的阵列,其中将包括product codes
这样的:
$productCodes = ['aa1', 'bb2', 'cc3'];
我需要扩大这个查询并通过$productCodes
阵列,我将获得集合进一步梳理。我需要检查magazines
表中productCodes
阵列的代码,我在那里有product_code
字段。应按magazine
分组的问题集合进行排序,以便第一个集合是它们所属的magazine
与productCodes
中的代码具有相同product_code
的集合,其中第一个集合的集合具有迄今为止的最新一期。然后其余的收藏应该按日期排序。我怎样才能做这种查询?
更新
我曾尝试与@保罗周刊的答案中建议的代码,现在我得到集合数组,与杂志的问题集合。每个杂志集合中的问题按日期排序,与$productCodes
阵列具有相同product_code
的杂志集合位于该数组的开头,但杂志集合的排列依然未按照最新版本的日期排序每个杂志集合。
首先:如果不使用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
将product_code
返回所有杂志从阵列中。 $query2
将返回所有其他杂志。
你现在两种方法的两个结果结合起来:
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或PHP与闭合排序任何方式。用连接对杂志排序也需要聚合。
$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');
的第一件事是with
需求是一个数组,并使用子查询时,不叫get()
因为它会复制查询每个Issue
给你一个可爱的小N + 1点的问题。对于groupBy
只需将id
作为第二个参数来帮助解决only_full_group_by错误。
它应该是这样的:
$issues = Issue::groupBy('magazine_id', 'id')
->with('magazine')
->orderBy('date', 'desc')
->get();
,如果你想通过date
订购此一个查询,然后再将由日期顺序您Issue
集合,然后在每个$issues->magazine
将product_code
订购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();
UPDATE
下应该给你按日期每本杂志收集由产品代码在$ 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;
});
代码第一:那么作为显示上述,我将与加入扩展它切换回原来的查询,并把它分解为两个部分,最后一步一步解释:
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;
}
这将执行每个杂志一个查询,对吧? –
@PaulSpiegel否,'load'方法将在1个查询中获取所有相关模型。所以2查询总数 – alepeino
我的意思是'getLatestIssueDateAttribute()'方法。 –
'问题:: GROUPBY( 'magazine_id' ) - > orderBy('date','desc') - > orderBy('product_codes','desc') - > get()'。按照您的意愿可以多次调用顺序。 – Andrew
但问题是'product_code'不是'问题'表中的字段,它在关系表'杂志' – Marco
啊,我明白了。我的错。那么:'issues :: groupBy('magazine_id') - > with('magazine'=> function($ q){$ q-> orderBy('product_codes','desc') - > get();}) - > orderBy('date','desc') - > get();'。至少可以说,我的疯狂技能有点生疏,但我认为这应该起作用。 – Andrew