If I have a query: MyModal::with('relation1.relation2')->get(), how can I limit the fields selected from relation1?
MyModal::with('relation1:column1,column2')->with('relation1.relation2')->get() selects all fields on relation1.
MyModal::with('relation1:column1,column2.relation2')->get() gives an SQL error because it tries to find a column named column2.relation2.
I'm not sure what other approach there could be, so is this possible, or will fetching nested relations always fetch all fields on the first relation?
You should do column select after relation select:
MyModal::with(['relation1.relation2', 'relation1:column1,column2'])->get()
Or you can define it in model itself
public function relation1()
{
return $this->hasOne(Relation1::class)->select(['column1', 'column2']);
}
Model::query()
->with(array('relation1' => function($query) {
$query->select('column1', 'column2');
},'relation2' => function($query) {
$query->select('column1', 'column2');
}))
->get();
Related
So I have a many-to-many table with created_at and the foreign keys student_id and status_id.
For each student, I want to retrieve an entry only if the latest one has status_id = 1.
I tried it like this: (it's a chain of queries, in this case $query would be Student::where(something else))
$query->whereHas('statusuri', function($query) use ($statusuri) {
$query->latest('status_student.created_at')->where('status_id', 1);
});
(statusuri is the many-to-many relationship)
but I get a different result from what I need.
It does the where clause first and then latest(). Basically, it retrieves the last entry which matches the where.
I want to search for each student, the latest entry and if the where clause matches it, get that entry. If not, don't return anything.
Is that possible with Eloquent?
Thanks.
In your case, you can use the where and latest methods together in a subquery, like this:
$query->whereHas('statusuri', function($query) use ($statusuri) {
$query->where(function($query) {
$query->where('status_id', 1)
->latest('status_student.created_at');
});
});
Have you considered a "has one of many" relationship : https://laravel.com/docs/9.x/eloquent-relationships#advanced-has-one-of-many-relationships
You can make a relationship in your Student model:
public function latestStatus()
{
return $this->hasOne(StatusStudent::class)->ofMany([
'created_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('status_id', 1);
});
}
Edit: works only for Laravel 8+
This question can probably be best asked in the form of an analogy.
Let's say I have a "table" model that has many "filters" - these filters have a column "type" which has to either be "color" or "make", and an "allowed" column which contains the array of allowed values for each. I want to use these tables to display a filtered list of "cars" (which have color/make columns), and if I then add a car to the DB, I want it to figure out the set of tables to which that car needs to be added depending on every table's filters for car and make. So that means I query tables, and I join filters - but where the filters are "color", they have to contain the color of the car, and where the filters are "make", they have to match the make of the car. In this way, the table would get a list of cars that matches all of its filters. In pseudocode, this would be something like:
Table::join('filters', 'filters.table_id', '=', 'tables.id')
->when('filters.type', '=', "color", function($query) use ($car) {
$query->whereJsonContains('filters.allowed', $car->type);
})
->orWhen('filters.type', '=', "make", function($query) use ($car) {
$query->whereJsonContains('filters.allowed', $car->make);
})
->get();
And I'm trying to work out what, if possible, the correct way of writing such a query would be. I had the following before:
Table::join('filters', 'filters.table_id', '=', 'tables.id')
>where(function ($query) use ($car) {
$query->where('filters.type', "color")
->whereJsonContains('filters.allowed', $car->color);
})->orWhere(function($query) use ($car) {
$query->where('filters.type', "make")
->whereJsonContains('filters.allowed', $car->make);
})->get();
But this would return all tables where the car matched any of the filters instead of all of them. If I make it a where instead of an orWhere, then I get conflicting conditions in where('filters.filter_type', "make") and where('filters.filter_type', "color"), which will then give me no results at all. So, is it possible to write conditional when clauses that depend upon the value of columns like in my pseudocode example? Thanks very much!
Figured it out! Wasn't actually as difficult as I thought, and didn't require any raw SQL. First, I created two extra relationships for the table in addition to the filters one:
public function filters()
{
return $this->hasMany(Filter::class);
}
public function colorFilters()
{
return $this->filters()->where('type', "color");
}
public function makeFilters()
{
return $this->filters()->where('type', "make");
}
Then I was able to make the query work by using whereHas and logical grouping:
Table::where(function ($query) use ($car) {
$query->whereHas('colorFilters', function($colorQuery) use ($car) {
$colorQuery->whereJsonContains('allowed', $car->color);
})
->orWhereDoesntHave('colorFilters');
})
->where(function ($query) use ($car) {
$query->whereHas('makeFilters', function($makeQuery) use ($car) {
$makeQuery->whereJsonContains('allowed', $car->make);
})
->orWhereDoesntHave('makeFilters');
})
->get();
I'm using datatables from yajra datatables and I have a problem.
I have a datatable where I obtain certain columns from other tables, for example the column "customer" is obtained through a relation from another table
But when I press the sort by customer button, the IDs change to the ID of the table where the relation belongs
This generates errors in the buttons that I have on the right since these IDs do not exist in the "meos" table, only in the "locations" table that is in the relation
How can I make it sort while keeping the same ID it had?
I want to always keep as order criteria the ID of the table I am with, which is the "meos" table belonging to the "meo" class
This is my query
public function query()
{
$languageId = Auth::user()->language_id;
return Meo::with(['businessType' => function ($query) use ($languageId) {
$query->with(['businessTypeDescriptions' => function ($subQuery) use ($languageId) {
$subQuery->where('language_id', '=', $languageId);
}]);
}])->with('location');
}
this is my function getcolumns
protected function getColumns()
{
return [
Column::make('id')->addClass('text-center')->title(__('digestReport.columns.id')),
Column::make('location.location_name')->addClass('text-center')->title(__('digestReport.columns.customer')),
Column::make('businessType')->addClass('text-center')->title(__('digestReport.columns.business_type'))->searchable(false),
Column::computed('action')->exportable(false)->printable(false)->width(160)->addClass('text-center')->title(__('digestReport.columns.actions')),
];
}
please, I need help.
Thanks :)
Try below code. Add ->select() statement
public function query()
{
$languageId = Auth::user()->language_id;
return Meo::with(['businessType' => function ($query) use ($languageId) {
$query->with(['businessTypeDescriptions' => function ($subQuery) use ($languageId) {
$subQuery->where('language_id', '=', $languageId);
}]);
}])->with('location')->select('meo-table.*');
}
Replace meo-table with your database table for Meo::class
I have an application where I want to fetch parent records based on children conditionals. Current problem is that I have Students, where they have multiple study fields and study fields belong to one faculty. Pivot table students_study_fields has attribute study_status_id.
What I need is, for example, fetch all students and their study fields which belongs to "prf" faculty AND pivot has study_status_id = 1.
So I write a query like this.
return Student::with(['studyfields' => function ($query1) use ($studyStatusId, $facultyAbbreviation) {
$query1->whereHas('pivot', function ($query2) use ($studyStatusId, $facultyAbbreviation) {
$query2->where('study_status_id', $studyStatusId);
});
$query1->whereHas('studyprogram', function ($query4) use ($facultyAbbreviation) {
$query4->whereHas('faculty', function ($query5) use ($facultyAbbreviation) {
$query5->where('abbreviation', $facultyAbbreviation);
});
});
}])->get();
But this query fetch students witch study_status_id = 2 as well because exists record where this same study field (its code) has relation with student, where study_status_id = 1.
So I don't want to include this studyfield if somewhere exists record with status = 1 in pivot but only if has status = 1 for current row
You need to chain the queries...
return Student::with(['studyfields' => function ($query1) use ($studyStatusId, $facultyAbbreviation) {
$query1->whereHas('pivot', function ($query2) use ($studyStatusId, $facultyAbbreviation) {
$query2->where('study_status_id', $studyStatusId);
})->whereHas('studyprogram', function ($query4) use ($facultyAbbreviation) {
$query4->whereHas('faculty', function ($query5) use ($facultyAbbreviation) {
$query5->where('abbreviation', $facultyAbbreviation);
});
});
}])->get();
Otherwise it will re-start the query1 so you won't get AND kind of query, only get the second part
Side Note: However, I want to warn you that whereHas is a slow query if you have many rows as it goes through each value. I personally prefer grabbing the ids with simple ->where queries and utilise ->whereIn approach.
I found solution for my situation
$students = Student::with(['studyfields' => function ($q) use ($studyStatusId) {
$q->whereHas('pivot')->where('study_status_id', $studyStatusId);
}])
->whereHas('studyfields', function ($q) use ($facultyAbbreviation) {
$q->whereHas('studyprogram', function ($q) use ($facultyAbbreviation) {
$q->where('faculty_abbreviation', $facultyAbbreviation);
});
})
->get();
$students = $students->filter(function ($student) {
return count($student->studyfields) > 0;
})->values();
Query above fetch all students from specific faculty and if studyfields array doesn't contains specific study_status, leave empty array so later I can filter collection from empty arrays assuming that each student belongs to at least one studyfield.
In my Database, I have:
tops Table
posts Table
tops_has_posts Table.
When I retrieve a top on my tops table I also retrieve the posts in relation with the top.
But what if I want to retrieve these posts in a certain order ?
So I add a range field in my pivot table tops_has_posts and I my trying to order by the result using Eloquent but it doesn't work.
I try this :
$top->articles()->whereHas('articles', function($q) {
$q->orderBy('range', 'ASC');
})->get()->toArray();
And this :
$top->articles()->orderBy('range', 'ASC')->get()->toArray();
Both were desperate attempts.
Thank you in advance.
There are 2 ways - one with specifying the table.field, other using Eloquent alias pivot_field if you use withPivot('field'):
// if you use withPivot
public function articles()
{
return $this->belongsToMany('Article', 'tops_has_posts')->withPivot('range');
}
// then: (with not whereHas)
$top = Top::with(['articles' => function ($q) {
$q->orderBy('pivot_range', 'asc');
}])->first(); // or get() or whatever
This will work, because Eloquent aliases all fields provided in withPivot as pivot_field_name.
Now, generic solution:
$top = Top::with(['articles' => function ($q) {
$q->orderBy('tops_has_posts.range', 'asc');
}])->first(); // or get() or whatever
// or:
$top = Top::first();
$articles = $top->articles()->orderBy('tops_has_posts.range', 'asc')->get();
This will order the related query.
Note: Don't make your life hard with naming things this way. posts are not necessarily articles, I would use either one or the other name, unless there is really need for this.
For Laravel 8.17.2+ you can use ::orderByPivot().
https://github.com/laravel/framework/releases/tag/v8.17.2
In Laravel 5.6+ (not sure about older versions) it's convenient to use this:
public function articles()
{
return $this->belongsToMany('Article', 'tops_has_posts')->withPivot('range')->orderBy('tops_has_posts.range');
}
In this case, whenever you will call articles, they will be sorted automaticaly by range property.
In Laravel 5.4 I have the following relation that works fine in Set model which belongsToMany of Job model:
public function jobs()
{
return $this->belongsToMany(Job::class, 'eqtype_jobs')
->withPivot(['created_at','updated_at','id'])
->orderBy('pivot_created_at','desc');
}
The above relation returns all jobs that the specified Set has been joined ordered by the pivot table's (eqtype_jobs) field created_at DESC.
The SQL printout of $set->jobs()->paginate(20) Looks like the following:
select
`jobs`.*, `eqtype_jobs`.`set_id` as `pivot_set_id`,
`eqtype_jobs`.`job_id` as `pivot_job_id`,
`eqtype_jobs`.`created_at` as `pivot_created_at`,
`eqtype_jobs`.`updated_at` as `pivot_updated_at`,
`eqtype_jobs`.`id` as `pivot_id`
from `jobs`
inner join `eqtype_jobs` on `jobs`.`id` = `eqtype_jobs`.`job_id`
where `eqtype_jobs`.`set_id` = 56
order by `pivot_created_at` desc
limit 20
offset 0
in your blade try this:
$top->articles()->orderBy('pivot_range','asc')->get();
If you print out the SQL query of belongsToMany relationship, you will find that the column names of pivot tables are using the pivot_ prefix as a new alias.
For example, created_at, updated_at in pivot table have got pivot_created_at, pivot_updated_at aliases. So the orderBy method should use these aliases instead.
Here is an example of how you can do that.
class User {
...
public function posts(): BelongsToMany {
return $this->belongsToMany(
Post::class,
'post_user',
'user_id',
'post_id')
->withTimestamps()
->latest('pivot_created_at');
}
...
}
You can use orderBy instead of using latest method if you prefer. In the above example, post_user is pivot table, and you can see that the column name for ordering is now pivot_created_at or pivot_updated_at.
you can use this:
public function keywords() {
return $this->morphToMany(\App\Models\Keyword::class, "keywordable")->withPivot('order');
}
public function getKeywordOrderAttribute() {
return $this->keywords()->first()->pivot->order;
}
and append keyword attribiute to model after geting and use sortby
$courses->get()->append('keyword_order')->sortBy('keyword_order');