Querying a Relationship with Constraints in Laravel: has vs whereHas Explained
Use whereHas to filter parent models by relationship constraints without loading related data, and use withWhereHas when you need both to filter the parent and eager-load the constrained relationship.
Querying a relationship with constraints in Laravel is a common pattern when you need to filter parent models based on conditions in their related data. According to the Laravel framework source code, the methods has, whereHas, and withWhereHas all live in Illuminate\Database\Eloquent\Concerns\QueriesRelationships, but they serve distinct purposes regarding filtering versus data loading.
How has and whereHas Work Internally
Both has and whereHas are defined in Illuminate\Database\Eloquent\Concerns\QueriesRelationships.
The whereHas method is essentially a thin wrapper that forwards all arguments directly to has (lines 70-73 in the source). It exists purely as a more expressive naming convention for applying constraints.
The has method (lines 39-73) builds a sub-query that checks for the existence (or count) of related models. It applies your closure constraints to this existence check, but critically, it does not eager-load the relationship data. The constraints only affect which parent models are returned, not what data is loaded with them.
The Critical Difference: Filtering vs. Eager Loading
When querying a relationship with constraints, you must distinguish between filtering the parent query and loading the related data.
whereHas(andhaswith a closure) only filters. It generates aWHERE EXISTSSQL clause to limit parent results, but the relationship collection remains unloaded unless you separately callwith().with()only loads. It eager-loads the relationship but does not filter the parent models based on whether the relationship exists or matches constraints.
Using whereHas alone means you cannot access the filtered related data without triggering an additional query (N+1 problem) or re-querying.
Using withWhereHas for Filtering and Eager Loading
Laravel provides withWhereHas (lines 86-90 in QueriesRelationships.php) specifically for the scenario where you need to both filter the parent and eager-load the constrained relationship.
This method first calls whereHas to filter the parent models, then immediately adds an eager-load (with) using the same closure constraints. This ensures the relationship data you filtered by is actually available on the returned models without extra queries.
Code Examples
Filtering Parents Only (No Eager Loading)
use App\Models\User;
// Returns only users who have published posts
// Does NOT load the posts relationship
$users = User::whereHas('posts', function ($query) {
$query->where('status', 'published');
})->get();
Equivalent using has:
$users = User::has('posts', '>=', 1, 'and', function ($query) {
$query->where('status', 'published');
})->get();
Filtering and Eager Loading Constrained Data
// Returns users with published posts AND loads only those published posts
$users = User::withWhereHas('posts', function ($query) {
$query->where('status', 'published');
})->get();
foreach ($users as $user) {
// $user->posts contains only published posts, no additional queries executed
echo $user->posts->count();
}
Eager Loading Without Filtering Parents
// Returns ALL users, but only eager-loads published posts
$users = User::with(['posts' => function ($query) {
$query->where('status', 'published');
}])->get();
// Users with no published posts will have empty posts collections
// but are still included in the results
Summary
whereHasfilters parent models using a sub-query but does not load related data. It is implemented inIlluminate\Database\Eloquent\Concerns\QueriesRelationshipsas a proxy to thehasmethod.haswith a closure performs identically towhereHas, checking for existence or count of constrained relationships without eager loading.withWhereHascombines filtering with eager loading, ensuring the constrained relationship data is available on the returned models without additional queries.- Use
whereHaswhen you only need to filter. UsewithWhereHaswhen you need both the filter and the data. Usewithalone when you need the data but not the filter.
Frequently Asked Questions
What is the difference between whereHas and with in Laravel?
whereHas filters the parent query to only return models that have a relationship matching specific constraints, but it does not load the relationship data. with eager-loads the relationship data for all parent models returned, but does not filter the parent query based on whether the relationship exists or matches conditions.
Does whereHas eager load the relationship?
No, whereHas does not eager load the relationship. According to the source code in Illuminate\Database\Eloquent\Concerns\QueriesRelationships, whereHas only constructs a sub-query to check for the existence of related records matching your constraints. The relationship remains unloaded unless you explicitly call with() or use withWhereHas.
When should I use withWhereHas instead of whereHas?
Use withWhereHas when you need to both filter the parent models based on relationship constraints and have the filtered relationship data available on the resulting models. withWhereHas is implemented in QueriesRelationships.php to call whereHas for filtering and then immediately add an eager-load with the same constraints, preventing the N+1 query problem that would occur if you used whereHas alone.
Is there a performance difference between has and whereHas?
There is no performance difference because whereHas is simply a syntactic wrapper that delegates directly to has. Both methods generate identical SQL sub-queries using WHERE EXISTS. The performance impact depends on the complexity of your closure constraints and whether you have proper database indexes on the foreign key and constrained columns, not on which method name you choose.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →