-
Notifications
You must be signed in to change notification settings - Fork 11.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[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
projectCount
to 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
select
is needed, before doing the count select. Not sure if this needs working around.