A common UI requirement is to display a boolean badge or status indicator based on whether a related record exists. For example, displaying an "Active Subscriber" badge on a user profile, checking whether a post has comments, or checking whether a customer has an unpaid invoice.
Many developers will do a costly eager load of the relationship just to check whether any records exist, or call withCount() just to see if the count is greater than zero. Both are a waste of database resources. Eloquent provides a dedicated method for checking whether a relationship exists, withExists(), which is much more performant.
Table of Contents
The Common (Inefficient) Ways
Here are the two ways developers typically check for relationship existence:
// Bad Approach 1: Eager loading the entire relationship
$users = User::with('orders')->get();
foreach ($users as$user) {
if ($user->orders->isNotEmpty()) {
// Loads thousands of Order model instances into memory just for a true/false check!
}
}
// Bad Approach 2: Using withCount()
$users = User::withCount('orders')->get();
foreach ($users as $user) {
if ($user->orders_count > 0) {
// Forces the database engine to count EVERY single row
}
}
Approach 1 hydrates hundreds of model objects into PHP memory.
Approach 2 makes MySQL count every row, even if an account has 50,000 orders and you only care if they have at least one.
The Efficient Way: withExists()
Laravel's withExists() adds an {relation}_exists boolean attribute directly to each model using a single subquery:
$users = User::withExists('orders')->get();
foreach ($users as $user) {
if ($user->orders_exists) {
echo "User has placed orders.";
}
}
Laravel executes an EXISTS subquery in SQL:
SELECT `users`.*,
EXISTS(
SELECT 1 FROM `orders` WHERE `orders`.`user_id` = `users`.`id`
) AS `orders_exists`
FROM `users`;
The database engine stops scanning the index the moment it finds the first matching row, returning a simple true (1) or false (0).
Adding Conditions and Custom Aliases
You can also pass a closure to constrain the check, or use an alias to give the resulting attribute a clearer name:
$users = User::withExists([
'subscriptions' => function ($query) {$query->where('status', 'active');
},
'orders as has_pending_orders' => function ($query) {$query->where('status', 'pending');
},
])->get();
foreach ($users as$user) {
if ($user->subscriptions_exists) {
// Active subscriber
}
if ($user->has_pending_orders) {
// Pending order exists
}
}
Why withExists() Beats withCount()
| Method | Database Work | PHP Memory | Best Used For |
|---|---|---|---|
with('relation') |
Loads all columns for every row | High (hydrates full models) | When you need to iterate child records. |
withCount('relation') |
Counts every matching row in index | Low (appends integer) | When you need to show numbers like "42 Comments". |
withExists('relation') |
Stops at first match (EXISTS) |
Low (appends boolean) | When you only need a true/false check. |
Summary
Do not load models or run aggregated count queries if you only need to know if a record exists. Instead, you can use the much more efficient withExists() method that will allow the database to return a lightweight boolean flag, saving you precious disk and memory resources.
Thank you for reading this article 😊
For any query, do not hesitate to comment 💬