# Querying a Relationship with Constraints in Laravel: has vs whereHas Explained

> Master Laravel query constraints: Understand has vs whereHas. Filter parents with whereHas, or filter & eager load with withWhereHas. Optimize your queries now.

- Repository: [Laravel/framework](https://github.com/laravel/framework)
- Tags: deep-dive
- Published: 2026-02-18

---

**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`** (and `has` with a closure) only filters. It generates a `WHERE EXISTS` SQL clause to limit parent results, but the relationship collection remains unloaded unless you separately call `with()`.
- **`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`](https://github.com/laravel/framework/blob/main/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)

```php
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`:

```php
$users = User::has('posts', '>=', 1, 'and', function ($query) {
    $query->where('status', 'published');
})->get();

```

### Filtering and Eager Loading Constrained Data

```php
// 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

```php
// 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

- **`whereHas`** filters parent models using a sub-query but does not load related data. It is implemented in `Illuminate\Database\Eloquent\Concerns\QueriesRelationships` as a proxy to the `has` method.
- **`has`** with a closure performs identically to `whereHas`, checking for existence or count of constrained relationships without eager loading.
- **`withWhereHas`** combines filtering with eager loading, ensuring the constrained relationship data is available on the returned models without additional queries.
- Use `whereHas` when you only need to filter. Use `withWhereHas` when you need both the filter and the data. Use `with` alone 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`](https://github.com/laravel/framework/blob/main/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.