Laravel: Difference between where and whereHas - laravel

What is the difference between the two methods where() and whereHas()? They both seem to be quite similar in the laravel documentation.

The method where() behaves like a regular SQL WHERE query part.
The method has() uses foreign key relationships to return something which has something else. For instance, Student::has('classes')->get(); would return all students who have classes.
The method whereHas() is like a regular has(), but it lets you put constraints on the search. Unlike where(), this is done on the child table rather than the parent one. Here's an example:
$students = Student::where('name', 'Pingu') // constrains the students table
->whereHas('classes', function($query) {
$query->where('name', 'like', '%physics%'); // constrains the classes table
})->get();
In this highly realistic example, you'd want all students with a name of Pingu, who are taking some class in physics.

Related

laravel withsum with additional where clause

I know you can do a withSum on a relationship like this:
$posts = Post::withSum('comments', 'votes')->get();
But I like to chain an additional where clause on this, something like this (doesn't work but as an example):
$posts = Post::withSum('comments', 'votes', function (Builder $query) {
$query->where('comments.votes', '>', 5);
})->get()
Is that possible?
While Aggregating Related Models is not explicit about this in the documentation, it does give us a clue by mentioning:
If you need to set additional query constraints on the count query, you may pass an array keyed by the relationships you wish to count. The array values should be closures which receive the query builder instance.
If you need to set additional query constraints on the count query, you can pass an encoded array by the relationships you want to count. The values in the array must be anonymous functions that receive the query builder instance.
So, we can take advantage of that anonymous function to modify the query builder instance so that instead of taking the default values (which would use the aggregate 'count'), and pass it the subquery we want to do, which is the where clause and the 'sum' of the column.
Said that, your query cuold looks like:
Post::withCount(['comments as comments_sum_votes' => function($query) {
$query->where('comments.votes', '>', 5)->select(DB::raw('sum(votes)'));
}])->get()

Accessing parent properties inside relationship query

An example:
$passing_students = App\Exam::whereHas('students', function ($query) {
$query->where('mark', '>=', $exam->pass_mark);
})->get();
I am interested in fetching all exams with students who passed however in the relationship query function I'm not sure how to access the parent model's properties such that I can complete the comparison. What should be in place of $exam->pass_mark?
Note that I'm looking for a solution that is done within the single query builder as I'm aware that this can be easily done in a separate foreach loop.
Probably instead of:
$query->where('mark', '>=', $exam->pass_mark);
you should use here:
$query->whereColumn('mark', '>=', 'exams.pass_mark');
Above exams.pass_mark is name of Exam model table (I assumed you use exams and column name from this table.

Laravel Eloquent - How Do You Add a Where Condition to a Distantly Related Model Using the ORM?

I currently have the following:
Cars::with('cases')->with(['parts', 'parts.engines', 'parts.engines.metals'])
->orderBy('car_name', 'DESC')->orderBy('id', 'DESC');
The above will list all rows in my cars table along with the metal associated with the engine in each of those cars. The metals table is related to the cars table through the parts and then the engines tables.
I've tried using:
Cars::with('cases')->whereHas(['parts', 'parts.engines', 'parts.engines.metals'], function($query){
$query->where('weight', '=', 45)
})->orderBy('car_name', 'DESC')->orderBy('id', 'DESC');
But this errors out since whereHas() does not accept an array for its first parameter and I don't see a way to link to distant relationships with it.
How do I apply a WHERE conditional on a column in the metals table using the built-in ORM?
whereHas() only needs the name of the relationship for which you'd like to add the conditions. So, if you're trying to add the condition to the metals, you just need to restrict the parts.engines.metals relationship.
On a side note, when you eager load nested relationships, you don't need to also specify to load the intermediate relationship. That is, when you eager load parts.engines, you don't need to also eager load parts.
So, your query would look something like:
Cars::with(['cases', 'parts.engines.metals'])
->whereHas('parts.engines.metals', function($query) {
$query->where('weight', '=', 45)
})
->orderBy('car_name', 'DESC')
->orderBy('id', 'DESC');
This query will only retrieve cars that have a related metal with a weight of 45. Additionally, for those cars that are retrieved, it will also eager load all of the cases, parts, engines, and metals related to those cars.
I think you mean this:
Cars::with(['cases', 'parts', 'parts.engines', 'parts.engines.metals' => function($query){
$query->where('weight', '=', 45);
}])->orderBy('car_name', 'DESC')->orderBy('id', 'DESC');

Eloquent 'with()' just 1 record?

I have a many-to-one relation between two tables, but how do you use the 'with()' method to bring in just one associated record, rather than all of them? (that whose date is nearest the current date, for example)?
You can use a closure on the with() method to query the relation.
$users->with(['address' => function($q) use ($someVariable) {
$q->where('zip', '=', $someVariable);
});
There is a ->first() method. Which will give you the first result only. This replaces ->get().
https://laravel.com/docs/5.3/eloquent#retrieving-single-models
You can use a with() method in following way.
$user->with(['address'])->findWhere(['zip'=>$someVariable]);

Laravel whereDoesntHave() - multiple OR conditions

In Laravel 4.2 I have a model called Product with many-to-many relationshis to other models like Country or Category. I want to filter out products that are "incomplete", which means they have no connected countries or no connected categories. I can use whereDoesntHave() method to filter out one relation. When I use it two times in one query it creates AND condition, but I need OR. I can't find orWhereDoesntHave() method in API documentation. I can't pass multiple relations as arguments because it expects first argument to be a string.
I need something like this:
$products = Product::whereDoesntHave('categories')->orWhereDoesntHave('countries')->get();
Is there any way to achive whereDoesntHave() with multiple OR conditions?
You can use doesntHave and specify the boolean operator:
$products = Product::doesntHave('categories')->doesntHave('countries', 'or')->get();
Actually you only need whereDoesntHave if you want to pass in a closure to filter the related models before checking if any of them exist. In case you want to do that you can pass the closure as third argument:
$products = Product::doesntHave('categories', 'or', function($q){
$q->where('active', false);
})->doesntHave('countries', 'or')->get();
Since Laravel 5.5 there is an orWhereDoesntHave function.
You may use it like this
Product::whereDoesntHave('categories', function($q){ //... })
->orWhereDoesntHave('countries', function($q){//...})
->get();
From you example it seems that you are not using a where clause, so you may just use
Product::doesntHave('categories')
->orDoesntHave('countries')
->get();
Use
Product::whereDoesntHave('categories')->doesntHave('countries', 'or')->get();
Laravel Source Code:
whereDoesntHave https://github.com/illuminate/database/blob/master/Eloquent/Builder.php#L654
calls
https://github.com/illuminate/database/blob/master/Eloquent/Builder.php#L628
internally.
Let’s say we have Authors and Books, with 1-n relationship – one Author can have one or many Books. Here’s how it looks in app\Author.php:
public function books()
{
return $this->hasMany(\App\Book::class, 'author_id');
}
Now, what if we want to show only those Authors that have at least one book? Simple, there’s method has():
$authors = Author::has('books')->get();
Similarly, there’s an opposite method – what if we want to query only the authors without any books? Use doesnthave():
$authors = Author::doesnthave('books')->get();
It’s not only convenient, but also super-easy to read and understand, even if you’re not a Laravel developer, right?

Resources