Consultas Eloquent: null, exists, count y selección de columnas

Introducción El ORM Eloquent que viene con Laravel te ofrece una forma sencilla de interactuar con tu base de datos. Simplifica todas las operaciones CRUD (create, read, update y delete) y cualquier otra consulta a la base de datos. En este tutorial aprenderás a comprobar si el valor de una columna concreta es NULL o no con Laravel

Consultas Eloquent: null, exists, count y selección de columnas
Respuesta rápida

Para comprobar un valor nulo, usa whereNull() y whereNotNull(). Para saber si una fila existe, exists() antes que count() > 0: la base de datos devuelve un booleano en lugar de contar. Sobre una colección ya cargada, comprueba isEmpty() y nunca if ($collection), que siempre es verdadero.

Cuatro preguntas vuelven una y otra vez en cuanto escribes consultas Eloquent: si una columna es nula, si un registro existe, cuántos hay y cómo recuperar solo las columnas que necesitas. Esta guía cubre las cuatro, con el SQL que se emite de verdad y las trampas que cuestan una consulta de más.

Este artículo forma parte del itinerario de Desarrollo web. Todos los ejemplos se han ejecutado en Laravel 13.30.1 con PHP 8.4.25, sobre una tabla tasks con las columnas id, title, assignee (nullable), active, priority y las columnas de timestamps, cuya ausencia provoca un error en el primer guardado, y cuya conversión en objetos Carbon es automática.

Comprobar si una columna es nula: whereNull y whereNotNull

En SQL se escribe IS NULL e IS NOT NULL, porque = NULL nunca devuelve verdadero. Eloquent expone ambas condiciones como métodos:

php
// las tareas sin responsable
Task::whereNull('assignee')->get();

// las tareas que sí tienen uno
Task::whereNotNull('assignee')->get();

El SQL que se produce es exactamente el esperado:

sql
select * from "tasks" where "assignee" is not null

Lo mismo funciona sobre el constructor de consultas, sin pasar por un modelo:

php
use Illuminate\Support\Facades\DB;

DB::table('tasks')->whereNull('assignee')->get();

Puedes pasar un array para comprobar varias columnas a la vez, la condición se aplica entonces a todas:

php
Task::whereNull(['assignee', 'closed_at'])->get();
Una idea equivocada que conviene corregir

Se lee a menudo que where('assignee', null) no funciona. Hace mucho que es falso: Laravel detecta el valor nulo y genera is null. Comprobado en Laravel 13, Task::where('assignee', null) produce select * from "tasks" where "assignee" is null y devuelve las mismas filas que whereNull(). Aun así, usa whereNull(): la intención es explícita y no depende de esa normalización.

Las variantes siguen la lógica habitual del constructor, con or y con la negación:

php
Task::whereNull('assignee')->orWhereNotNull('closed_at')->get();

Comprobar si un registro existe

Para saber si hay una fila que coincide, no vayas a buscarla. exists() le pide a la base de datos una respuesta booleana:

php
if (Task::where('title', 'Publier')->exists()) {
    // al menos una fila coincide
}

if (Task::where('title', 'Publier')->doesntExist()) {
    // ninguna fila
}

La diferencia con count() se ve en el registro de consultas. Esto es lo que las tres formas envían realmente a la base de datos:

sql
-- exists()
select exists(select * from "tasks" where "active" = ?) as "exists"

-- count() > 0
select count(*) as "aggregate" from "tasks" where "active" = ?

-- get()->isNotEmpty()
select * from "tasks" where "active" = ?

Las tres responden a la pregunta, pero la tercera se trae todas las filas y las hidrata como objetos para tirar el contenido justo después. En una tabla grande, esa es la diferencia entre una consulta de coste constante y una que crece con los datos.

Crear solo si el registro no existe

La pareja «compruebo y luego inserto» tiene nombre: firstOrCreate(). El primer array sirve para buscar, el segundo aporta los valores que se usan únicamente al crear.

php
$post = Post::firstOrCreate(
    ['slug' => $slug],                                  // criterio de búsqueda
    ['title' => $title, 'body' => $body],               // solo se usa al crear
);

Si la fila ya existe, el segundo array se ignora: el registro se devuelve tal cual, sin escritura. Dos variantes completan la familia:

php
// misma lógica, pero sin guardar en la base de datos
$post = Post::firstOrNew(['slug' => $slug], ['title' => $title]);
$post->exists;   // false mientras no llames a save()

// encuentra y actualiza, o crea
$post = Post::updateOrCreate(['slug' => $slug], ['title' => $title]);
No es atómico

firstOrCreate() ejecuta un SELECT y después un INSERT. Dos peticiones simultáneas pueden pasar el SELECT a la vez e intentar dos inserciones. Pon una restricción de unicidad en la base de datos sobre la columna en cuestión, mediante una migración: es ella, y no el código PHP, la que garantiza la unicidad.

Contar y detectar una colección vacía

Hay que distinguir dos count(). Sobre el constructor de consultas es un COUNT(*) que ejecuta la base de datos. Sobre una colección ya cargada es un simple recuento en memoria.

php
// COUNT(*) en la base de datos, ninguna fila traída
Task::where('active', true)->count();      // 3

// las filas se cargan y luego se cuentan en PHP
$tasks = Task::where('active', true)->get();
$tasks->count();                           // 3

Si de todos modos necesitas las filas, la segunda forma es la buena: no lances otra consulta de recuento. Si solo quieres el número, quédate con la primera.

Para comprobar si una colección está vacía, dos métodos explícitos:

php
$tasks = Task::where('title', 'Inexistant')->get();

$tasks->isEmpty();      // true
$tasks->isNotEmpty();   // false
Una colección vacía no es falsy

if ($tasks) siempre es verdadero, incluso cuando la colección no contiene nada: es un objeto. La comprobación debe apoyarse en isEmpty(), isNotEmpty() o blank(). Es el error más frecuente de esta sección.

Fíjate de paso en que isEmpty() no acepta ningún argumento. Se ve a menudo $posts->isEmpty($posts), que funciona por accidente porque el argumento se ignora.

En una vista Blade, @forelse resuelve el caso vacío sin condición explícita:

resources/views/tasks/index.blade.php
@forelse ($tasks as $task)
    <li>{{ $task->title }}</li>
@empty
    <li>Aucune tâche pour le moment.</li>
@endforelse

Seleccionar solo las columnas útiles

Por defecto, Eloquent hace un select *. En una tabla ancha, o cuando solo muestras dos campos, es transferencia e hidratación para nada. Hay tres formas de restringir las columnas:

php
// 1. select() en el constructor de consultas
Task::select('title', 'priority')->where('active', true)->get();

// 2. como argumento de get() o de first()
Task::where('active', true)->get(['title']);
Task::where('assignee', 'Damien')->first(['title', 'priority']);

// 3. añadiendo a una selección existente
Task::select('id')->addSelect('title')->first();

Cuando solo quieres un valor, dos métodos evitan pasar por un modelo completo:

php
// un solo valor escalar
Task::where('assignee', 'Damien')->value('title');   // 'Écrire le guide'

// una lista de valores
Task::where('active', true)->pluck('title');         // ['Écrire le guide', 'Relire']

// una lista indexada por otra columna
Task::pluck('title', 'id');                          // [1 => 'Écrire le guide', …]

value() sustituye con ventaja al first(['title'])->title que se escribe por reflejo: devuelve directamente la cadena, y null si no coincide ninguna fila, mientras que la otra forma provoca un error sobre un objeto nulo.

Una columna no seleccionada no es nula, es que no está

Después de un first(['title']), el atributo priority no existe en el modelo. Acceder a él devuelve null sin error, lo que oculta el problema hasta el momento de mostrarlo. Acuérdate también de incluir la clave primaria en tu selección: sin id, las relaciones y save() ya no pueden localizar la fila.

Errores frecuentes

if ($collection) siempre es verdadero Una colección vacía sigue siendo un objeto, así que se evalúa como verdadera. Comprueba isEmpty(), isNotEmpty() o blank().nfirstOrCreate() no es atómico | Un SELECT y después un INSERT: dos peticiones concurrentes pueden insertar dos veces. Añade una restricción de unicidad en la base de datos.
Una columna no seleccionada no está, no es nula Después de first(['title']), leer priority devuelve null sin error. Incluye siempre la clave primaria, si no, las relaciones y save() dejan de funcionar.nget()->count() lo trae todo | Si solo necesitas el número, usa count() sobre el constructor: es un COUNT(*) que no se trae ninguna fila.nisEmpty() no acepta argumentos | $posts->isEmpty($posts) se ve a menudo y funciona por accidente, porque el argumento se ignora.

EloquentLaravelPHPSQL

Damien Flandrin Desarrollador web desde 2010, creador de Gekkode y de Email Impact. Cada artículo se prueba en un proyecto real antes de publicarse. Contacto
Newsletter

Las nuevas pruebas, tutoriales y proyectos, por correo.

Pruebas reproducibles, código versionado, resultados fechados. Nunca spam.