Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

patrykszady's avatar

whereHas latest child equals a value

Hi all,

Besides my problem, I'm really struggling with queries / Eloquent ... any pointes to good tutorials? (summing ->groupBy , etc).

I have a Project(hasMany) and Projectstatus(belongsTo) models. I'm trying to get all Projects where the latest('created_at') Projectstatus('status_id') for this/these Projects is 5:

This is what I have so far:

$projects = Project::whereHas('projectstatuses', function ($query) {
    $query->where('status_id', 5);
        })->get();

This returns all projects where status_id on the projectstatus table = 5, but a Project can have a few statuses throughout it's cycle. I only need this to get projects where the latest('created_at) title_id = 5.

Something like latest()->where('status_id', 5)->first() ? Where would I even put that?

After I figure this out, would this end up in the Model so I can call something like Project::active() to get all active projects?

Thanks so much@! -Patryk

0 likes
15 replies
Bandab's avatar
Bandab
Best Answer
Level 1

Hi @patrykszady,

You could try something like this:

$projects = Project::whereHas('projectstatuses', function ($query) {
    $query->latest()->where('status_id', 5);
})->get();

Then, if you would like to use it as a scope:

public function scopeActive(){
    return $query->whereHas('projectstatuses', function ($q) {
         $q->latest()->where('status_id', 5);
    });
}

Hope it helps.

Kind regards, Darek

1 like
patrykszady's avatar

@Bandab Thanks Darek --- I thought I tried every $query->where/latest last night. This works great and is very clean. Exactly what I was hoping for.

1 like
patrykszady's avatar

@Bandab Actually, I jumped the gun because that still gives me all Projects as long as they have status_id of 5, it doesn't matter if status_id 6 or 7 or 8 was created after the status_id 5.

It shows as active, even if the project is in another state.

patrykszady's avatar

So I can get what I'm looking for with this

public function latestStatus(){
        return $this->hasOne('App\Projectstatus')->latest()->limit(1)->first()->title_id;
    } 

This returns 4,5,6, etc. and then I can easily use this, however how would I make this work in a scope?

patrykszady's avatar

Wow, this was difficult for me.

On my Project Model

    public function scopeActive($query){
        return $query->with('latestStatus')->get()->where('latestStatus.title_id', 5);
    }
    public function latestStatus(){
        return $this->hasOne('App\Projectstatus')->latest();
    }
2 likes
scheMeZa's avatar

@PATRYKSZADY - This should be considered best answer. This is the best answer I could find thus far that touches this topic, even though it uses the where function of eloquent.

Zalo's avatar

It doesnt work! When you use HasOne(...)->latest() you only are adding an orderBy() sql but it still will consider all related records, so, resulted sql will not be limited to ONLY the last record... If you use a '<=' comparison, this query will still give you all records instead of searching for ONLY the last one... Some one know how to face it with query builder? I think its not possible and you have to use DB::raw() thing.

MelanieD's avatar

I'm having the exact same issue right now, will let you know if I come across anything. It seems like the problem is not being able to limit 1 on orderBy()

kondorb's avatar

I know my answer will be a bit late to the party, but I hope it can save someone a bit of hair on their head, since it took me a good long evening to figure it out when I faced the same problem.

What we want to achieve here is not possible with an Eloquent builder only, we have to go a level deeper - to a DB query builder and just a touch of raw SQL.

Basically, when we say whereHas('projectstatuses', function...) we're creating an exists subquery. This subquery will be executed for each of the elements of the result of the main query. A result of the main query will be returned to us only if the result of its subquery is not empty. Hope it makes sense.

So, we want to write a subquery that will return something only when the latest projectstatus has a status_id we are asking.

Now, where('status_id, 5) is pretty obvious. We want to add another where clause to work with the latest entry related to the project in question. To get one specific entry out of a table we usually use one of the aggregate SQL functions, like max(), avg() or count(). We can go for the latest entry by incorporating max(created_at) for example. But I would suggest going with max(id) since it is basically the same by the logic of the table we're working with and has zero likelihood of ever being non-unique. Also, you'll see why it makes more sense in a second.

So, here's how you get an ID of the latest project status for a given project for example:

DB::table('projectstatuses')
->selectRaw('max(id)')
->where('project_id', $project->id)
->get();

Last thing we need to know is, while it isn't 100% obvious from the documentation, we can pass a closure to other where statements the same way we do it for whereHas, which will create the same sort of subquery. So, since we now know how to get the id of the latest status of the project, lets use a whereIn('id', ...) statement like so:

->whereIn('id', function (QueryBuilder $query) {
    $query
        ->selectRaw('max(id)')
        ->from('projectstatuses')
        ->whereColumn('project_id', 'projects.id');
});

Note, that we're using whereColumn because we're now comparing a column to another column, not to a value.

Finally, let's combine all of that into a single query:

use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as QueryBuilder;

...

Project::whereHas('projectstatuses', function (EloquentBuilder $query) {
    $query
        ->where('status_id', 5)
        ->whereIn('id', function (QueryBuilder $query) {
            $query
                ->selectRaw('max(id)')
                ->from('projectstatuses')
                ->whereColumn('project_id', 'projects.id');
        });
})->get();

How to make it into a scope is trivial from here.

Hope my explanation wasn't too convoluted. I want you to actually understand the direction of thought that brings you to solving this sort of problems.

Bonus:

Try changing ->get() to ->dd(). This dumps a full query how it will be sent to a DB with all the bindings. Very helpful to actually understand what Laravel is trying to do behind the scenes. Laravel can be a bit unintuitive sometimes.

9 likes
scibuff's avatar

I seriously cannot believe that there isn't a way to do this with the query builder without using raw sql! What's the point of all those abstractions if they cannot generate a relatively simple query.

BTW, if your Project model, you can do

public function status(){
    return $this->hasOne(ProjectStatus::class)->latest('id');
}

and $project->status->status_id will give you the latest status for that project instance.

Now, back to SQL. I've tested the following raw SQL and it works

SELECT * 
FROM projects AS p
LEFT JOIN project_statuses AS ps
ON ps.id = ( SELECT ps1.id FROM project_statuses AS ps1 WHERE p.id = p1.project_id ORDER BY p1.id DESC LIMIT 1 )
WHERE ps.status = 5;

I see that there's joinSub in Laravel now but I couldn't get it to generate anything close to this. No joins I see can do the subquery in the on clause.

My solution in an identical situation now is to add a status column to the project table. I use a ProjectRepository and ProjectStatusRepository to handle all Model interactions with the application there, i.e. I'll be just updating the projects.status column as well as creating a new project_statuses entry

thdebay's avatar

Just wanted to thank you @kondorb for your answer. Even years after, this thread has been very useful for me since I couldn't find an answer anywhere else.

Perhaps it would be helpful if @patrykszady could select your answer as best answer, to save time to other people looking for a working solution to this problem.

NabilAnam's avatar

@kondorb Thank you very much! I needed to compare data for first relationship and couldn't figure out how to do this in Laravel. Again, thank you very much!

pravnkay's avatar

I maybe a noob here, but after days of shoveling I am unable to find a solution so posting here.

I am in a similar situation.

I have SaleInvoice model and Payment model under a Ploymorphic relationship of One SaleInvoice HasMany Payments & Each Payment BelongsTo A SaleInvoice.

As multiple payments are made, the last polymorph model of Payment is marked as closed and till then it is as due in field, payment_status in the Payment model itself.

Now, I need to fetch the SaleInvoices where the latest Payment's payment_status is not closed. (I am saying 'not closed ' and not 'as due' because there are other payment_status like overdue)

From the answer of @kondorb , I was able to do this,

	use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
	use Illuminate\Database\Query\Builder as QueryBuilder;
	...
	$sales = SaleInvoice::whereHas('payments', function (EloquentBuilder $query) {
		$query
			->where('payment_status',  '!=',  'closed')
			->whereIn('id', function (QueryBuilder $query) {
				$query
					->selectRaw('max(id)')
					->from('payments')
					->whereColumn('payable_id',  'sale_invoices.id');
			});
	});   

The problem is I use UUID for id fields and max('id') is not working and max('created_at') also doesn't seem to have any effect.

	SELECT * FROM ph_sale_invoices AS sv	
	LEFT JOIN ph_payments AS ps	
	ON ps.id = ( 
						SELECT ps1.id FROM ph_payments AS ps1 
									WHERE sv.id = ps1.payable_id 
									ORDER BY ps1.created_at 
									DESC LIMIT 1 
				) 
	WHERE ps.payment_status = 'closed';

As quoted by @scibuff , the above raw query seems to work.

I have no clue what is wrong here. Any help is much appreciated.

healyhatman's avatar

For me I created a HasOne relationship with ->latestOfMany() and it worked for a distant relation:

->whereHas('latestRelationshipOfMany.middleRelationship.endRelationship', fn($query) => $query->where('end_relationship_table.column_i_want', $value_i_want)

Please or to participate in this conversation.