internal orderBy gets overwritten by external orderBy in Laravel Eloquent - php

I have a reliationship User hasOne Position and I'm fetching the users, but I want to sort them first by Position->name and then by User->name. I tried the following
<?php
$sorted = Position::where('groupId', $this->groupId)
->whereIn('id', $positions)
->with(['user' => function($query) {
$query->orderBy('user.name'); // internal sort
}])
->orderBy('position.name') // external sort
->get();
This way the results are sorted by the external sort only, or, by Position->name. Different users with the same Position->name are listed unsorted. If I remove the external sort, and leave only the sortBy User->name, it works, BUT only for the names, while positions are random.
I have tried different ways
setting the order in the Position->user relationship, does not work
setting the order in the User->position relationship, does not work
defining only an external orderBy('position.name, user.name'), crashes, saying user table is not in the query.
I also tried following similar questions like
Laravel orderBy on a relationship
Ordering Related Models with Laravel/Eloquent
but they don't seem to be trying to sort the results both by the parent and the relationship. It seems my only solution is to walk the result and sort them in PHP instead of from the DB, but this sounds dumb.
Please advice, thank you.

When you want to sort the parent Position by the relationship User, you need to use a join():
$sorted = Position::where(...)
->whereIn(...)
->with('user')
->join('users', 'users.id', '=', 'positions.user_id') // Or whatever the join logic is
->orderBy('users.name')
->orderBy('positions.name')
->get();
Note: The orderBy() on the user relationship within with() doesn't seem necessary, as by convention, a singular-named relationship should only return 1 record, and sorting on a single record is pointless.
This will return a Collection of Position models, with an attached User model, sorted by the User's name, then the Position's name. You might need to add a select('positions.*') to avoid any ambiguity issues, but this should give you the general idea.

Related

Total count decreases in group by and sorting laravel app

I'm trying to build an application in Laravel 6.0 where I'm having a sorting functionality where some of the sorting is done by joining the table as it is relational data. My code is:
$query->join('project_attribute_relation', 'projects.id', '=', 'project_attribute_relation.project_id')
->join('project_attributes', 'project_attribute_relation.attribute_id', 'project_attributes.id')
->select('projects.*', 'project_attributes.name as categories_name')
->groupBy('projects.id')
->orderBy('categories_name', request('sort_by_column')['order']);
My problem is the paginated data i.e. counts of total project becomes less during this sorting as it always counts the existing relational data and empty relation is ignored. But I want to have those empty relation also as it is effecting my project functionality.
This is my general list of projects:
And this is my list when I do sorting with categories:
Because you are using Laravel's join(), that means you are using innerjoin to query, and the project with empty relation will be not included.
And if you want to display the project with empty relation,
you need to use leftjoin, so that the projects with empty relation will be included, code like this:
$query->leftjoin('project_attribute_relation', 'projects.id', '=', 'project_attribute_relation.project_id')
->leftjoin('project_attributes', 'project_attribute_relation.attribute_id', 'project_attributes.id')

How can this be done with the relationship, and is it worth it? (Get all departments for clinic)

I have 3 tables:
clinics
departments
clinics_in_departments
Using Query Builder:
$department = ClinicsInDepartment::whereIn('clinic_id',[1,2,3])
->join('departments', 'clinics_in_departments.department_id', '=', 'departments.id')
->get();
How can this be done with the relationship, and is it worth it?
If you look at the documentation of Laravel at the Many to Many section https://laravel.com/docs/5.6/eloquent-relationships#many-to-many it's already explained in there. If you're planning to keep using Laravel I would recommend using the best practises of Eloquent. It's easier to understand and read for other developers. It's always worth to make your product the best you can. It also gives possibilities to quickly extend and maintain your application.
All you need to do is to define a relationship in your model clinics
// second, third and fourth parameter could also be optional
function departments(){
return $this->belongsToMany('App\Clinics', 'clinics_in_departments', 'department_id', 'clinic_id');
}
To retrieve the data you can use
$clinics = Clinics::with('departments')->get();
// this would hold a list of departments for each clinic
To get exactly the same data extend the query to this
$clinics = Clinics::with('departments')->whereIn('clinic_id',[1,2,3])->get();
Because it's a Many to Many relationship you could also define a relationship for the model Departments and do exactly the same as mentioned above.
You can define a belongs to many relation inside Clinics model like below code
function departments(){
return $this->belongsToMany('App\Clinics', 'clinics_in_departments');
}

Return all data from hierarchical belongsToMany relationship Laravel 5.3

I have models that are Netflix-esque (in the sense of hierarchy). As an example, I have a Lesson which belongsToMany Course which belongToMany Collection, which belongsToMany Subgroup, which belongsToMany Group. Lesson is the lowest level, up to Group as the top level. Going down the chain, each one belongsToMany of the next link down as well.
I am using a filter button that will make a call from Wordpress to my Laravel API. When I pass the group id and the subgroup id, I need to be able to return the collections belonging to the subgroup. But what I need is something like:
$group->with('subgroups')->where('subgroup_id')->with('collections')->with('courses')->with('lessons');
However, that kind of syntax doesn't work. Is there a way to query each level down and get that level's relationships?
If more code is needed, I'd be happy to share more.
The following is untested, but should hopefully either work immediately, or give you an idea as to how to solve the problem yourself.
A couple points:
You can chain relationships within a with call. E.g. courses.lessons will get all courses and related lessons for the collections found.
whereHas allows you to query relationships. In this example, I am looking for all collections, with a subgroup that matches the subgroup ID passed, and that also have a group that matches the group ID passed.
Example:
$groupId = 123;
$subgroupId = 456;
$collections = Collection::with('courses.lessons')
->whereHas('subgroup', function ($query) ($groupId, $subgroupId) {
return $query->whereHas('group', function ($query) use ($groupId) {
return $query->where('id', $groupId);
})
->where('id', $subgroupId);
})
->get();

How to use groupBy() to group a Collection object

My goal is to retrieve all of a user's 'items' and display then in my view grouped by their 'status'. There are 4 possible statuses, each with their own <div> on the page containing the items' info. After some poking around I believe I need to use the groupBy() method like so:
$items = Item::ownedBy( Auth::id() )->groupBy('status')->get();
This does seem to do some sort of grouping, but when I iterate over the collection I get a max of 4 results, one for each status. This doesn't really solve my problem as I need to display all of a user's items for each status, not just one. I must be missing something here, I'd really like to avoid making a query for each status and displaying them that way. I suppose I could filter the collection by status and create 4 new collections, but isn't this what groupBy() is supposed to do?
You can do that very easy with laravel and Collections. Collections have powerful API and a lot of handy methods, to easily achieve what you want.
Your mistake here is that you are calling groupBy on the QueryBuilder object which returns only groupped by records from the database. Instead you must select all of the records you need, then they will be returned as a collection. After that you can manipulate the collection as you wish. So what you need is:
$items = Item::ownedBy( Auth::id() )->get()->groupBy('status');
You can view all of the Colletion class useful methods here.

How do I filter on eagerly-loaded tables in Eloquent?

Basically, I want to be able to retrieve users and eagerly load e-mail addresses, and then restrict which users I bring in with a where clause.
$query = User::with('emails')->whereIn ('user_id', $user_ids);
$query->where('emails.email_address', 'LIKE', "%example%");
$usersWithEmails = $query->get();
This works:
$query->where('first_name', 'LIKE', "%test%");
which leads me to believe that the where clause is only being applied to the base user object, but I don't want to get users who don't match that e-mail. I'm aware that I can do this with several queries, but this is for a UI filter and they can filter on many child tables, so this would work much better).
How do I do that?
If you need to filter users based on a relationship, what you are looking for is whereHas.
http://laravel.com/docs/5.0/eloquent#querying-relations
User::whereIn('id', [1, 3])->whereHas('emails', function ($query) {
$query->where('emails.email_address', 'LIKE', '%example%');
})->with('emails');
This will: select users with id = [1,3], Then filter out the results where the email pattern is not matched and then will load the relationship for the resulting rows.
The difference about using whereHas and with (Logan's example) is that the latter will load all users but email field WILL BE NULL for these where the eager loading pattern is not matched.
Of course you can accomplish the same using eager loading restrictions and then filtering the collection by null fields, but I find this cleaner. I let the DB do the job for me =)
You can change the with parameter into an associative array, where the key is the name of the relationship and the value is a closure that applies any related constraints.
$query = User::with(['emails' => function ($query) {
$query->where('emails.email_address', 'LIKE', '%example%');
}])->whereIn ('user_id', $user_ids);
You can see Eloquent's docs for more information, it's the first bullet point in "Eager Loading".

Categories