Here is the inconvenient truth about latestOfMany() (\Illuminate\Database\Eloquent\Relations\Concerns\CanBeOneOfMany) at scale, how it can lead to timeouts, and how Maravel-Rest-Wizard can offer an alternative to it (inspired by this issue).
Given this relation definition:
public function latestProduct(): HasOneThrough
{
return $this->products()->one()->latestOfMany();
}
The root of the issue lies in how standard Eloquent eager loads these relationships. When you call Operation::with('latestProduct')the resulting eager loading SQL is:
SELECT
`products`.*,
`operations_products_pivot`.`operation_id` AS `laravel_through_key`
FROM `products`
INNER JOIN `operations_products_pivot`
ON `operations_products_pivot`.`product_id` = `products`.`id`
WHERE `operations_products_pivot`.`operation_id` IN (3748918, 3748911, ...)
AND `products`.`id` IN (
SELECT MAX(`product_id`)
FROM `operations_products_pivot`
-- extra where would go here if the above linked issue would be solved
INNER JOIN `products` AS `sub_products` ON `sub_products`.`id` = `operations_products_pivot`.`product_id`
WHERE `sub_products`.`currency` = 'EUR'
-- end of Fix
GROUP BY `operation_id`
);
In a standard dataset, this could work. But imagine running this query on a table with multiple million rows. In simple words, timeouts will happen sooner or later.
The approach of Maravel-REST-Wizard (via MaravelQL) when a relation is defined based on that aggregated column:
public function latestProduct(): HasOne
{
return $this->hasOne(Product::class, 'id', 'products__id_max');
}
// or segregated relations in Maravel/Maravelith
protected function segregatedRelationsDefinitionMap(): array
{
return [
'products' => fn(): HasManyThrough => $this->hasManyThrough(
Product::class,
OperationProductPivot::class,
'operation_id',
'id',
'id',
'product_id'
),
'latestProduct' => fn(): HasOne => $this->hasOne(Product::class, 'id', 'products__id_max'),
];
}
GET /operations?aggregates[maxsRelations][products][]=id&aggregates[maxsRelationsFilters][products][currency][in][]=EUR&withRelations[]=latestProduct
This translates into:
-- 13.52 ms on 3.7 M rows, sql:
select `operations`.*, (
select max(`products`.`id`)
from `products` inner join `operations_products_pivot`
on `operations_products_pivot`.`product_id` = `products`.`id`
where `operations`.`id` = `operations_products_pivot`.`operation_id`
and `products`.`currency` = 'EUR'
) as `products__id_max` from `operations` order by `created_at` desc limit 10 offset 0;
-- eager load sql:
select * from `products` where `id` in (61107, ...);
and results in:
[{
"id": 3748918,
"parent_id": 3,
"client_id": 68378,
"currency": "EUR",
"value": "27.00",
"created_at": "2024-01-17 11:04:57",
"updated_at": "2026-02-19 16:40:57",
"products__id_max": null,
"latestProduct": null, // or latest_product
"primary_key_identifier": 3748918
},
{
"id": 3748911,
"parent_id": 3,
"client_id": 91963,
"currency": "EUR",
"value": "24.00",
"created_at": "2024-01-17 11:04:57",
"updated_at": "2025-08-04 13:26:03",
"products__id_max": 61107,
"latestProduct": { ... }, // or latest_product
"primary_key_identifier": 3748911
}, ...]

Top comments (0)