[5.2] Add relationship subquery count#13414
Conversation
|
CS fixed and tests added :) |
|
Would it be possible to have a syntax similar to Article::withCount('comments', 'tags', 'categories')->get()And with additional constraints: Article::withCount(['comments' => function($query) {
$query->where('published_at', '>=', Carbon::now());
}])->get() |
|
Not easily, because you need both the relation name + the attribute name, in addition to an optional closure. Or you need to set a 'default' attribute name, but that might conflict with existing columns. |
|
If I run this query (I tweaked your code slightly): I only get the |
|
Yes that's what I said in the OP:
If at least one column is selected, it ignores the |
|
I think this should be addressed in 5.3. If I call A workaround may be to explicitly call |
|
That was already submitted as PR, but Taylor said the current behaviour of addSelect was correct: #8048 But if we decide to change addSelect in 5.3, we can still pull this in 5.2, and it will be improved in 5.3. |
|
Updated the code to the Article::withCount(['comments' => function($query) {
$query->where('published_at', '>=', Carbon::now());
}])->get()Also checked if columns were set, otherwise add |
|
Thanks |
|
@barryvdh How does this work with this kind of setup... // User.php
class User extends Model
{
// skipped usual model stuff for brevity
public function comments()
{
return $this->hasMany('Comment')->where('status', 1);
}
}// Controller.php
class UserController extends Controller
{
public function index($id)
{
dd(User::withCount('comments')->find($id));
}
}This seems to count ALL comments ignoring the |
|
If that's true that would indeed be a large problem :) |
|
Nice work! Is there a way to get it auto eager loaded? Like with
Is this working? EDITTo answer my own question, there is a |
|
@JayBizzle is that just with the count or also with the 'has' queries? |
|
Submitted a PR to merge the wheres: #13612 |
|
@barryvdh |
|
Yeah I noticed, PR is already submitted with a fix :) (see above) |
|
Yeah, seen that, just testing it locally 👍 |
Fixes missing wheres defined on the relation, see laravel/framework#13414 (comment)
|
Great job. Any updates related to $withCount model property (like @peterpan666 mentioned above)? |
|
Relations with not null deleted_at attribute should also be excluded from count. |
|
Can you submit a PR with tests + fix for that, if you run into it? |
|
@chartspro @barryvdh that works with the latest fix, just hasn't been tagged yet. |
|
Okay good, thought I was still missing something. For softDeletes, these tests should cover it already: 4abb185 |
|
Does this work with nested eager loading? |
|
No, but if you can figure out how to do it nicely, good chance it will get merged ;) |
|
@barryvdh awesome and so needed feature! how about other aggregates, such as |
|
Any news on @chyupa request ? |
|
Is there a way to 'lazy-eager-load' the counts with a It would be useful when doing a |
|
Bump to @terion-name question. |
|
I really like withCount() and it would be awesome to have something like withSum() as well. For example to get a ranking of all transactions I am currently using this: $users = $request->user()
->association
->users()
->withCount(['transactions' => function ($query) {
$query->whereDate('created_at', '>', Carbon::today()->subDays(21));
$query->whereIn('item_id', [100, 102, 103, 109, 111]);
}])
->orderBy('transactions_count', 'desc')
->get();But since a transaction actually has a column called 'amount', it would actually make more sense to weight each transaction individually. Therefore I don't need to count all transactions, but to call the sum(amount) function. $users = $request->user()
->association
->users()
->withSum('transactions', 'amount')
->orderBy('transactions_sum', 'desc')
->get();This would sum the column 'amount' in all transactions. $users = $request->user()
->association
->users()
->withSum(['transactions' => function ($query) {
$query->whereDate('created_at', '>', Carbon::today()->subDays(21));
$query->whereIn('item_id', [100, 102, 103, 109, 111]);
}], 'amount')
->orderBy('transactions_sum', 'desc')
->get();What do you think @terion-name @decadence @barryvdh? |
|
But, is it not dangerous to make a subquery like this? What if you have 1 000 000 clients, this approach will end up doing 1 000 000 queries to projects table, I think it is a bad practice to make subqueries when you don't have control of the number of rows. |
Yes, it might be. That's why it depends on the coder whether to use it in the project or not. In my case I do need it and I am very aware of the "risk" / slightly increased load. I just implemented it and created a PR: #16815. |
|
In my experience using withCount('relation') is really slow if there is a huge amount of related models.... |
|
Hi, @barryvdh! |
This uses the same subquery as the
has()clause, but adds it to theselect. This way the count for a relationship can be loaded in 1 query, without loading the relationship itself.Example:
Resulting query
This will add an attribute
projectCountto each Client object. This can also be used with->having('projectCount', '>=', 1)to avoid using an extrahas()subquery. And also easy ordering in the query:->orderBy('projectCount', 'desc')I know this has been rejected before (#2813), but it seems that most of the 'complicated' logic has been implemented already (subquery selects and relationCountQuery).
The alternatives are:
$client->projects->count()-> Needs to load all relations, even when you don't need them.$client->projects()->count()-> Needs to run 1 query per client, so not good for many rows.Potential problem: Adding a select will stop the default select columns from being added, so an explicit
selectis needed, before doing the count select. Not sure if this needs working around.