Where clause inside whereHas being ignored in Eloquent - laravel

Im trying to make a query using whereHas with eloquent. The query is like this:
$projects = Project::whereHas('investments', function($q) {
$q->where('status','=','paid');
})
->with('investments')
->get();
Im using Laravel 5.2 using a Postgres driver.
The Project model is:
public function investments()
{
return $this->hasMany('App\Investment');
}
The investments model has:
public function project() {
return $this->belongsTo('App\Project');
}
The projects table has fields id,fields...
The investments table has the fields id,project_id,status,created_at
My issue is that the query runs and returns a collection of the projects which have at least one investment, however the where clause inside the whereHas is ignored, because the resulting collection includes investments with status values different than paid.
Does anyone has any idea of what is going on?

I believe this is what you need
$projects = Project::whereHas('investments', function($q) {
$q->where('status','=','paid');
})->with(['investments' => function($q) {
$q->where('status','=','paid');
}])->get();
whereHas wil check all projects that have paid investments, with will eagerload all those investments.

You're confusing whereHas and with.
The with method will let you load the relationship only if the query returns true.
The whereHas method will let you get only the models which have the relationship which returns true to the query.
So you need to only use with and not mix with with whereHas:
$projects = Project::with(['investments' =>
function($query){ $query->where('status','=','paid'); }])
->get();

Try like this:
$projects = Project::with('investments')->whereHas('investments', function($q) {
$q->where('status','like','paid'); //strings are compared with wildcards.
})
->get();

Change the order. Use with() before the whereHas(). I had a similar problem few weeks ago. Btw, is the only real difference between the problem and the functional example that you made.

Related

Laravel 7 Query with() and using Where()

Hey guys I have a query that looks like this
$query = Transaction::with(['customer', 'merchant', 'batch'])
->select(sprintf('%s.*', (new Transaction)->table));
I need to filter the transaction based on the iso_id that belons to the current user logged in.
$query = Transaction::with(['customer', 'merchant', 'batch'])
->select(sprintf('%s.*', (new Transaction)->table))
->where('merchant.iso_id', '=', auth()->user()->isIso());
The iso_id I need to compare to, is inside the merchant table
auth()->user()->isIso() returns the correct iso_id if true or sends false if not
So my first try at this was to use where('merchant.iso_id', '=', auth()->user()->isIso())
But that returns that the column does not exist because for some reason, it's not switching from the transaction model to the merchant one.
I am not sure how to use the stuff inside with() as a selector for my where()
Any help would be appreciated!
Try using whereHas to add the constraint:
$query = Transaction::with(['customer', 'batch'])
->whereHas('merchant', function ($q) {
$q->where('iso_id', auth()->user()->isIso());
})
->select(sprintf('%s.*', (new Transaction)->table))
->get();

Refactor whereHas to use via global scopes in Laravel

I've 2 models, CoreChallenge and Challenge. Below are relations between table.
I want to fetch Challenges which has core_challenges active. I tried to do putting global scope in CoreChallenge model, but when I'm getting null in relationship when Corechallenge is inactive.
I've done it this way
$challenges = Challenge::with('core_challenge')->whereHas('core_challenge', function($q){
$q->where('status', '=', 'active');
})->get();
I want to do it using global scopes
Global scope on CoreChallenge gives me null, but I want that it's parent (Challenge) should not load even, like in whereHas. Is there any way?
I have stumbled to the same approach, but when the table got bigger (core_challenges_table in your scenario), whereHas ended up being very slow (around 1min response time).
So I used a solution like this:
$ids = CoreChallenge::where('status', 'active')->pluck('id');
$challenges = Challenge::with('core_challenges')
->whereIn('core_challenge_id', $ids)
->get();
With this approach, my query reduced to 600~ms from 1min.
Which can be translated to Model scopes
class Challenge {
public function scopeActive($query) {
$activeIds = CoreChallenge::where('status', 'active')->pluck('id');
return $query->whereIn('core_challenge_id', $ids);
}
}
Challenge::with('core_challenges')->active()->get();

WhereBetween on many-to-many relationships

in my sensory model i have this query helper method
public function asAroma()
{
return $this->belongsToMany('App\SensoryEvaluationForm')->wherePivot('sensory_type', '=', 2);
}
How can I add a query to this?
I am using it like this
$cs = App\Sensory::first();
$cs->asAroma()->whereBetween('created_at', [$startingDate, $endingDate])->get();
It returns nothing. I think there is a different way on building queries with relationships?
What I'm trying to do is to add a wherebetween filter to the resulting relationship.
Use whereHas() and pass a callback to it.
CsModel::with('asAroma')->whereHas('asAroma', function($query) use ($start, $end) {
$query->whereBetween('created_at', [$start, $end]);
})->get();
I already found an answer, it seems I need to specify the table name of the created_at attribute I want to refer to in this case sensory_evaluation_forms.created_at

Eloquent filter on pivot table created_at

I want to filter the contents of two tables which have an Eloquent belongsToMany() to each other based on the created_at column in the pivot table that joins them. Based on this SO question I came up with the following:
$data = ModelA::with(['ModelB' => function ($q) {
$q->wherePivot('test', '=', 1);
}])->get();
Here I'm using a simple test column to check if it's working, this should be 'created_at'.
What happens though is that I get all the instances of ModelA with the ModelB information if it fits the criteria in the wherePivot(). This makes sense because it's exactly what I'm telling it to do.
My question is how do I limit the results returned based on only the single column in the pivot table? Specifically, I want to get all instances of ModelA and ModelB that were linked after a specific date.
OK, here it goes, since the other answer is still wrong.
First off, wherePivot won't work in whereHas closure. It's BelongsToManys method and works only on the relation object (so it works when eager loading).
$data = ModelA::with(['relation' => function ($q) use ($someDate) {
$q->wherePivot('created_at', '>', $someDate);
// or
// $q->where('pivot_table.created_at', '>', $someDate);
// or if the relation defines withPivot('created_at')
// $q->where('pivot_created_at', '>', $someDate);
}])->whereHas('ModelB', function ($q) use ($someDate) {
// wherePivot won't work here, so:
$q->where('pivot_table.created_at', '>', $someDate);
})->get();
You are using Eager Loading Constraints, which constrain only, like you said, the results of the related table.
What you want to use is whereHas:
$data = ModelA::whereHas('ModelB' => function ($q) {
$q->wherePivot('test', '=', 1);
})->get();
Be aware that ModelB here refers to the name of the relationship.

laravel search many to many Relashionship

I am testing eloquent for the first time and I want to see if it suit my application.
I have Product table:
id, name
and model:
class Produit extends Eloquent {
public function eavs()
{
return $this->belongsToMany('Eav')
->withPivot('value_int', 'value_varchar', 'value_date');
}
}
and eav table:
id, name, code, field_type
and pivot table:
product_id, eav_id, value_int, value_varchar, value_date
class Eav extends Eloquent {
public function produitTypes()
{
return $this->belongsToMany(
'ProduitType'
->withPivot('cs_attributs_produits_types_required');
}
All this is working.
But I want to search in that relashionship:
e.g: all product that have eav_id=3 and value_int=3
I have tested this:
$produits = Produit::with( array('eavs' => function($query)
{
$query->where('id', '3')->where('value_int', '3');
}))->get();
But I get all the product, and eav data only for these who have id=3 and value_int=3.
I want to get only the product that match this search...
Thank you
I know the question is very old. But added the answer that works in the latest versions of Laravel.
In Laravel 6.x+ versions you can use whereHas method.
So your query will look like this:
Produit::whereHas('eavs', function (Builder $query) {
// Query the pivot table
$query->where('eav_id', 3);
})->get()
My suggestion and something I like to follow is to start with what you know. In this case, we know the eav_id, so let's go from there.
$produits = Eav::find(3)->produits()->where('value_int', '3')->get();
Eager loading in this case isn't going to save you any performance because we are cutting down the 1+n query problem as described in the documentation because we are starting off with using find(). It's also going to be a lot easier to read and understand.
Using query builder for checking multiple eavs
$produits = DB::table('produits')
->join('eav_produit', 'eav_produit.produit_id', '=', 'produits.id')
->join('eavs', 'eavs.id', '=', 'eav_produit.eav_id')
->where(function($query)
{
$query->where('eav_produit.value_int','=','3');
$query->where('eavs.id', '=', '3');
})
->orWhere(function($query)
{
$query->where('eav_produit.value_int','=','1');
$query->where('eavs.id', '=', '1');
})
->select('produits.*')
->get();
Making it work with what you already have...
$produits = Produit::with( array('eavs' => function($query)
{
$query->where('id', '3')->where('value_int', '3');
$query->orWhere('id', '1')->where('value_int', '1');
}))->get();
foreach($produits as $produit)
{
if(!produit->eavs)
continue;
// Do stuff
}
From http://four.laravel.com/docs/eloquent:
When accessing the records for a model, you may wish to limit your results based on the existence of a relationship. For example, you wish to pull all blog posts that have at least one comment. To do so, you may use the has method
$posts = Post::has('comments')->get();
Using the "has()" method should give you an array with only products that have EAV that match your criteria.
$produits = Produit::with( array('eavs' => function($query)
{
$query->where('id', '3')->where('value_int', '3');
}))->has('eavs')->get();

Resources