Laravel Group & Count by Eloquent Relationship - php

I know this question has be asked before here: Laravel Grouping by Eloquent Relationship but the answers are from 2016 and they seem not to work in 2019. Also my question is more simple, since I only need one relation level.
My Question
A
user has multiple items.
How to find how many items a user has of each item_type with one query?
This is what I tried:
A query like
User::with(['items' => function($q){
$q->groupBy('type');
});
returns this error:
Syntax error or access violation: 1055 Expression #1 of SELECT list
is not in GROUP BY clause and contains nonaggregated column 'items.id'
which is not functionally dependent on columns in GROUP BY clause;
this is incompatible with sql_mode=only_full_group_by
I tried to fix this error with the following query:
User::with(['items' => function($q){
$q->select('type', \DB::raw('count(*) as total'))
->groupBy('type');
});
However, this returns a collection of users where each user's item collection is empty.
Is it somehow possible to group by a relation in the query?

There is an error :
You are using $q as closure argument and inside you are using $query. Also sometimes I have faced issue where I had to pass the foreign key inside the relation query closure to get the results :
<?php
$userWithItems = User::with(['items' => function($q){
$q->select('type', \DB::raw('count(*) as total'), 'user_id')
->groupBy('type');
});
Try it once by removing user_id if it works then it's better. Secondly you can not select non aggregated columns in mysql when you have groupby. The option is disable only_full_group_by in mysql configurations. So mostl likely user_id will also fail unless you disable this configuration

Related

Why group by is not working properly? laravel

I'm currently using groupBy to group concatenated (book name and book_author). I'm intentionally using groupBy because I have other columns to get their average, and sum.
I initially used this code below. But it returning me an error.
Illuminate\Database\QueryException: SQLSTATE[42000]: Syntax error or access violation: 1055 Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated
Reference::select('*')
->selectRaw("CONCAT(book_name, ' ', book_author) AS book")
->groupBy('type');
If I add an id column inside groupBy, it does not returning an error. But it does not order, I still getting duplicate book.
Reference::select('*')
->selectRaw("CONCAT(book_name, ' ', book_author) AS book")
->groupBy(['id', 'type']);
Someone, how to achieve this properly?
TLDR; When you use group by in strict mode, your selected fields must be aggregated fields.
Let's say you have two books with the same type.
book_name
book_name
type
First
author
1
Second
editor
1
When you group by type, those having the same type will be merged into one. So wich result will it be for CONCAT(book_name, ' ', book_author) ?
First author ?
Second editor ?
First editor ?
Second author?
The query doesnt gamble when strict mode is active, you need to aggregate the fields, for example LISTAGG(book_name , ',') is an aggregationg of the field book_name and result in 'first,second'.
If you disable the strict mode ('strict' => false,) in the file config/database.php, the result might be any of the 4 listed above as it is kinda random.
Other example for aggregation functions: SUM(), AVG(), COUNT()...
Please use the below link, as the solution is already available.
Solution Link.
Please check and you will find the solution.

Trying to avoid Subquery in the Select Statement : Laravel 5.1

I am using Laravel 5.1. below is my Database query
\App\Models\Project\Bids\ProjectBid_Model
::with(['Project' => function($query){
$query->where('WhoCreatedTheProject', 16);
}])
->where('ProjectBidID', 1)
->where('FreelancerAwardedProjectStatusID', \App\ProjectStatus::InProgress)
->first()
What's the Question ?
In the below part, if there exists no record, then the Project Object will be null. This seems a sub query in the select statement. Is there any way to return overall no record if 'WhoCreatedTheProjectvalue does not match in the database ? I meant I am looking forInner Join`.
::with(['Project' => function($query){
$query->where('WhoCreatedTheProject', 16);
}])
You can do this this way (you will have one query):
\App\Models\Project\Bids\ProjectBid_Model
::selectRaw('projectbid_table.*')
->join('projects_table','projectbid_table.project_id','=','project_table.project_id')
->where('WhoCreatedTheProject',16);
->where('ProjectBidID', 1)
->where('FreelancerAwardedProjectStatusID', \App\ProjectStatus::InProgress)
->first();
You need to of course alter table names in join to match your values.
But answering the question, in your example any subquery won't be run. You are getting all the project bids (this is 1st query) and later in separate query you get all the projects for this bids for which WhoCreatedTheProject = 16 (this is 2nd query). So this what you showed is something different what you want to probably achieve.

Id Column Overwritten by Join

Using Laravel 4.2 & MySQL.
I have an applications table with an id and a fit_score_id column, and a fit_scores table with an id column. It's a basic "belongs to" relationship.
The following code:
$query = Application::join('fit_scores', 'applications.fit_score_id', '=', 'fit_scores.id');
$collection = $query->get();
...produces a collection of Application models with the id property set to the value of the fit_score_id. What am I doing to cause this?
I should note that it is necessary to do this join rather than simply using eloquent relations, because I'm going to want to order the results by a column on the fit_scores table. I don't believe this is possible using Eloquent without an explicit join.
The best way to solve this is by chaining the join method to a select method as following:
Application::select('*', \DB::raw("applications.id as appid"))
->join('fit_scores', 'applications.fit_score_id', '=', 'fit_scores.id')
->get();
Explained: The solution simply suggest that instead of thinking to prevent the behavior of overwriting the first id with the joined id, we can hook into the primary selection query (before joining) and change the label of the id column into something else (in this case 'appid'). By doing so, we end up with both the id of the parent table being labeled 'appid' and the id of the joined table being labeled 'id' again while they lives together on the final result.
I was able to find a possible solution using this answer:
Laravel 4 - JOIN - Same column name
Basically, since Laravel does not automatically prefix column names with table_name. for joined tables, we need to manually work around it by aliasing any conflicting column names in joins. Adding this select statement to my query did it:
->select(DB::raw("applications.*, fit_scores.*, applications.id as id"))
It depends on what you need but probably you can achieve it using eager loading. In case you need to mix joins and eager loading check this out. http://www.jmilan.net/posts/eager-loading-joins-in-laravel

What's Laravel's Eloquent search on relation lightest query?

In a classic Rest API approach, my application needs to check if the user is related to a certain account. We can have actions on multiple accounts, so the query is executed on every request.
Since this is our most often called query, it's lightness is important for my peace of mind.
Current query, using Eloquent
$user-> accounts()-> where($primaryKey, $id);
Somehow, these both generate errors (Eloquent bug?)
$user-> accounts()-> find ($id);
$user-> accounts()-> where ($primaryKey, $id)->count();
The error:
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'a_id' in where clause is ambiguous (SQL: select * from 'accounts' inner join 'users_accounts' on 'accounts'.'a_id' = 'users_accounts'.'a_id' where 'users_accounts'.'u_id' = 1 and 'a_id' = 1 limit 1)
Back to my question, is there a more elegant solution then the "where" function?
If this is one of your most used queries, why not cache it?
$user->accounts()->where($primaryKey, '=', $id)->remember(60)->get();
The mentioned queries fail because you have a column with the same name in both tables
where 'users_accounts'.'u_id' = 1 and 'a_id' = 1

Laravel query builder, use custom select when calling count()

I have three tables, one defining a many to many relationship between the other two.
table : collections
table : collection_question
table : questions
I am running this query to return the number of questions a collection has
$results = DB::table('collections')
->select(array(
DB::raw('count(collection_question.question_id) as question_count'),
'name',
'description'
))->leftJoin(
'collection_question',
'collections.id',
'=',
'collection_question.collection_id'
)->where('question_count', '>', 1)
->orderBy('question_count', 'asc')
->get();
This works fine as the select query is not tampered with by the query builder.
When I swap out get() for count() however the query builder replaces my select clause with select count(*) as aggregate which is understandable, however I loose the binding to question_count in the process and a SQL exception is thrown.
I've looked through the source code for Illuminate\Database\Query\Builder to try and find a solution but other than manually executing the raw query with a custom count(*) alongside my other select clauses I'm at a bit of a loss.
Can anyone spot a solution to this?
Instead of calling count() on a Builder object, I've resorted to creating my own count expression in addition to any other select clauses on the query rather than replacing them.
// This is in a class that uses Illuminate\Database\Query\Expression the clone
// isn't necessary in most instances but for my case I needed to take a snapshot
// of the query in its current state and then manipulate it further.
$query = clone($query);
$query->addSelect(new Expression("count(*) as aggregate"));
$count = (int) $query->first()->aggregate;

Categories