I have two tables with relation one to many. My task is quite simple, I need for each parent last item from relation table where is applying condition.
First table is status:
| id | partner_id | status | created_at |
|----|------------|--------|---------------------|
| 1 | 1 | 1 | 2018-01-01 00:00:00 |
| 2 | 1 | 5 | 2018-02-01 00:00:00 |
| 3 | 2 | 1 | 2018-01-01 00:00:00 |
Relation is
public function partner()
{
return $this->belongsTo(Partner::class, 'partner_id');
}
Second is partners:
| id | name |
|----|-----------|
| 1 | partner_1 |
| 2 | partner_2 |
Relation is:
public function status()
{
return $this->hasMany(Status::class, 'partner_id')->orderBy('id', 'desc');
}
Let say I want to get all partners where they last status is 1. I tried this:
Partner::with('status')
->whereHas('status', function ($query) use ($status) {
$query->where('status', $status);
})
->get();
The result is this:
"select * from `partners` where exists (select * from `status` where `partners`.`id` = `status`.`partner_id` and `status` = 1)"
But with it I will get also partner_1 even if his last status is 5 but in history he had status 1.
I created sql query to get this but I think that is quite complicated to make such a simple task. And also I think there must be some easy Laravel approach:
select p.id, ps.status, ps.newest_status
from partners as p
INNER JOIN (SELECT a.created_at AS newest_status, a.partner_id, a.status
FROM status as a
INNER JOIN (
SELECT max(created_at) as newest_status, partner_id
FROM status
GROUP BY partner_id
) as b ON a.partner_id = b.partner_id AND a.created_at = b.newest_status
) ps ON p.id = ps.partner_id and ps.status = 1
GROUP BY p.id
My goal is to get only partner_2 because only he has last status 1.
One way to do it is to customize the relationship method to include some ordering (as suggested by @yrv16)
But the way to go is subqueries if you want efficiency. A very good article by J. Reinink explains that nicely.
In short, something like that:
class Partner extends Model {
// ...
public function scopeWithLastStatus($query)
{
$query->addSubSelect('last_status', Status::select('status')->latest());
}
// ...
This will add the 'virtual' attribute last_status
to your model.
Then you can do something like that:
Partner::withLastStatus()->get()->where('last_status', 'idle');
You can use this:
Partner::select('partners.*')
->join('status', 'partners.id', 'status.partner_id')
->where('status.status', $status)
->where('status.id', function($query) {
$query->select('id')
->from('status')
->whereColumn('partner_id', 'partners.id')
->orderByDesc('id')
->limit(1);
})->get();
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.