Passer au contenu principal

Qu’est-ce que le Query Builder

Le query builder de Laravel construit et exécute les requêtes SQL via une interface fluent. Point de départ : DB::table(), suivi d’un chaînage de méthodes.
use Illuminate\Support\Facades\DB;

$users = DB::table('users')->get();
En interne, les paramètres sont liés via PDO, ce qui prévient automatiquement les injections SQL.
Le query builder fonctionne avec toutes les bases prises en charge par Laravel (MySQL, MariaDB, PostgreSQL, SQLite, SQL Server). Le code reste identique quand vous changez de base.

Query Builder vs Eloquent

ContexteRecommandation
Besoin de modèles et de relationsEloquent
Agrégations complexes, reportingQuery Builder
Traitement massif orienté performanceQuery Builder
Opérations simples sur des tables existantesQuery Builder
Migrations, seedersQuery Builder

Lecture des données

Tous les enregistrements

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

foreach ($users as $user) {
    echo $user->name;
}
get() retourne une Illuminate\Support\Collection d’objets stdClass.

Un enregistrement

// Premier résultat (null si absent)
$user = DB::table('users')->where('name', 'Taro Yamada')->first();

// Exception si absent (404 automatique)
$user = DB::table('users')->where('name', 'Taro Yamada')->firstOrFail();

// Une seule colonne
$email = DB::table('users')->where('name', 'Taro Yamada')->value('email');

// Par ID
$user = DB::table('users')->find(3);

Liste d’une colonne

// Collection des valeurs de la colonne email
$emails = DB::table('users')->pluck('email');

// Tableau associatif name → email
$emailByName = DB::table('users')->pluck('email', 'name');

Traitement par lots

use Illuminate\Support\Collection;

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

// Utiliser chunkById quand on met à jour pendant l'itération
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]);
        }
    });
Utilisez chunkById() (pas chunk()) si vous modifiez/supprimez des enregistrements pendant l’itération, pour éviter les décalages.

Streaming (LazyCollection)

DB::table('users')->orderBy('id')->lazy()->each(function (object $user) {
    // Traitement un par un
});

Agrégations

$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');

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

Existence

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

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

Clause SELECT

$users = DB::table('users')
    ->select('name', 'email as user_email')
    ->get();

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

$query = DB::table('users')->select('name');
$users = $query->addSelect('age')->get();

Clause WHERE

Conditions de base

// = par défaut
$users = DB::table('users')->where('votes', 100)->get();

// Opérateurs
$users = DB::table('users')->where('votes', '>=', 100)->get();
$users = DB::table('users')->where('name', 'like', 'Yamada%')->get();

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

// OR
$users = DB::table('users')
    ->where('votes', '>', 100)
    ->orWhere('name', 'Taro Yamada')
    ->get();

Groupement de conditions

use Illuminate\Database\Query\Builder;

$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

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

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

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

whereLike (pattern matching)

// Insensible à la casse par défaut
$users = DB::table('users')
    ->whereLike('name', '%Yamada%')
    ->get();

// Sensible à la casse
$users = DB::table('users')
    ->whereLike('name', '%Yamada%', caseSensitive: true)
    ->get();

whereAny / whereAll

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

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

whereNullSafeEquals

whereNullSafeEquals traite NULL = NULL comme vrai (contrairement à = classique). Équivalent à <=> sous MySQL, IS NOT DISTINCT FROM sous PostgreSQL.
$lastLoginIp = $request->input('last_login_ip');

// Fonctionne même si $lastLoginIp est null
$users = DB::table('users')
    ->whereNullSafeEquals('last_login_ip', $lastLoginIp)
    ->get();
where('column', null) devient WHERE column IS NULL. whereNullSafeEquals reste cohérent quelle que soit la valeur bindée.

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();

// Plusieurs tables
$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 par sous-requête

$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();

ORDER BY / GROUP BY / LIMIT

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

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

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

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

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

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

Sous-requêtes

// Sous-requête WHERE
$activeUsers = DB::table('users')->select('id')->where('is_active', 1);

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

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

Expressions Raw

Les expressions Raw sont insérées telles quelles dans la requête. Ne concaténez jamais l’input utilisateur : utilisez toujours les bindings.
$users = DB::table('users')
    ->select(DB::raw('count(*) as user_count, status'))
    ->groupBy('status')
    ->get();

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

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

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

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

INSERT / UPDATE / DELETE

INSERT

DB::table('users')->insert([
    'email' => '[email protected]',
    'name'  => 'Taro Yamada',
]);

DB::table('users')->insert([
    ['email' => '[email protected]', 'name' => 'Taro Yamada'],
    ['email' => '[email protected]', 'name' => 'Hanako Suzuki'],
]);

$id = DB::table('users')->insertGetId([
    'email' => '[email protected]',
    'name'  => 'Jiro Sato',
]);

UPSERT

DB::table('users')->upsert(
    [
        ['email' => '[email protected]', 'name' => 'Taro Yamada', 'votes' => 5],
        ['email' => '[email protected]', 'name' => 'Hanako Suzuki', 'votes' => 10],
    ],
    uniqueBy: ['email'],
    update: ['name', 'votes']
);

UPDATE

$affected = DB::table('users')
    ->where('id', 1)
    ->update(['name' => 'Ichiro Yamada', 'updated_at' => now()]);

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

DELETE

$deleted = DB::table('users')->where('status', 'inactive')->delete();

DB::table('users')->truncate();

Requête conditionnelle (when)

Applique une condition dynamiquement sans casser le chaînage.
$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();

Débogage

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

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

DB::table('users')->where('active', true)->dump();

DB::table('users')->where('active', true)->dd();
dd() est utile en dev, mais à proscrire en production. Préférez toSql() + getBindings().

Récapitulatif

MéthodeDescription
get()Tous les résultats (Collection)
first()Premier résultat
find($id)Par ID
value($column)Valeur d’une colonne
pluck($column)Liste des valeurs
count()Comptage
sum($col)Somme
avg($col)Moyenne
max($col)Maximum
min($col)Minimum
exists()Existence
insert([...])Insertion
update([...])Mise à jour
delete()Suppression
chunk($n, fn)Traitement par lots
when($cond, fn)Condition dynamique
toSql()SQL généré
Le Query Builder est plus bas niveau et retourne stdClass, non des instances de modèle. Sans besoin de relations ou d’événements (observers), il est plus simple et plus rapide.
// Eloquent : instance User
$users = User::where('active', true)->get();
echo $users[0]->name;

// Query Builder : stdClass
$users = DB::table('users')->where('active', true)->get();
echo $users[0]->name;
Dernière modification le 13 juillet 2026