I have an Item table, Meta table and a Item_Meta table which has all meta records for Items. One Item has many Meta records.
Item_meta has the following columns:
Item_id | Meta_id | Value
Lets say I have a requirements:
Requirement 1 - ['Meta_id' => 1, 'value' => 'new']
Requirement 2 - ['Meta_id' => 3, 'value' => 'LCD']
I need to build a query get all items, which have meta_id with id 1 equal to new, and meta_id with id 3 equal to LCD.
So if an item does not have one of this metas (or one of metas has wrong value) it should not be returned.
You can filter your models on relation's attributes using Eloquent's whereHas() method. In your case the following code should do the trick:
$items = Item::whereHas('metas', function($query) {
$query->where('Meta_id', 1);
$query->where('value', 'new);
})
->whereHas('metas', function($query) {
$query->where('Meta_id', 3);
$query->where('value', 'LCD);
})
->get();
I assumed that your item->meta relation is called metas.
You could also use another form of using whereHas() where you provide number of matching related records. The code will be a bit more complex but it will result in less subselects in the executed query:
$items = Item::whereHas('metas', function($query) {
$query->where('Meta_id', 1);
$query->where('value', 'new);
$query->orWhere(function($query2) {
$query2->where('Meta_id', 3);
$query2->where('value', 'LCD);
};
}, '=', 2)
->get();
I have tried the first approach from the previous answer before asking the question, but it failed so I thought it had a mistake. But after seeing and that answer it seemed that I don't understand something. So I digged inside of Laravel Eloquent internals and here is what I found:
Eloquent Build class "::where" method changes $this->query property before returning the instance. Here is the code:
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($column instanceof Closure) {
$query = $this->model->newQueryWithoutScopes();
call_user_func($column, $query);
$this->query->addNestedWhereQuery($query->getQuery(), $boolean);
} else {
call_user_func_array([$this->query, 'where'], func_get_args());
}
return $this;
}
Take notice of "$this->query->addNestedWhereQuery($query->getQuery(), $boolean);" on the 6th line.
The Builder class "::whereHas" method does not modify "$this->query" property.
public function whereHas($relation, Closure $callback, $operator = '>=', $count = 1)
{
return $this->has($relation, $operator, $count, 'and', $callback);
}
So, what it has to do with my problem. I have created a query like this:
$query = Item::where('active',1);
Then I added the conditions:
if($meta){
$query->whereHas('metas', function($query) {
$query->where('Meta_id', 1);
$query->where('value', 'new);
})
->whereHas('metas', function($query) {
$query->where('Meta_id', 3);
$query->where('value', 'LCD);
})
}
and then query was performed:
$query->get();
The mistake was related to ::whereHas method, what I had to do was assign whereHas result to the variable:
if($meta){
$query = $query->whereHas('metas', function($query) {
$query->where('Meta_id', 1);
$query->where('value', 'new);
})
->whereHas('metas', function($query) {
$query->where('Meta_id', 3);
$query->where('value', 'LCD);
})
}
So thats all. Always check with internals if something works unexpectadely
Related
I have created many-to-many relation using belongsToMany function:
class Doctor extends Model
{
...
public function categories()
{
return $this->belongsToMany('App\Category', 'doctors_to_categories', 'doctor_id', 'category_id');
}
...
}
Now I want to create query with many-to-many condition. In SQL in would be:
SELECT *
FROM `doctors`
JOIN `doctors_to_categories`
ON `doctors_to_categories`.`doctor_id` = `doctors`.`id`
WHERE `doctors_to_categories`.`category_id` = 1
I have tried to achieve this like:
$doctors = Doctor::with(['categories' => function($query) {
$query->where('category_id', '=', 1);
}])->get();
Or
$doctors = Doctor::with(['categories' => function($query) {
$query->where('categories.id', '=', 1);
}])->get();
But it is not working. Any ideas how it should be? Thanks for any help.
The with() function does not actually introduce a join in your query, it just loads the relation of all models as a second query. So the with() function couldn't possibly change the original result set.
What you are looking for is whereHas(). This will add a WHERE EXISTS clause to the existing query.
$doctors = Doctor::with('categories')->whereHas('categories', function ($query) {
$query->where('categories.id', 1);
})->get();
Using ->with() doesn't actually limit the results of the Doctor::...->get() query; it simply tells Laravel what to return in the relationships attribute. If you actually want to enforce returning only Doctors that have a category 1 relationship, you need to use whereHas():
$doctors = Doctor::whereHas('categories', function($query) {
$query->where('categories.id', '=', 1);
// `id` or `categories.id` should work, but `categories.id` is less ambigious
})->get();
You can add whereHas condition for this. Try code below:
$doctors = Doctor::with('categories')->whereHas('categories', function($query) {
$query->where('id', 1);
})->get();
I'm currently using this function to gather all of my users with a relationship
$users = users::with(array('statusCurrent' => function($query)
{
$query->where('status.status', 'in');
$query->orderBy('status.date', 'DESC');
}));
$users = $users->get();
This returns both of my users, and if status = 'in' then it returns the relationship aswell, but if status = 'out' it still returns the row, but with status_current = null.
Basically I want to ignore the user completely if the arguments inside the with query builder function are not true.
I have tried $candidates = $candidates->has('statusCurrent')->get(); to try and only get results where the relationship is not null, but it still returns users where the StatusCurrent relationship is null.
How do I do it so that foreach of the users, if whatever arguments I pass into the with(array('statusCurrent' => function(){}) are not true, it is ignored completely?
EDIT
Users Model
public function statusCurrent(){
return $this->hasOne('App\Status', 'user_id', 'id')->orderBy('date', 'desc')->limit(1);
}
The user can have many status', but the StatusCurrent relationship is meant to return their 1 most recent status based on the status.date
You need whereHas to filter out users based on their relationship
$users = users::with(array('statusCurrent' => function($query)
{
$query->orderBy('status.date', 'DESC');
}))
->whereHas('statusCurrent', function ($query) {
$query->where('status', 'in');
})
->get();
See Querying Relationship Existence
I have a 'user' table that has a pivot table for services that a user offers:
// App\User
public function services()
{
return $this->hasMany('App\ServiceUser');
}
On the ServiceUser model I then have another relationship to get the service information:
public function service()
{
return $this->hasOne('App\Service', 'id');
}
When fetching a team (using Laravel Spark) the query I am using is:
Team::with('users')->withUserCustomerServices()->where('id', $teamId)->first();
The scope for this query is in the Team model:
public function scopeWithCustomerServices($query)
{
$query = $query;
$query->with('users.services');
$query->with(['users.services.service' => function($q) {
$q->where('visible_to_customers', 1);
}]);
return $query;
}
When outputting (using Vue.js):
{{ user.services.length }}
I get (in this example) 6 results returned. However, one of the services has a database field 'visible_to_customers' set to 0.
Initially I thought my query would work as expected and only return 5 services however it actually still returns them all, but doesn't return the relationship (service) if the field is 0.
How can I can I only return the pivot table result where the relationship has a certain field value?
EDIT
I have updated the query to use a whereHas on the first nested relation:
$query->with(['users.services' => function($q) {
$q->whereHas('service', function($q) {
$q->where('visible_to_customers', 1);
});
}]);
This works great, only returns the pivot table rows where the services table has a field value of 1 for visible_to_customers.
However, that doesn't fetch the related row itself.
If I then chain on:
$query->with(['users.services' => function($q) {
$q->whereHas('service', function($q) {
$q->where('visible_to_customers', 1);
});
}]);
$query->with(['users.services.service' => function($q) {
$q->where('visible_to_customers', 1);
}]);
It remains the same issue where it fetch all of the rows but then only the related rows where the field is 1.
Fixed this issue by using a where has on the first relationship that is the pivot:
$query->with(['users.services' => function($q) {
$q->whereHas('service', function($q) {
$q->where('visible_to_customers', 1);
})->with('service');
}]);
I then appended the ->with('service') to the end of the chain.
I have 2 models: TheSeries and TheEpisodes.
TheSeries has many TheEpisodes and TheEpisodes has one TheSeries.
I am trying to list all TheSeries and display latestEpisode in each, by using TheEpisodes.addDate.
The code I have right now is this:
$TheSeries = TheSeries::with('TheEpisodes');
What should I do to display only latest 1 episode for each TV serial?
EDIT
->take(1) and ->limit(1) do not work for TheEpisodes
EDIT (Latest Semi-Working Code)
$results = TheSeries::take(5)->with(['TheEpisodes' => function($q) {
$q->orderBy('addDate', 'desc');
}])->get()
This works, it returns the episodes in correct order but I am unable to limit the results to 1. This following codes don't work:
// Example 1
$results = TheSeries::take(5)->with(['TheEpisodes' => function($q) {
$q->orderBy('addDate', 'desc')->take(1);
}])->get()
// Example 2
$results = TheSeries::take(5)->with(['TheEpisodes' => function($q) {
$q->orderBy('addDate', 'desc')->limit(1);
}])->get()
// Example 3
$results = TheSeries::take(5)->with(['TheEpisodes' => function($q) {
$q->orderBy('addDate', 'desc')->first();
}])->get()
Those are the column names of the tables:
TheSeries - id, originalTitle, aliasTitle, description, imageURL, startingDate, endingDate, activeBool
TheEpisodes: id, seriesID, authorID, addDate, episodeVersion
Define a TheLatestEpisode hasOne relation on your TheSeries model:
class TheSeries extends Model
{
public function TheLatestEpisode()
{
return $this->hasOne(TheEpisode::class, 'seriesID')->orderBy('id', 'desc');
}
}
Then you can easily do:
$series = TheSeries::with('TheLatestEpisode')->get();
You can try it as:
$TheSeries = TheSeries::with(['TheEpisodes' => function($q) {
$q->orderBy('created_at', 'desc')->take(1);
}])
->get();
Or try with limit as:
$TheSeries = TheSeries::with(['TheEpisodes' => function($q) {
$q->orderBy('created_at', 'desc')->limit(1);
}])
->get();
$TheSeries = TheSeries::with(['TheEpisodes' => function($q) {
$q->orderBy('created_at', 'desc')->first();
}])
->get();
can't work?
Why don't you use the DB statement ?
DB::table('TheEpisodes')
->leftjoin('TheSeries','TheEpisodes.SeriesId','=','TheSeries.id')
->select('TheEpisodes.*','TheSeries.id as sId','TheSeries.name as sName',...)
->orderBy('TheEpisodes. addDate','desc')
->take(1)
->get();
You can try something like this in your TheSeries model: (it is easier)
public function getLatestEpisodeAttribute(){
$episode = TheEpisodes::where('series_id',$this->attributes['id'])
->latest()
->first();
if(!$episode){
return "no episodes for this Series";
}
return $episode;
}
On your controller just do the query normally without including anything related to TheSeries and you can access it values in your blade file like this:
//lets suppose there is a title attibute in the episodes
{{$TheSeries->latest_episode->title}}
//or a duration attribute
{{$TheSeries->latest_episode->duration}}
The best solution I found, was to create a one-to-one relationship with orderBy('addDate'), it works!!!
Reference: https://softonsofa.com/tweaking-eloquent-relations-how-to-get-latest-related-model/
$TheSeries = TheSeries::with('TheEpisodes')->first();
Or
$TheSeries = TheSeries::with('TheEpisodes')->firstOrFail();
I have a query which looks like this:
$items = Item::live()
->with('location')
->where('last_location_id', Input::get('last_location_id'))
->get();
The background of this is...
2 tables: Items & Cars.
The live scope is:
public function scopeLive($query)
{
return $query->whereHas('basic_car', function($q)
{
$q->whereNotNull('id')->where('sale_status', 'Live');
});
}
This basically checks the cars table for a matching id to that of the items 'car_id' field and will run some where clauses on the cars table.
I now however want to check another field on the cars table, but using the Input::get('last_location_id') from the original query.
$items = Item::live()
->with('location')
->where('last_location_id', Input::get('last_location_id'))
->orWhere('ROW ON THE CARS TABLE' = Input::get('last_location_id'))
->get();
This does't work, then I tried:
$items = Item::live()
->with('location')
->where('last_location_id', Input::get('last_location_id'))
->orWhere(function($query)
{
$query->where('cars.Location', Input::get('last_location_id'));
})
->get();
Which results in an unknown column 'cars.Location' error.
My next test was to create another scope:
public function scopeLiveTest($query)
{
return $query->whereHas('basic_car', function($q)
{
$q->whereNotNull('id')->where('sale_status', 'Live')->where('Location', 1); // hardcoded ID
});
}
And replacing the live() scope with that works but I dont get the affect of the orWhere in the query itself and I also cannot specify a ID from the Input.
How can I do this?
You can pass a parameter to scope like this:
$items = Item::liveAtLocation(Input::get('last_location_id'))
->orWhere(function( $query ) { // to get the OR working
$query->live()
->with('location')
->where('last_location_id', Input::get('last_location_id'));
})
->get();
And for the scope:
public function scopeLiveAtLocation($query, $location_id)
{
return $query->whereHas('basic_car', function($q) use ($location_id)
{
$q->whereNotNull('id')->where('sale_status', 'Live')->where('Location', $location_id);
});
}