Model::count
Runs a SQL COUNT for the query and returns the number of matching rows as an integer.
Model::count(Illuminate\Contracts\Database\Query\Expression|string $columns = '*'): intParameters
| Parameter | Type | Description |
|---|---|---|
$$columnsoptional | Illuminate\Contracts\Database\Query\Expression|string= '*' | The column to count. Counting a specific column ignores rows where that column is null; the default counts every matching row. |
Return value
intThe number of rows matching the query, as an integer of zero or more.
count compiles the query you have built into a select count(*) and returns a plain integer. No models are hydrated, so the cost is one round trip regardless of how many rows match. Its siblings sum, avg (aliased average), min and max work the same way but take a required column name.
When to reach for it
Reach for it whenever you want a number rather than records: a dashboard tile, a guard clause, a report row. The mistake it exists to prevent is Flight::all()->count(), which loads every row into memory to produce one integer.
What trips people up
Two things. First, if you only want to know whether anything matches, exists() and doesntExist() are cheaper — the database can stop at the first hit instead of scanning to total everything up.
Second, the return types are not uniform across the aggregates. count always gives an int, and sum falls back to 0 when nothing matches, but avg, min and max return null on an empty result set. Feeding that null into number_format is a very common warning.
Example
<?php
use App\Models\Flight;
$total = Flight::count();
$active = Flight::where('active', 1)->count();
$max = Flight::where('active', 1)->max('price');
$revenue = Flight::where('active', 1)->sum('price');
echo $total; // 1500
echo $active; // 1200
echo $max; // 899.00
echo $revenue; // 412300.00
var_dump($total); // int(1500)When to use it
- Render a dashboard tile showing how many orders were placed today.
- Assert in a test that a job created exactly the expected number of rows.
- Total a column with sum() for an invoice line without pulling the rows into PHP.
- Report the highest and lowest price in a category with max() and min().
Frequently asked questions
How do I count rows in Laravel without loading the models?
Call count() on the query rather than on the result: Flight::where('active', 1)->count() sends a select count(*) to the database and returns an integer. Flight::all()->count() hydrates every row into memory first.
Why does count() return the wrong number with groupBy?
Because Laravel reads the aggregate value from the first returned row, and a grouped query returns one row per group. To count the groups, run get() and count the resulting collection, or count over a subquery.
Should I use count() > 0 or exists() in Laravel?
Use exists() — it lets the database stop at the first matching row, while count() has to total them all. Use count() only when you actually need the number.
More examples
Use exists() when you only need a yes or no
Both branches print the same thing, so the two forms look interchangeable. They are not: count() must total every matching row, while exists() compiles to a query the database can abandon after the first hit. On a user with fifty thousand orders that is the difference between a full index scan and a single seek.
<?php
use App\Models\Order;
// Counts every matching row, then compares to zero.
if (Order::where('user_id', 7)->count() > 0) {
echo "has orders\n";
}
// Stops at the first match.
if (Order::where('user_id', 7)->exists()) {
echo "has orders\n";
}
if (Order::where('user_id', 7)->doesntExist()) {
echo "no orders\n";
}Empty result sets return different types per aggregate
count() and sum() are safe to print directly because both floor at zero — sum() explicitly converts a null result to 0. avg(), max() and min() hand back null, which is why formatting an average without a null coalesce is such a frequent source of deprecation warnings.
<?php
use App\Models\Flight;
// Assume no flights match this condition at all.
$query = Flight::where('destination', 'Nowhere');
var_dump($query->count()); // int(0)
var_dump($query->sum('price')); // int(0)
var_dump($query->avg('price')); // NULL
var_dump($query->max('price')); // NULL
var_dump($query->min('price')); // NULLCounting related rows without N+1 queries
withCount adds a comments_count attribute using a single correlated subquery, so the loop costs two queries in total. The second loop calls count() on a lazily loaded relation, which issues a query for every post — the classic N+1 that shows up in Telescope as hundreds of near-identical selects.
<?php
use App\Models\Post;
// One extra subquery for the whole result set.
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
echo $post->title . ': ' . $post->comments_count . "\n";
}
// This runs one count query per post instead.
foreach (Post::all() as $post) {
echo $post->comments->count() . "\n";
}
Discussion