Laravel / Eloquent: nested WhereHas - php

I've just started learning Laravel and stumbled upon one issue which I can't make work using the Eloquent relationships.
Let's assume that I have a Worker model, a Skill model and a pivot table skills_workers to keep the many-to-many relationship.
When I'm trying to get all the workers, who have following skill, then it goes without a problem using the following syntax:
$skill='carpenter';
$workers=Worker::whereHas('skills', function (Builder $query) use($skill){
$query->where('name','=',$skill);
})->get()();
The problem begins when I need to find all workers who have the set of skills. For example, carpenter-driver-chef (just for example). If the worker should have one of the skills, then I'd just use the whereIn function, but I need to find the worker who posess all of the skills in array.
I can't make the nested WhereHas as every time the user performs a search the skill set might be different. Sometimes it's just 1 skill, sometimes 2 and so on.
So the following construction:
$skills=['carpenter','driver'];
$workers=Worker::whereHas('skills', function (Builder $query) use($skills){
$query->where('name','=',$skills[0]);
})->whereHas('skills', function (Builder $query) use($skills){
$query->where('name','=',$skills[1]);
})->get();
is not an option.
Is there a way to use whereHas inside of a foreach loop, for example? Or, maybe, there is a more elegant way of performing such queries? None of the other posts on StackOverflow that I've found, helped...
I'd really like to avoid using the raw SQL queries, if possible.
Thank you in advance

As your $skills variable appears to be an array, you could use the Eloquent whereIn function.
$workers = Worker::whereHas('skills', function (Builder $query) use ($skills) {
$query->whereIn('name', $skills);
})->get();
Update
The following should get you a collection of Workers that have all the Skills.
$workers = Worker::whereHas('skills');
foreach ($skills as $skill) {
$workers->whereHas('skills', function (Builder $query) use ($skill) {
$query->where('name', $skill);
})->get();
}
$workers->get();

I think you can use foreach for skills to get multiple matching condition
$workers=Worker::whereHas('skills', function (Builder $query) use($skills){
foreach($skills as $value){
$query->where('name',$value);
}
})->get();

You can start with getting all skills and after that you can use whereIn like this
$skills=['carpenter','driver'];
$skills_id = Skill::whereIn(['name',$skills])->pluck('id');
By using pluck the query will return an array of IDs [1,3,...] not model.
$workers = Worker::whereHas('skills', function(Builder $query) use ($skills_id) {
$query->whereIn('id', $skills_id);
})->get();

Related

Query Laravel Eloquent many to many where all id's are equal

I making a project based on Laravel and have the tables: companies, attributes, and attribute_company related as Many To Many relation when attribute_company use as a pivot table to connect companies and attributes tables.
I get an array of attribute_id's from the client and I need to get results of the companies that has the whole attributes exactly.
The only solution I found is to query whereHas combined with whereIn inside like this:
Company::whereHas('attributes', function (Builder $query) use ($atts_ids) {
$query->whereIn('attribute_id', $atts_ids);
})->get();
This query will return companies if at least one attribute_id found (which is not what I am looking for).
It would be great if anybody can make it clearer for me.
Thank you all in advance :)
One possible solution:
$company = new Company();
$company = $company->newQuery();
foreach($atts_ids as $att_id)
{
$company = $company->whereHas('attributes', function (Builder $query) use ($att_id) {
$query->where('attribute_id', $att_id);
});
}
$company = $company->get();

Laravel Eloquent, multiple schema query

I have the below query (simplified):
$q = ModelOne::with('relation_one', 'relation_two')
->whereHas('relation_three', function ($q) {
$q->where('object', 'Obj1');
})
->whereHas('relation_four', function ($q) {
$q->where('object', 'Obj2');
})
->get();`
It loads the relation_one and relation_two relationships fine, I also need to load another relationship per row, either relation_three or relation_four depending on the value of ModelOne->object.
The issue I am having is that ModelOne is from schema1 and the tables used in relation_three & relation_four are from schema2.
Both models are set up correct with their individual protected $connection and protected $table variables.
The error I am recieving is that the tables for relationship_three or relationship_four does not exist as the sub-query is checking the wrong schema.
Can anyone suggest how to fix this? Have had a look through the docs but couldn't find a solution.
Maybe not the most elegant solution but got this working by calling relationships and joining as follows:
$q = ModelOne::with('relation_one', 'relation_two')
->with(['relation_three' => function ($q) {
$q->leftJoin(
'schema1.model_one',
'table_three.id',
'=',
'model_one.object_id'
)
->where('object', 'Obj1');
}])
->with(['relation_four' => function ($q) {
$q->leftJoin(
'schema1.model_one',
'table_four.id',
'=',
'model_one.object_id'
)
->where('object', 'Obj2');
}])
->get();`
If anyone can suggest some improvements or a more efficient way to do this please let me know
I would suggest separating the different databases relations to different fields, at least. This way you can then load both (as suggested in comments) and differentiate the logic within controller/model code.
Also, I guess you'll need to define the connection name on the Model level, if not done yet:
class Model_Two_Relation {
protected $connection = 'your-database-name-from-config';
}
You also might want to specify the connection within the relation join condition:
$q = ModelOne::with('relation_one', 'relation_two')
->whereHas('relation_three', function ($q) {
$q->from('resources.one')->where('object', 'Obj1');
})
->whereHas('relation_four', function ($q) {
$q->from('resources.two')->where('object', 'Obj2');
})
->get();
Links: http://fideloper.com/laravel-multiple-database-connections

Match array of ids from pivot table

Scenario
I have 3 main tables Employees,Jobs,Skills. Employees and Jobs has many-to-many relationship with Skills table.
So, an employee can have skills 1,2,3,5. A job may requires skills 1,3,5.
Now my question is how can I match the id's in an eloquent query. Like, if I want to search all the employees for a job requiring skills 1,3,5, it should search all the employees having all those skills(1,3,5)
You said you have an array of IDs, so use multiple whereHas():
$employees = Employee::JobLocations($jobZipId);
foreach ($skillIds as $id) {
$skilledEmployees = $emloyees->whereHas('skills', function ($q) use ($id) {
$q->where('id', $id);
});
}
$skilledEmployees = $skilledEmployees->get();
You can do it like, I am taking $job as already a defined object.
Employee::whereHas('skills', function($q) use($job) {
$q->whereIn('id', $job->skills->lists('id')->toArray());
})->get();
Update
In case of Laravel 5.3 and up, the method lists won't work (as its is removed) so you can also use pluck method instead of lists.
Hope this helps you to solve your problem.

Laravel - Using a nested relationship with the query builder

I am working on an API but its starting to get a bit slow now that the data is increasing. I am moving some of the queries so that they use the DB query builder.
I have my last one which has a nested query:
$artists = Artist::with('performances', 'performances.stage')->get();
I have got so far:
$artists = \DB::table('artists')
->leftJoin('performances', 'artists.id', '=', 'performances.artist_id')
->get();
But now need to do the second relationship which in the Performance model is:
public function stage()
{
return $this->hasOne('App\Models\Stage', 'id', 'stage_id');
}
Any help on how I do this?
yes you can use eloquent relationship with query builder like this
$artists = Artist::join('performances', 'artists.id', '=', 'performances.artist_id')
->all();
foreach($artists as $artist){
$data = $artist->stage()->first();
}
It is very well covered in the official documentation, please, refer to this section of Documentation
I think that you want to achieve something like this:
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
And also, please, read carefully this section

Merge 'with' and 'whereHas' in Laravel 5

I have this code in Laravel 5, using Eloquent, which is working perfectly:
$filterTask = function($query) use ($id) {
$query->where('taskid', $id);
};
User::whereHas('submissions', $filterTask)->with(['submissions' => $filterTask])->get();
Basically the goal is to get only those users with their filtered submissions, which has any of them.
However, it seems wasting to run both whereHas and with methods with the same callback function. Is there a way to simplify it?
Thanks.
In terms of performance you can't really optimize anything here (except if you were to move from eloquent relations to joins). With or without whereHas, two queries will be run. One to select all users another one to load the related models. When you add the whereHas condition a subquery is added, but it's still two queries.
However, syntactically you could optimize this a bit by adding a query scope to your model (or even a base model if you want to use this more often):
public function scopeWithAndWhereHas($query, $relation, $constraint){
return $query->whereHas($relation, $constraint)
->with([$relation => $constraint]);
}
Usage:
User::withAndWhereHas('submissions', function($query) use ($id){
$query->where('taskid', $id);
})->get();
The 'macroable' way (Laravel 5.4+)
Add this inside a service provider's boot() method.
\Illuminate\Database\Eloquent\Builder\Eloquent::macro('withAndWhereHas', function($relation, $constraint){
return $this->whereHas($relation, $constraint)->with([$relation => $constraint]);
});
I want to extend the answer from #lukasgeiter using static functions.
public static function withAndWhereHas($relation, $constraint){
return (new static)->whereHas($relation, $constraint)
->with([$relation => $constraint]);
}
Usage is the same
User::withAndWhereHas('submissions', function($query) use ($id){
$query->where('taskid', $id);
})->get();
You can now achieve that In Laravel 9.17
Example:
use App\Models\User;
$users = User::withWhereHas('posts', function ($query) {
$query->where('featured', true);
})->get();
Check out the documentation for more information

Categories