Model::count

Runs a SQL COUNT for the query and returns the number of matching rows as an integer.

Signature
Model::count(Illuminate\Contracts\Database\Query\Expression|string $columns = '*'): int

Parameters

ParameterTypeDescription
$$columnsoptionalIlluminate\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

Example · php
<?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.

Example · php
<?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.

Example · php
<?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'));   // NULL

Counting 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.

Example · php
<?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

  • Be the first to comment on this lesson.