简体   繁体   中英

Laravel 5.8 Eloquent query without using DB::raw() — get rows based on latest value of many to many relationship

My goal is to select all issues that have been marked as false-positive. The issues are connected to Status via a ManyToMany relationship with pivot table IssuesStatus . The current status of an issue is determined by the value of the status column on the Status table.

I've come up with a solution that works but seems somehow suspect. I'm looking for a way to rewrite the query using Eloquent query builder without relying on the DB::raw() method.

public function getFalsePositivesAttribute() {
    return Issue::where(DB::raw(
        '( select `status` '.
        'from `issues-status` '.
        'left join `status` on `status`.id = `issues-status`.status_id '.
        'where `issues-status`.issue_id = `issues`.id '.
        'order by `issues-status`.id desc limit 1 )'
    ), '=', 'false-positive')->get();
}

Example of desired SQL query:

SELECT
    `Issues`.id
FROM
    `issues` AS `Issues`
LEFT JOIN
    `issues-status` `IssueStatus` on `Issues`.id = `IssueStatus`.issue_id
LEFT JOIN
    `status` as `StatusTable` on `IssueStatus`.status_id = `StatusTable`.id
WHERE
    `Issues`.report_id = 2
AND
    ( 
        SELECT
            `status`
        FROM
            `issues-status` `IssueStatus`
        LEFT JOIN
            `status` `StatusTable` on `StatusTable`.id = `IssueStatus`.status_id
        WHERE
            `IssueStatus`.issue_id = `Issues`.id
        ORDER BY
            `IssueStatus`.id desc
        LIMIT 1
    ) = 'false-positive'
GROUP BY
    `Issues`.id

Models:

class Issue extends Model {
    ...

    public function status() {
        return $this->belongsToMany( Status::class, 'issues-status')
            ->withTimestamps()
            ->withPivot('note');
    }

    ...
} 

class Status extends Model {
    ...

    public function issues() {
        return $this->hasMany(Issue::class);
    }

    ...
}

Tables:

Issues:
  id - identity

Status
  id - identity
  status - string

IssueStatus
  id - identity
  issue_id - relation to Issues
  status_id - relation to Status
  created_at - timestamp
  note - text

If i understood correctly, you want to fetch the issue where the latest status is equals to something.

Add a latestStatus function to your Issues model:

public function latestStatus()
{
    return $this->hasMany(IssueStatus::class)->latest();
}

Now, swap your status function with this one:

public function status() {
    return $this->belongsToMany( Status::class, 'issues-status')
       ->withTimestamps()
       ->withPivot('note');
}

Then:

//Get all issues which has the desired status
Issue::whereHas('status', function($query){
    $query->where('id', $desiredStatusId);
})
->get()
->reject(function($issue){ 
    //filter the collection by the latest issue status,
    // and reject those who does not have the desired latest status;
    return $issue->latestStatus()->first()->status_id != $desiredStatusId;
});

Hope it helps.

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