Laravel Eloquent Get grouped by Relation Data - laravel

I am trying to get data with eager loading grouped by relation. Means, I want to eager load the relation in group by of a column. I have tried this code,
$customer = Customer::with(['orders' => function($query) {
$query->groupBy('shop_id');
}])->where('id', $id)->get();
Here the DB relation is,
Customer Has Many Orders (One to Many)
As an example, if I want to groupBy shop_id, I am expecting data in below format:
customer1 =>
otherProperties,
orders =>[
shop_id1 => [
order1,
order2 ...
],
shop_id2 => [
order5
]
]
But I am getting normal eager loaded data, like,
customer1 =>
otherProperties,
orders => [
order1,
order2,
order5,
]
Can anyone help me in this regard?
I can achieve similar result using raw query or php. But how I can achieve this using eloquent?

The groupBy happen on the collection and not on the query itself.
First suggestion : Create a custom scope
You could create a custom scope into your model with some Raw expressions : https://laravel.com/docs/5.8/queries#raw-expressions . Then, you will be able to do this:
$customer = Customer::with('newScope')->find($id);
Did you know that $customer = Customer::where('id', $id)->get()
return a collection of model and $customer = Customer::find($id)
return a single model?
$customer = Customer::where('id', $id)->get();
$customer = { [0] => {data} };
$customer = Customer::find($id);
$customer = { data };
Second suggestion : Use groupBy on the collection after the query
$customer = Customer::with('orders')->find($id);
$customer->orders = $customer->orders->groupBy('shop_id'));

Related

Laravel order by eagerly loaded column

I am using laravel eager loading to load data on the jquery datatables. My code looks like:
$columns = array(
0 => 'company_name',
1 => 'property_name',
2 => 'amenity_review',
3 => 'pricing_review',
4 => 'sqft_offset_review',
5 => 'created_at',
6 => 'last_uploaded_at'
);
$totalData = Property::count();
$limit = $request->input('length');
$start = $request->input('start');
$order = $columns[$request->input('order.0.column')];
$dir = $request->input('order.0.dir');
$query = Property::with(['company','notices']);
$company_search = $request->columns[0]['search']['value'];
if(!empty($company_search)){
$query->whereHas('company', function ($query) use($company_search) {
$query->where('name','like',$company_search.'%');
});
}
$property_search = $request->columns[1]['search']['value'];
if(!empty($property_search)){
$query->where('properties.property_name','like',$property_search.'%');
}
if(!Auth::user()->hasRole('superAdmin')) {
$query->where('company_id',Auth::user()->company_id);
}
$query->orderBy($order,$dir);
if($limit != '-1'){
$records = $query->offset($start)->limit($limit);
}
$records = $query->get();
With this method I received error: Column not found: 1054 Unknown column 'company_name' in 'order clause' .
Next, I tried with following order condition:
if($order == 'company_name'){
$query->orderBy('company.name',$dir);
}else{
$query->orderBy($order,$dir);
}
However, it also returns similar error: Column not found: 1054 Unknown column 'company.name' in 'order clause'
Next, I tried with whereHas condition:
if($order == 'company_name'){
$order = 'name';
$query->whereHas('company', function ($query) use($order,$dir) {
$query->orderBy($order,$dir);
});
}else{
$query->orderBy($order,$dir);
}
But, in this case also, same issue.
For other table, I have handled this type of situation using DB query, however, in this particular case I need the notices as the nested results because I have looped it on the frontend. So, I need to go through eloquent.
Also, I have seen other's answer where people have suggested to order directly in model like:
public function company()
{
return $this->belongsTo('App\Models\Company')->orderBy('name');
}
But, I don't want to order direclty on model because I don't want it to be ordered by name everytime. I want to leave it to default.
Also, on some other scenario, I saw people using join combining with, but I am not really impressed with using both join and with to load the same model.
What is the best way to solve my problem?
I have table like: companies: id, name, properties: id, property_name, company_id, notices: title, slug, body, property_id
The issue here is that the Property::with(['company','notices']); will not join the companies or notices tables, but only fetch the data and attach it to the resulting Collection. Therefore, neither of the tables are part of the SQL query issued and so you cannot order it by any field in those tables.
What Property::with(['company', 'notices'])->get() does is basically issue three queries (depending on your relation setup and scopes, it might be different queries):
SELECT * FROM properties ...
SELECT * FROM companies WHERE properties.id in (...)
SELECT * FROM notices WHERE properties.id in (...)
What you tried in the sample code above is to add an ORDER BY company_name or later an ORDER BY companies.name to the first query. The query scope knows no company_name column within the properties table of course and no companies table to look for the name column. company.name will not work either because there is no company table, and even if there was one, it would not have been joined in the first query either.
The best solution for you from my point of view would be to sort the result Collection instead of ordering via SQL by replacing $records = $query->get(); with $records = $query->get()->sortBy($order, $dir);, which is the most flexible way for your task.
For that to work, you would have to replace 'company_name' with 'company.name' in your $columns array.
The only other option I see is to ->join('companies', 'companies.id', 'properties.company_id'), which will join the companies table to the first query.
Putting it all together
So, given that the rest of your code works as it should, this should do it:
$columns = [
'company.name',
'property_name',
'amenity_review',
'pricing_review',
'sqft_offset_review',
'created_at',
'last_uploaded_at',
];
$totalData = Property::count();
$limit = $request->input('length');
$start = $request->input('start');
$order = $columns[$request->input('order.0.column')];
$dir = $request->input('order.0.dir');
$query = Property::with(['company', 'notices']);
$company_search = $request->columns[0]['search']['value'];
$property_search = $request->columns[1]['search']['value'];
if (!empty($company_search)) {
$query->whereHas(
'company', function ($query) use ($company_search) {
$query->where('name', 'like', $company_search . '%');
});
}
if (!empty($property_search)) {
$query->where('properties.property_name', 'like', $property_search . '%');
}
if (!Auth::user()->hasRole('superAdmin')) {
$query->where('company_id', Auth::user()->company_id);
}
if ($limit != '-1') {
$records = $query->offset($start)->limit($limit);
}
$records = $query->get()->sortBy($order, $dir);

Laravel Eloquent with() selecting specific column doesn't return results

Say I have 2 models, Category and POI where 1 Category can have many POIs.
$categoryDetails = Category::with([
'pois' => function ($query) {
$query->where('is_poi_enabled', true);
},
])->findOrFail($id);
The above query returns results from the specific Category as well as its POIs.
However, with the query below:
$query->select('id', 'name')->where('is_poi_enabled', true);
The POIs become empty in the collection.
Any idea why this is happening? When added a select clause to the Eloquent ORM?
While doing a select it's required to fetch the Relationship local or Primary key.
For an example POIs table contains category_id then it's required to select it
Try this:
$categoryDetails = Category::with([
'pois' => function ($query) {
$query->select(['id', 'category_id', 'is_poi_enabled'])
->where('is_poi_enabled', true);
},
])->findOrFail($id);
Good luck!

Laravel Get Result Ordred By For an eager Loaded Relation

I have:
'cards' table
-id
-name
'card_categories' Table
id
card_id
category_id
'categories' Table
id
name
index
I'am Loading The Card then Eager load the Relation, what i would like to do is when doing this :
Card::with('ctrCategories.category').......;
I would like that all loaded category from categories will be sorted by Index just the categories.
I spent the hole day doing everything , but no solution:
I tried this:
$card = Card::findOrFail($id);
return $card->with('cardCategories')
->with('ctrCategories.category')
->with('ctrCategories.arguments')
->orderBy('ctrCategories.category.index')->get();
I also tried this approach:
$data = $this->card
->with([
'roles' => function ($q) {
$q->with(['tabs' => function ($q) {
$q->with(['department' => function ($q) {
$q->with(['panel' => function ($q) {
$q->orderBy('position', 'asc');
}])->orderBy('position', 'asc');
}])->orderBy('position', 'asc');
}])->orderBy('position', 'asc');
}
])
->findOrFail($id);
=====EDIT=====
I writed the SQL query and i got the result i want Now i want to transform it to Laravel Eloquent or DB query:
select cards.id,
cards.name,categories.name,categories.id,categories.index
from cards
inner join card_categories on cards.id = card_categories.card_id inner join categories on categories.id = card_categories.category_id
where cards.id = 120
AND cards.support_id= categories.support_id
order by categories.index asc
any help ? I can't figure it out after couple of hours of testing

Search With Filters from Other Tables

I try to build a search query with different params but some of the filters columns comes from other tables who are in relation with another table , i don't know how to achieve that
For exemple the column "compet_id" comes from my table Rencontre.
In my table RencontreOfficiel i have rencontre_id to make the relation with "Rencontre"
I'm not sure if my clear it's a little bit difficult to explain ; hope someone could see and help .
here my controller :
$query = RencontreOfficiel::query();
$filters = [
'compet_id' => 'compet_id',
'structure_id' => 'structure_id',
'catg_compet_id' => 'dt_rencontre',
'fonction_id' => 'dt_rencontre',
'bareme_id' => 'bareme_id',
'dt_min_rencontre' => 'dt_rencontre',
'dt_max_rencontre' => 'dt_rencontre',
];
$dt_min = $request->input('dt_rencontre_min');
$dt_max = $request->input('dt_rencontre_max');
foreach ($filters as $key => $column) {
$query->when($request->{$key}, function ($query, $value) use ($column , $dt_min , $dt_max) {
$query->where($column, $value)->orWhereBetween('dt_rencontre' , [$dt_min , $dt_max]);
});
}
You can use whereHas and with keyword in Eloquent search.
for example you have Blog method that connected to User model. every Blog send by user . If you want to have search in User according to his blogs you can use this :
$user = User::where(function($query){
$query->where('age','>=',18);
})->whereHas('blogs', function ($query){
$query->where('text', 'LIKE', '%game%');
});
In this code blogs in whereHas method is name of Eloquent method in User model.
Result : this code return users older than 18 years that have blogs with title like game.

Laravel 5.3 - multiple db queries when loading relations

I have posts table (id, user_id, title) and Post model with this content
class Post extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
I want to get some post by id and also the user's information, so I use this query
$post = new Post();
$res = $post->where('id', 1)->select('id', 'title', 'user_id')->with([
'user' => function ($query) {
$query->select('id', 'name', 'email');
}
])->first();
It returns the data as expected, and i can access the post's info like $res->title, or the user's info like $res->user->email, but the problem is it makes 2 queries to the database
I would expect to have one query only
SELECT
`posts`.`id`,
`posts`.`title`,
`posts`.`user_id`,
`users`.`id`,
`users`.`email`,
`users`.`name`
FROM
`posts`
LEFT JOIN `users`
ON `posts`.`user_id` = `users`.`id`
WHERE `posts`.`id` = '1'
LIMIT 1
Please note, this is not the same as N+1 problem
https://laravel.com/docs/5.3/eloquent-relationships#eager-loading
I know I can manually do left join,
$res = $post->where('posts.id', 1)
->select('posts.id', 'posts.title', 'posts.user_id', 'users.email', 'users.name')
->leftJoin('users', 'posts.user_id', '=', 'users.id')
->first();
and it will have the one query as I need, but the problem is in the result all data from related table is in the same array (and besides, what is the point of defining/using relationships if i have to manually make a left join every time)
So, my question is how to get the post data with related tables with one query and result organized according to relations: I am curious what is the best practice in laravel and how experienced Laravel developers are doing this ?
Thanks
Eloquent never uses JOINs to retrieve relationship data, but instead uses seperate queries and links the data together in PHP objects. Therefore, you will always have one extra query for each relationship. Also, Eloquent mostly loads all columns (using *).
To link them together, you have to stop using the query builder and instead use Eloquent directly:
$post = Post::find(1)->load('user');
If you insist on using JOINs, you will have to continue using the query builder.
That is eager loading.
You are using
->with([
'user' => function ($query) {
$query->select('id', 'name', 'email');
}
])
In eager loading, what happens is first run above query and get all the users matching the query.
Then the result is applied to the outer query which is
$post->where('id', 1)->select('id', 'title', 'user_id')->with([
'user' => function ($query) {
$query->select('id', 'name', 'email');
}
])->first();

Resources