Saltar al contenido principal

Qué es el query builder

El query builder de Laravel es un mecanismo para construir y ejecutar consultas de base de datos mediante una interfaz fluida. Se parte de DB::table() y se encadenan métodos para armar la consulta.
use Illuminate\Support\Facades\DB;

$users = DB::table('users')->get();
Internamente utiliza binding de parámetros de PDO, así que la protección contra inyecciones SQL es automática.
El query builder funciona con todas las bases de datos soportadas por Laravel (MySQL, MariaDB, PostgreSQL, SQLite, SQL Server). Puedes cambiar de motor sin modificar el código.

Query builder frente a Eloquent

SituaciónRecomendado
Necesitas modelos y relacionesEloquent
Agregados o informes complejosQuery builder
Rendimiento crítico con muchos datosQuery builder
Operaciones sencillas sobre tablas existentesQuery builder
Migraciones y seedersQuery builder

Obtener datos

Todos los registros

$users = DB::table('users')->get();

foreach ($users as $user) {
    echo $user->name;
}
get() devuelve una Illuminate\Support\Collection. Cada registro es un stdClass de PHP.

Un solo registro

// Primer registro (null si no lo encuentra)
$user = DB::table('users')->where('name', 'Ana García')->first();

// Lanza excepción (respuesta 404 automática) si no lo encuentra
$user = DB::table('users')->where('name', 'Ana García')->firstOrFail();

// Solo el valor de una columna
$email = DB::table('users')->where('name', 'Ana García')->value('email');

// Búsqueda por ID
$user = DB::table('users')->find(3);

Lista de valores de una columna

// Colección con los valores de email
$emails = DB::table('users')->pluck('email');

// Colección asociativa: name → email
$emailByName = DB::table('users')->pluck('email', 'name');

Procesar grandes volúmenes por partes

use Illuminate\Support\Collection;

// Procesar en bloques de 100
DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {
    foreach ($users as $user) {
        // Procesar...
    }
});

// Cuando modificas datos durante el chunk, usa chunkById
DB::table('users')->where('active', false)
    ->chunkById(100, function (Collection $users) {
        foreach ($users as $user) {
            DB::table('users')
                ->where('id', $user->id)
                ->update(['active' => true]);
        }
    });
Si actualizas o eliminas registros durante el chunk, usa chunkById() en lugar de chunk(). Con chunk() podrían saltarse registros.

Streaming (LazyCollection)

DB::table('users')->orderBy('id')->lazy()->each(function (object $user) {
    // Procesar registro a registro
});

Agregados

$count   = DB::table('users')->count();
$maxAge  = DB::table('users')->max('age');
$minAge  = DB::table('users')->min('age');
$avgAge  = DB::table('users')->avg('age');
$total   = DB::table('orders')->sum('amount');

// Agregado condicional
$avgPremium = DB::table('orders')
    ->where('plan', 'premium')
    ->avg('amount');

Comprobar existencia de registros

if (DB::table('orders')->where('finalized', 1)->exists()) {
    // Existe
}

if (DB::table('orders')->where('finalized', 1)->doesntExist()) {
    // No existe
}

Cláusula SELECT

// Columnas específicas
$users = DB::table('users')
    ->select('name', 'email as user_email')
    ->get();

// Sin duplicados
$users = DB::table('users')->distinct()->get();

// Añadir columnas después
$query = DB::table('users')->select('name');
$users = $query->addSelect('age')->get();

Cláusula WHERE

Condiciones básicas

// Igualdad (el = se puede omitir)
$users = DB::table('users')->where('votes', 100)->get();

// Operador de comparación
$users = DB::table('users')->where('votes', '>=', 100)->get();
$users = DB::table('users')->where('name', 'like', 'Ana%')->get();

// Varias condiciones (AND)
$users = DB::table('users')
    ->where('status', 'active')
    ->where('age', '>', 20)
    ->get();

// OR
$users = DB::table('users')
    ->where('votes', '>', 100)
    ->orWhere('name', 'Ana García')
    ->get();

Agrupar condiciones

use Illuminate\Database\Query\Builder;

// Agrupar OR y combinarlo con AND
$users = DB::table('users')
    ->where('active', true)
    ->where(function (Builder $query) {
        $query->where('role', 'admin')
              ->orWhere('role', 'moderator');
    })
    ->get();
// WHERE active = 1 AND (role = 'admin' OR role = 'moderator')

whereIn / whereBetween / whereNull

// IN
$users = DB::table('users')
    ->whereIn('id', [1, 2, 3])
    ->get();

$users = DB::table('users')
    ->whereNotIn('id', [1, 2, 3])
    ->get();

// BETWEEN
$users = DB::table('users')
    ->whereBetween('age', [20, 40])
    ->get();

// Comprobar NULL
$users = DB::table('users')->whereNull('deleted_at')->get();
$users = DB::table('users')->whereNotNull('email_verified_at')->get();

whereLike (coincidencia de patrón)

// Por defecto no distingue mayúsculas
$users = DB::table('users')
    ->whereLike('name', '%Ana%')
    ->get();

// Distinguir mayúsculas y minúsculas
$users = DB::table('users')
    ->whereLike('name', '%Ana%', caseSensitive: true)
    ->get();

whereAny / whereAll (misma condición en varias columnas)

// Al menos una columna coincide
$users = DB::table('users')
    ->where('active', true)
    ->whereAny(['name', 'email', 'bio'], 'like', '%Laravel%')
    ->get();

// Todas las columnas coinciden
$posts = DB::table('posts')
    ->whereAll(['title', 'content'], 'like', '%Laravel%')
    ->get();

whereNullSafeEquals (igualdad NULL-safe)

whereNullSafeEquals y orWhereNullSafeEquals comparan una columna con un valor tratando dos NULL como iguales. Con el operador = normal, NULL = NULL es false; en cambio whereNullSafeEquals considera NULL igual a NULL. Se corresponde con <=> en MySQL y con IS NOT DISTINCT FROM en PostgreSQL.
$lastLoginIp = $request->input('last_login_ip');

// Aunque $lastLoginIp sea null, encuentra los usuarios con last_login_ip IS NULL
$users = DB::table('users')
    ->whereNullSafeEquals('last_login_ip', $lastLoginIp)
    ->get();
where('column', null) se convierte en WHERE column IS NULL, pero whereNullSafeEquals('column', $value) se comporta de la misma manera independientemente de que el valor bindeado sea null o no. Es especialmente útil cuando el valor de entrada del usuario puede ser null.

JOIN

// INNER JOIN
$users = DB::table('users')
    ->join('orders', 'users.id', '=', 'orders.user_id')
    ->select('users.name', 'orders.amount')
    ->get();

// LEFT JOIN
$users = DB::table('users')
    ->leftJoin('orders', 'users.id', '=', 'orders.user_id')
    ->get();

// JOIN de varias tablas
$users = DB::table('users')
    ->join('contacts', 'users.id', '=', 'contacts.user_id')
    ->join('orders', 'users.id', '=', 'orders.user_id')
    ->select('users.*', 'contacts.phone', 'orders.amount')
    ->get();

JOIN con subconsulta

// JOIN con una subconsulta
$latestOrders = DB::table('orders')
    ->select('user_id', DB::raw('MAX(created_at) as last_order_at'))
    ->groupBy('user_id');

$users = DB::table('users')
    ->joinSub($latestOrders, 'latest_orders', function ($join) {
        $join->on('users.id', '=', 'latest_orders.user_id');
    })
    ->get();

Orden, agrupación y límites

// Ordenar
$users = DB::table('users')
    ->orderBy('name', 'asc')
    ->get();

// Varias columnas
$users = DB::table('users')
    ->orderBy('last_name')
    ->orderBy('first_name', 'desc')
    ->get();

// Aleatorio
$users = DB::table('users')->inRandomOrder()->get();

// Agrupar
$orders = DB::table('orders')
    ->select('status', DB::raw('COUNT(*) as count'))
    ->groupBy('status')
    ->get();

// HAVING
$orders = DB::table('orders')
    ->select('user_id', DB::raw('SUM(amount) as total'))
    ->groupBy('user_id')
    ->having('total', '>', 10000)
    ->get();

// LIMIT y OFFSET
$users = DB::table('users')
    ->skip(10)   // OFFSET
    ->take(5)    // LIMIT
    ->get();

Subconsultas

// Subconsulta en el WHERE
$activeUsers = DB::table('users')->select('id')->where('is_active', 1);

$comments = DB::table('comments')
    ->whereIn('user_id', $activeUsers)
    ->get();

// Subconsulta en el SELECT
$users = DB::table('users')
    ->select('name')
    ->selectSub(function ($query) {
        $query->from('orders')
              ->selectRaw('COUNT(*)')
              ->whereColumn('orders.user_id', 'users.id');
    }, 'order_count')
    ->get();

Expresiones Raw

Las expresiones Raw se insertan literalmente en la consulta. Nunca pases datos del usuario directamente: usa siempre bindings para evitar inyecciones SQL.
// DB::raw() incrusta una expresión SQL arbitraria
$users = DB::table('users')
    ->select(DB::raw('count(*) as user_count, status'))
    ->groupBy('status')
    ->get();

// selectRaw
$orders = DB::table('orders')
    ->selectRaw('price * ? as price_with_tax', [1.10])
    ->get();

// whereRaw
$orders = DB::table('orders')
    ->whereRaw('price > IF(state = "JP", ?, 100)', [500])
    ->get();

// havingRaw
$orders = DB::table('orders')
    ->select('department', DB::raw('SUM(amount) as total'))
    ->groupBy('department')
    ->havingRaw('SUM(amount) > ?', [100000])
    ->get();

// orderByRaw
$orders = DB::table('orders')
    ->orderByRaw('updated_at - created_at DESC')
    ->get();

INSERT / UPDATE / DELETE

INSERT

// Insertar 1 registro
DB::table('users')->insert([
    'email' => '[email protected]',
    'name'  => 'Ana García',
]);

// Insertar varios
DB::table('users')->insert([
    ['email' => '[email protected]', 'name' => 'Ana García'],
    ['email' => '[email protected]', 'name' => 'Luis Pérez'],
]);

// Obtener el ID auto-incremental tras insertar
$id = DB::table('users')->insertGetId([
    'email' => '[email protected]',
    'name'  => 'María López',
]);

UPSERT (INSERT o UPDATE)

// Si existe se actualiza; si no, se inserta
DB::table('users')->upsert(
    [
        ['email' => '[email protected]', 'name' => 'Ana García', 'votes' => 5],
        ['email' => '[email protected]', 'name' => 'Luis Pérez', 'votes' => 10],
    ],
    uniqueBy: ['email'],    // Columna para detectar duplicados
    update: ['name', 'votes'] // Columnas a actualizar
);

UPDATE

// Actualizar con condición
$affected = DB::table('users')
    ->where('id', 1)
    ->update(['name' => 'Ana López', 'updated_at' => now()]);

// Incrementar y decrementar
DB::table('users')->where('id', 1)->increment('votes');       // +1
DB::table('users')->where('id', 1)->increment('votes', 5);    // +5
DB::table('users')->where('id', 1)->decrement('votes');       // -1
DB::table('users')->where('id', 1)->decrement('balance', 100); // -100

DELETE

// Con condición
$deleted = DB::table('users')->where('status', 'inactive')->delete();

// Vaciar toda la tabla (resetea el auto-incremental)
DB::table('users')->truncate();

Consultas condicionales (when)

Para aplicar condiciones dinámicamente, when() mantiene el código limpio.
$status = request('status');
$sortBy = request('sort', 'name');

$users = DB::table('users')
    ->when($status, function ($query, $status) {
        $query->where('status', $status);
    })
    ->when($sortBy === 'email', function ($query) {
        $query->orderBy('email');
    }, function ($query) {
        $query->orderBy('name');
    })
    ->get();

Depuración

// Ver la SQL generada
$sql = DB::table('users')->where('active', true)->toSql();
// "select * from `users` where `active` = ?"

// SQL y bindings
$bindings = DB::table('users')->where('active', true)->getBindings();

// Ejecuta y muestra la query, y continúa
DB::table('users')->where('active', true)->dump();

// Ejecuta, muestra la query y termina
DB::table('users')->where('active', true)->dd();
dd() es útil en depuración, pero no lo uses jamás en producción. toSql() y getBindings() son opciones seguras.

Resumen

MétodoDescripción
get()Todos los registros (Collection)
first()Primer registro
find($id)Por ID
value($column)Un valor de columna
pluck($column)Lista de valores
count()Número de registros
sum($col)Suma
avg($col)Media
max($col)Máximo
min($col)Mínimo
exists()Comprueba existencia
insert([...])Inserta
update([...])Actualiza
delete()Elimina
chunk($n, fn)Procesamiento por bloques
when($cond, fn)Consulta condicional
toSql()SQL generada
El query builder es más bajo nivel: devuelve stdClass en lugar de instancias de modelo. Cuando no necesitas relaciones ni observers, el query builder es más sencillo y rápido.
// Eloquent: devuelve instancias del modelo User
$users = User::where('active', true)->get();
echo $users[0]->name; // Objeto User

// Query builder: devuelve stdClass
$users = DB::table('users')->where('active', true)->get();
echo $users[0]->name; // Objeto stdClass
Última modificación el 13 de julio de 2026