Not really doing anything wrong here, it's just the basics of Laravel, hitting a very big pile of data.
whereHas is checking the existence of the relationship, which is done with the query:
select * from `especies` inner join `especie_zona` on `especies`.`id` = `especie_zona`.`especie_id` where `zonas`.`id` = `especie_zona`.`zona_id` and `id` = '3041'
So especies * especie_zona ~= 100.000 * 400 = 40.000.000 rows to check after this join.
It might get a lot better if you put in indexing on those 2 fields (if there aren't yet):
- especie_zona.especie_id
- especie_zona.zona_id
If there was already an index, and it's still slow, you might want to do the query a bit differently by doing 2 single select queries instead of the subquery with join:
$zoneIds = \DB::table('especie_zona')->select('zona_id')->where('especie_id', $especieId)->get()->pluck('zona_id');
$zonasMarcadas = Zona::whereIn('id', $zoneIds)->get();
If that works ok, you could put it in a query scope for reusability:
// Zona.php
public static method scopeHavingEspecie($query, $especieId)
{
$zoneIds = \DB::table('especie_zona')->select('zona_id')->where('especie_id', $especieId)->get()->pluck('zona_id');
return $query->whereIn('id', $zoneIds);
}
// usage:
$zonasMarcadas = Zona::havingEspecie(85)->get();