framework: Constraints on eager-loaded hasMany doesn't retrieve data correctly
- Laravel Version: 5.2.x
- PHP Version: 7
- Database Driver & Version: MySQL 5.7
Description:
Constraint on eager-load only retrieves 1 record, when ->take(10) is specified.
Steps To Reproduce:
- Each User model has their instagram data updated daily, and inserted into an AccountData model: User::hasMany(AccountData::class);
- User has at least 400 sibling update records inserted. Going through every user to perform updates kills memory, some have up to 2000. Only the last/previous record is needed, so the hasMany relation is constrained to take only the last 10 or so (to be safe).
Example code, before constraint:
User::with(['account_data'])->chunk(10, function ($users) {
foreach ($users as $user) {
// do something with $user->account_data->last().
// could have 400+ account_data
}
}
This will produce 400 records in the hasMany relation.
Then with optimized version:
User::with([
'account_data' => function($query) {
$query->orderBy('created_at', 'DESC')->take(10);
}])->chunk(10, function ($users) {
foreach ($users as $user) {
// zero records, 1 record, or the first ever record
// account_data->count() is 1
}
}
This is expected to eager load the previous 10 records for each user, but only ever returns a) no records, or b) the first ever record. The result is unpredictable.
When manually pulling the user’s relation count, the results are consistent:
// test here is:
$user->account_data->count(); // eager-loaded
$user->account_data()->count(); // go to db
Eager-loaded count is 1 data records.
Forced count from DB is 3 records.
Eager-loaded count is 0 data records.
Forced count from DB is 0 records.
Eager-loaded count is 0 data records.
Forced count from DB is 0 records.
Eager-loaded count is 4 data records.
Forced count from DB is 552 records.
Eager-loaded count is 1 data records.
Forced count from DB is 3 records.
Eager-loaded count is 1 data records.
Forced count from DB is 29 records.
Eager-loaded count is 0 data records.
Forced count from DB is 0 records.
Edit: SQL generated by DB::getQueryLog() is:
array (
'query' => 'select * from "account_data" where "account_data"."user_id" in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) and "account_data"."deleted_at" is null order by "created_at" desc limit 10',
'bindings' =>
array (
0 => 128,
1 => 115,
2 => 116,
3 => 48,
4 => 129,
5 => 130,
6 => 69,
7 => 125,
8 => 66,
9 => 120,
),
'time' => 39.350000000000001,
),
Say there are 1000 users being chunked - appears that the eager load constraint is being applied to the child records, rather than for each user individually, i.e. to gets the last 10 records from the hasMany relation for each parent item.
Is this a case of me not applying the constraint correctly?
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 1
- Comments: 20 (13 by maintainers)
@giovannipds Your issue is not related to this thread.
It’s not possible to use a relationship with an
orWhere()
like this.@azcoppen @themsaid I’ve updated the modified query builder to handle the belongToMany relationship. Not sure if this takes care of the remaining relationships or not… just using it for my own use case (GraphQL). I’m also not a fan of using the
ReflectionMethod
, but since the methods I need access to are protected, it seemed like the lesser of two evils (rather than refactoring theBelongToMany
relationship).If I can find some time this weekend, I’ll create some tests and check out the other relationships to see if everything still works and possibly submit a PR. Not sure what to do about SQLite in this case though since it won’t accept the supplied query.
Usage
Collection Macro
Updated Query Builder
@derekmd Naturally, I think that’s a good idea. Would you write it?
We could also throw an exception (that links to the documentation) if someone tries to apply a limit to their relationship when eager loading.
Duplicate issues:
As noted by @staudenmeir, his pull request was declined by Taylor due to the maintenance burden for a solution not supported by all databases that Laravel projects run.
Since this problem has come up so much and it can now be fixed by a third-party package, maybe the best solution is to update https://laravel.com/docs/5.7/eloquent-relationships#eager-loading under heading “Constraining Eager Loads” to clearly communicate that
limit()
andtake()
aren’t supported unless that package is installed and MySQL, etc. are the most recent version?I released the rejected PR as a package: https://github.com/staudenmeir/eloquent-eager-limit
OMG… Why this issue has not been fixed yet??
It has been 2 years…