
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:
// 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:
select * from "tasks" where "assignee" is not nullLo mismo funciona sobre el constructor de consultas, sin pasar por un modelo:
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:
Task::whereNull(['assignee', 'closed_at'])->get();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:
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:
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:
-- 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.
$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:
// 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]);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.
// 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(); // 3Si 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:
$tasks = Task::where('title', 'Inexistant')->get();
$tasks->isEmpty(); // true
$tasks->isNotEmpty(); // falseif ($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:
@forelse ($tasks as $task)
<li>{{ $task->title }}</li>
@empty
<li>Aucune tâche pour le moment.</li>
@endforelseSeleccionar 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:
// 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:
// 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.
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
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.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.

