framework: eloquent count() does not work well with groupBy()
Eloquent count() completely ignores what is in select and just adds count(*) as aggregate into select part. If get() method generates this query :
select sellers.* from sellers
left join locations
on locations.seller_id = sellers.id
group by sellers.id
count() would generate this :
select count(*) as aggregate from sellers
left join locations
on locations.seller_id = sellers.id
group by sellers.id
but what should really generate is this :
SELECT count(*) FROM (
select sellers.* from sellers
left join locations
on locations.seller_id = sellers.id
group by sellers.id) as aggregate ;
If we have 2 sellers the : The first query generated from count() call returns result 1, but there are 2 sellers. The second query returns 2 as it should.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 7
- Comments: 17 (1 by maintainers)
I’ve had the same issue and I’ve just solved it using getCountForPagination().
So I did:
$queryWithGroupBy->getQuery()->getCountForPagination();
The issue with $queryWithGroupBy->get()->count() is that the query gets executed before counting the results, which is useless since you could just do count($queryWithGroupBy->get()).
But if you want to get the number of rows before doing any queries (in my case I needed this number to calculate the query progress time), this is might be the one of the best ways.
@36864 you are 100% but I already know what is happening behind scene, that second count() call adds “count(*) as aggregate_count” to select(), and first count() performs count() on collection, but that is not the question here, only question is if this behaviour is normal :
?
I don’t think so, and I think that 99% people which use above eloquent example expect 10 in second example.
One way to do it is to use DB::raw() expression as per this answer.
It’s cleaner than remove groups and can be done in one line.
I expect a grouped count to return the count of each group. That’s literally what you’re asking the builder to do.
I would not expect the behavior of
Collection::count()
to be the same as that ofBuilder::count()
in this context, much like I don’t expectCollection::groupBy()
to behave likeBuilder::groupBy()
, and I don’t expect the result of callingModel::groupBy()->get()->count()
to have the same result asMode::all()->groupBy()->count()
.While these methods have the same name, they behave very differently, and their behavior is well documented and has been in use for a long time. Changing it now would just cause confusion.
How would I get a grouped count if the method was changed as you’re suggesting? Would I need to add a raw select to my query? That’s inconvenient at best, not to mention a huge breaking change.
No, no no. This question is not about finding appropriate query for that what I want. I want exact this query, and this query works perfectly for me.
Above query is easily generated by laravel eloquent functions and get() at the end
But running count() on the same builder gives me wrong result.
$builder->get()->count() //result here is 10 $builder->count() //result here is 1
How this can be normal ?
There are 2 solutions :
1.eloquent should forbid running count() when groupBy() is applied because the result is wrong 2.or eloquent can change count() function as I described in my first post.
Do you understand now what is the problem ?
PS. If you are confused, I am joining locations because I want sort by locations. address, but that is really not important here…
@36864 ok, then we are not expecting the same thing.
1.I am an expection same value for count() on builder and on collection 2.I am expection exact count() value for builder so I can calculate pagination values(hasMorePages, nextPage, totalPages etc.).
I would be happy with answer “yes, you are right, but this is breaking change we can only change this in laravel 6. x”
@Dylan-DPC I don’t feel this is subjective, eloquent allows using groupBy() with count() but then result is not correct and this is the fact, no subjective…
Is there not a method to remove
groupBy()s
?Right now, I do this:
The problem is when one of the methods that builds the complex query’s joins and wheres also needs a
groupBy
. Then,$complexQuery->count() returns 1, while the number of rows returned by
->pluck()` can be many more.To get past this unfortunate bug that seems to be in a
WONT_FIX
state, I use the following hack:I truly hope that this really helps someone else out.
@timmcleod Thank you for showing me that
->get()->count()
works. My method saves a lot of memory, as it only deals with primary keys and no mass object creation.I’ve encountered this on a few different occasions and it gets me every time (including today). Just noticed that the paginator uses a workaround for this problem @fico7489.
When paginating (is that a word? ha), builder uses
$this->toBase()->getCountForPagination()
to find the total number of records instead of just querying with->count()
. That method checks to see if the query is grouping, and if so, just counts the results usingcount($results)
.So, slightly annoying, but looks like we’re stuck with
->get()->count()
.I feel this is subjective. If I have to do a groupby-count in SQL i would write the same query that eloquent is generating.
I agree it’s not working good with joins and groupBy. I was also facing this problem when building pagination. I just applied all filters in query and then used ->get()->count()
$data = QUERY->get(); $dataCount = $data->count(); $data = $data->splice(OFFSET, LIMIT);
aggregate queries + group by return the aggregate for each group. eg how many locations the first seller has, then how many the second seller has etc etc
your example for what you think it “should” generate isnt even a valid query in default installs of more recent versions of mysql
what it looks like you are trying to do is get the number of sellers, which should be done as
select count(distinct sellers.id) from sellers
(or even justselect count(*) from sellers
as the id should be unique anyway). also as far as i know that left join in your query is useless, you arent using any of the fields from the joined table, and as it is left join its not going to effect the count