简体   繁体   中英

Get last from relation where is condition

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM