Laravel - whereHas with where affect other rows - php

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.

Related

Laravel eloquent query not working with sub query

so we have a page where we have to filter a list of students based on filters selected by the user in the front end.
Filters in the front end
Filter by Subjects (students who have opted for subjects)
Filter by location (students who are part of a location)
Filter by Gender (self explanatory)
Now Location & Gender are part of student table, so it is very easy to use these filters with simple query but subjects is a totally different table
Check the attached table
Query current we have
$student = Student::select('*')
->where(function ($query) use ($request) {
if (!empty($request->location)) {
$query->whereIn('location', $request->location);
} else if (!empty($request->gender)) {
$query->where('gender', $request->gender);
} else if (!empty($request->subjects)) {
// selecting students by id who enrolled for a particular subject
// end user can select multiple subjects
$query->whereIn('id', function($subjectQuery) use ($request) {
$subjectQuery->select('student_id')
->whereIn('subject_id', [$request->subjects])
->from('student_subjects')
->get();
});
})->get();
when passing {"subject": [201, 205]}
getting following error
Nested arrays may not be passed to whereIn method
But when passing {"subject": [201]}
This return empty result,
What are we doing wrong? or what we can do to improve this query?
In this case I usually use when() and whereHas() (assuming you have model StudentSubject and have the relationship). It will look like this:
$student = Student::select('*')
->when(!empty($request->location), function ($query) use($request) {
$query->whereIn('location', $request->location);
})
->when(!empty($request->gender), function ($query) use($request) {
$query->whereIn('gender', $request->gender);
})
->when(!empty($request->subjects), function ($query) use($request){
$query->whereHas('StudentSubject',function($sub) use($request) {
return $sub->whereIn('subject_id',$request->subjects);
});
})
->get();
I don't know your $request->subjects format so I assuming it's just the usual array like [1,2,3].

How can we use when clause for multiple tables in laravel eloquent query?

I'm using condition based queries but i have a problem how can i use (when) with other table/model because i have to put condition based on the other table column (branch_id).
branch_id comes from steps table.
ContactBoard::with(['steps'])
->when($request->has('branch'), function ($query) use ($request) {
$query->where('steps.branch_id', $request->query('branch'));
})
->where('id', $board_id)->get()->first();
You can make it like
$query->when($request->has('branch'), function ($q)use($request) {
return $q->with('steps',function($q){
$q->where('branch_id', $request->query('branch'));
});
});
$query = $query->get();

With PHP Laravel, how can I make an Eloquent query that is conditional like a "when" statement, but dependent on the value of retrieved columns?

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();

Datatables: how to preserve id when sorting through a relation from another table (yajra, laravel)

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

Conditional wherehas with Laravel Eloquent

I have one similar (area) value in two tables one and two and these two tables has relation with main table master. At a time, the master table will be having data in one relation only and the other one will be null.
With this architecture, I have a search page where user can search any values related to these tables and the search fields are placed with AND condition.
Here, if user enters some value for area I need to check the area value exists in any one of the tables (one or two) without breaking the AND condition. Tried the below code but it is breaking AND rule and considering OR. Any suggestions to fix?
$result = Master::where(function ($query) use ($request) {
if ($request->filter == true) {
$query->where('user_id', Auth::user()->id);
}
// other conditions here
if (!empty($request->area_from) && !empty($request->area_to)) {
$query->whereHas('one', function ($query) use ($request) {
$query->whereBetween('area', [$request->area_from, $request->area_to]);
});
$query->orWhereHas('two', function ($query) use ($request) {
$query->whereBetween('area', [$request->area_from, $request->area_to]);
});
}
// other conditions here
})->with(['one', 'two'])->paginate($request->item);
You are wrapping all of your where statements in brackets. I think what you want to do is pull your first part of the query out of the where clause so that you can easily wrap the whereHas part in brackets.
// Initialise the model
$query = new Master;
// Start building the query
if ($request->filter == true) {
$query->where('user_id', Auth::user()->id);
}
if (!empty($request->area_from) && !empty($request->area_to)) {
// Wrap these in brackets so we don't interfare with the previous where
$query->where(function($query2) use ($request) {
$query2->whereHas('one', function ($query3) use ($request) {
$query3->whereBetween('area', [$request->area_from, $request->area_to]);
});
$query2->orWhereHas('two', function ($query3) use ($request) {
$query3->whereBetween('area', [$request->area_from, $request->area_to]);
});
}
}
$query->with(['one', 'two'])->paginate($request->item);
You can create a merged relationship refer this link
public function mergedOneAndTwo($value)
{
// There two calls return collections
// as defined in relations.
$onedata= $this->one;
$twodata= $this->two;
// Merge collections and return single collection.
return $onedata->merge($twodata);
}
and use whereHas('mergedOneAndTwo')
Using a closer where and making the conditional inside may work fine
$master = Master::with('one')->with('two');
$result = $master->where(function($subQuery)
{
$subQuery->whereHas('one', function ( $query ) {
$query->whereBetween('area', [$request->area_from, $request->area_to] ); //assuming $request->area_from, $request->area_to is range of value
})
->orWhereHas('two', function ( $query ) {
$query->whereBetween('area', [$request->area_from, $request->area_to] );
});
});

Categories