Laravel, query on joined table after join - laravel

The functionality:
I'm displaying a list of responses (user is related to a response), and i want to be able filter by user name
I have an assignment response table with a user id in it
i am joining users to this table based on that id
i have a search filter for these responses
I want to be able to search by user name
Is it possible to do something like this;
$responses = AssignmentResponse::query()->where('assignment_id', '=', $request->assignment)
->with([
'likes',
'user'
]);
//FILTERS
if ($request->queryString) {
$responses->where('user.name', 'LIKE', '%' . $request->queryString . '%');
}
return $responses->get();

Simply alter the relationship with a callback:
return AssignmentResponse::where('assignment_id', $request->assignment)
->with('likes')
->when(
$request->filled("queryString"),
function ($q) use ($request) {
$q->whereHas("user", fn ($q) => $q->where("name", "like", "%$request->queryString%"))
->with(["user" => fn ($q) => $q->where("name", "like", "%$request->queryString%")]);
}
)
->get()
Note both with() and whereHas() are used to ensure only matching values are returned. A conditional clause is used in place of your if statement.

Related

Laravel query orderBy nested relationship

I have the following relationship:
Unit (HasMany)-> Users -> (BelongsTo) -> Position
I am trying to return an array of units with users, where users are sorted by their position. The property in the position model is 'order' that I would like to use as the sort field. I have attempted the following:
return Unit::query()->ordered()->with(['users' => function($query) {
$query->with(['position' => function($query) {
$query->orderBy('order');
}]);
}])->get();
You can not order by nested relationship just using with() method. You need to join the relation first. So the code should be:
return Unit::query()->ordered()->with([
'users' => function ($query) {
$query->join('positions', 'positions.id', '=', 'users.position_id');
$query->orderBy('positions.order');
}
])->get();
or another way is order using laravel collection sortBy
$ordered_units = Unit::query()->ordered()->with(['users' => function($query) {
$query->with(['position' => function($query) {
$query->orderBy('order');
}]);
}])->get();
return $ordered_units->sortBy('users.position.order');

How to make laravel query with search condition and filter condition from relational table

I have models 'Case' and 'Type' in many to many relation. 'Case' belongs to a 'Type'. Now I am trying to implement search and filter in my case index. I have already included the search in the query builder so that the keywords match multiple columns and also columns in related table. Now I am trying to incorportae a filter also. But when I am including that filter in query builder, it is not working. Including either search or filter is working but including both together is not.
Here is my code to include the search in query builder:
$key = $this->search;
$cases = Landcase::query()->orderBy('created_at', 'DESC');
$cases
->where('number', 'like', '%'.$this->search.'%')
->orWhere('title', 'like', '%'.$this->search.'%')
->orWhereHas('type', function ($query) use ($key) {
$query->where('name', 'like', $key.'%');
});
This much is perfectly working. I can search in title, number and type's name. Now here is the additional code to include the filter with type id(s) so that only specific type's cases will be shown and the search will work on those only.
if($this->selected_types){
$ids= $this->selected_types;
$cases->whereHas('type', function ($query) use ($ids){
$query->whereIn('id', $ids);
});
}
this block is not affecting the collection. But if I comment out the first block, this works. How can I incorporate it to my existing query builder block?
You have several OR in your where clauses so adding the last part makes it something like:
WHERE … OR … OR (… AND …)
What you need is to group your orWheres:
$cases->where(function ($query) {
$query
->where('number', 'like', '%'.$this->search.'%')
->orWhere('title', 'like', '%'.$this->search.'%')
->orWhereHas('type', function ($query) use ($key) {
$query->where('name', 'like', $key.'%');
});
});
More here: https://laravel.com/docs/9.x/queries#or-where-clauses

laravel nested hasmany with where clause on last child

I want to get data from nested relationships with where clause on the last child, (as json nested response). I'm making an API with 4 nested models with hasmany relations like this:
modelA ->hasmany modelB ->hasmany modelC ->hasmany modelD
(modelA is first parent etc..)
I have a route on api.php
Route::get('/rdata/{string}', [ModelAController::class, 'getdata']);
On my ModelAController:
public function getdata($string)
{
return ModelA::thedata($string);
}
And then on my ModelA
public static function thedata($string)
{
return ...
}
I want to get data based on a field of the 4th ModelD. How can I do a where clause on the 4th child maybe something like this:
where('column', 'like', '%'.$string.'%')
I also tried with collection resources, I can get all the data nested fine but I cant perform the query I want on the last child. Best thing I've achieved is doing the query on the last child load parents but it doesnt work properly
return ModelD::where('column', 'like', '%'.$string.'%')->with('modelc.modelb.modela')->get();
or even with load
return ModelD::where('column', 'like', '%'.$string.'%')->get()->load('modelc.modelb.modela');
then on ModelC:
public function modelb()
{
return $this->belongsTo(ModelB::class, 'f_key','f_key');
}
and the same for the other parents but this is wrong as I get all the children inside them again and also the json is inversed. I want to keep the format:
$response = ['ModelA' => [
'id' => '..'
'field1' => '..'
'ModelB'=>[
etc..
]]
Or is there another way to do it that I don't see? In a way I need something like this but formated in nested json
return DB::table('modela')
->join('modelb', 'modela.id', 'modelb.modela_id')
->join('modelc', 'modelb.id', 'modelc.modelb_id')
->join('modeld', 'modelc.id', 'modeld.modeld_id')
->where('column', 'like', '%'.$string.'%')
->get();
Edit1: I got this so far based on the answer and I want to get parent columns on last child
return self::with('modelb.modelc.modeld')-> whereHas('modelb.modelc.modeld', function ($modeld) use ($string) {
$modeld->where('column', 'like', "%$string%");
->whereColumn('modelc.column2', 'modeld.column2')
->whereColumn('modelc.column3', 'modeld.column3');
})
->with(['modelb.modelc.modeld' => function ($modeld) use ($string) {
$modeld->where('column', 'like', "%$string%");
->whereColumn('modelc.column2', 'modeld.column2')
->whereColumn('modelc.column3', 'modeld.column3');
}])
->get();
I think you can do that with whereHas/with Closures.
ModelA::query()
->whereHas('modelb.modelc.modeld', function ($modeld) use ($string) {
$modeld->where('column', 'like', "%$string%");
->with(['modelb.modelc.modeld' => function ($modeld) use ($string) {
$modeld->where('column', 'like', "%$string%");
}])
->get();
Something like this, but probably more refined in the conditions.
I think based on your edit, that this query should work for your needs.
ModelA::query()
->whereHas('modelb.modelc', function ($modelC) use ($string) {
$modelC->whereHas('modeld', function ($modelD) use ($string) {
$modelD->whereColumn('table_c.column2', 'table_d.column2')
->whereColumn('table_c.column3', 'table_d.column3')
->where('table_d.column', 'like', "%$string%");
});
->with(['modelb.modelc' => function ($modelC) use ($string) {
$modelC->with(['modeld' => function ($modelD) use ($string) {
$modelD->whereColumn('table_c.column2', 'table_d.column2')
->whereColumn('table_c.column3', 'table_d.column3')
->where('table_d.column', 'like', "%$string%");
}]);
}])
->get();

Relational Query In laravel

My Code
$videos = ExamVideo::with([
'user' => function ($query) use ($request) {
$query->where('user_name', 'like', '%'.$request->search.'%');
}
])->where('is_marked','=', 0)->get();
return $videos;
I want to get only those videos where is_marked is zero & it's relation user_name match my search result.
But I get all the videos which marked is zero.
The with method is only for eager loading and not used to filter your records.
You can accomplish what you want with the whereHas method which will perform a query on the relation:
$videos = ExamVideo::whereHas('user', function ($query) use ($request) {
$query->where('user_name', 'like', "%{$request->search}%");
})->where('is_marked', false)->get();
For more information about whereHas: https://laravel.com/docs/6.x/eloquent-relationships#querying-relationship-existence

Laravel 5.2 Eloquent Order Query by Eagerly Loaded Data

Is it possible to order the results of an Eloquent query by the eagerly loaded data. To be clear I get the right data, it is paginated properly, and works in every way except it isn't ordered the way I need it to be. In this case I'd like to sort users by profile.firstname.
Something like this:
$results = User::where('id', '!=', $user->id)
->whereNotIn('id', $ids)
->with([
'profile' => function ($query) {
$query->addSelect(['id', 'first_name', 'last_name']);
},
])
->orderBy('profile.first_name', 'desc')
->paginate();
The data is used in an Ionic application and is used for an infinite scroll so I need to have the data in the format below ordered by first_name prior to being received on the client, but the above example doesn't work.
DATA needed for Ionic View
user: {
id: '1',
username: 'aUsername'
email: 'anEmail'
profile: {
id: '2',
user_id: '1',
first_name: 'aFirstName',
last_name: 'aLastName',
phone_number: '999-999-9999'
}
}
...
Solution (with flattened result set)
$results = User::join('user_profiles', 'users.id', '=', 'user_profiles.user_id')
->select('users.id', 'users.username', 'user_profiles.first_name', 'user_profiles.last_name')
->where('users.id', '!=', $user->id)
->whereNotIn('id', $existingContactIds)
->where('users.username', 'like', $request->input('username') . '%')
->orderBy('user_profiles.first_name')
->paginate();
Solution (with proper result format)
$results = User::where('users.id', '!=', $user->id)
->whereNotIn('users.id', $existingContactIds)
->where('users.username', 'like', $request->input('username') . '%')
->with([
'profile' => function ($query) use ($columns) {
$query->addSelect('user_profiles.first_name', 'user_profiles.last_name');
},
])
->select('users.id', 'users.username')
->join('user_profiles', 'users.id', '=', 'user_profiles.user_id')
->orderBy('user_profiles.first_name')
->paginate();
For ordering by another table, you need to perform a join.
Chain in a join for your profile table
-> join('profiles', 'profiles.id', '=', 'users.profile_id')
You'll then be able to orderBy first_name (though not profile.first_name).
It isn't possible to orderBy eager loaded tables. If you look at the SQL Laravel produces, you'll see that it firsts gets all the results from the users tables then passes the IDs of just the results (so a subset of all results if you're using pagination) to the profiles table to get relevant data.
You could sometimes avoid the join by using the Collection method sortBy on the results, but this won't work when coupled with pagination as sortBy only works on the results, so will be ordering a subset of data only (but sortBy does support the dot notation you've tried using for your orderBy).
Im seeing your code and I think it should be ->orderBy('profile->first_name', 'desc') or try this other way ->orderBy('first_name', 'desc')
Try like this
$results = User::where('id', '!=', $user->id)
->whereNotIn('id', $ids)
->with([
'profile' => function ($query) {
$query->addSelect(['id', 'first_name', 'last_name']);
$query->orderBy('first_name','DESC');
},
])
->paginate();

Resources