> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Query Builder

> Utilisation du query builder de Laravel via DB::table(), du niveau de base à avancé, et comparaison avec Eloquent.

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

```php theme={null}
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.

<Info>
  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.
</Info>

## Query Builder vs Eloquent

| Contexte                                     | Recommandation |
| -------------------------------------------- | -------------- |
| Besoin de modèles et de relations            | Eloquent       |
| Agrégations complexes, reporting             | Query Builder  |
| Traitement massif orienté performance        | Query Builder  |
| Opérations simples sur des tables existantes | Query Builder  |
| Migrations, seeders                          | Query Builder  |

## Lecture des données

### Tous les enregistrements

```php theme={null}
$users = DB::table('users')->get();

foreach ($users as $user) {
    echo $user->name;
}
```

`get()` retourne une `Illuminate\Support\Collection` d'objets `stdClass`.

### Un enregistrement

```php theme={null}
// 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

```php theme={null}
// 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

```php theme={null}
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]);
        }
    });
```

<Warning>
  Utilisez `chunkById()` (pas `chunk()`) si vous modifiez/supprimez des enregistrements pendant l'itération, pour éviter les décalages.
</Warning>

### Streaming (LazyCollection)

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

## Agrégations

```php theme={null}
$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

```php theme={null}
if (DB::table('orders')->where('finalized', 1)->exists()) {
    // ...
}

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

## Clause SELECT

```php theme={null}
$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

```php theme={null}
// = 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

```php theme={null}
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

```php theme={null}
$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)

```php theme={null}
// 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

```php theme={null}
$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.

```php theme={null}
$lastLoginIp = $request->input('last_login_ip');

// Fonctionne même si $lastLoginIp est null
$users = DB::table('users')
    ->whereNullSafeEquals('last_login_ip', $lastLoginIp)
    ->get();
```

<Info>
  `where('column', null)` devient `WHERE column IS NULL`. `whereNullSafeEquals` reste cohérent quelle que soit la valeur bindée.
</Info>

## JOIN

```php theme={null}
// 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

```php theme={null}
$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

```php theme={null}
$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

```php theme={null}
// 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

<Warning>
  Les expressions Raw sont insérées telles quelles dans la requête. Ne concaténez jamais l'input utilisateur : utilisez toujours les bindings.
</Warning>

```php theme={null}
$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

```php theme={null}
DB::table('users')->insert([
    'email' => 'yamada@example.com',
    'name'  => 'Taro Yamada',
]);

DB::table('users')->insert([
    ['email' => 'yamada@example.com', 'name' => 'Taro Yamada'],
    ['email' => 'suzuki@example.com', 'name' => 'Hanako Suzuki'],
]);

$id = DB::table('users')->insertGetId([
    'email' => 'sato@example.com',
    'name'  => 'Jiro Sato',
]);
```

### UPSERT

```php theme={null}
DB::table('users')->upsert(
    [
        ['email' => 'yamada@example.com', 'name' => 'Taro Yamada', 'votes' => 5],
        ['email' => 'suzuki@example.com', 'name' => 'Hanako Suzuki', 'votes' => 10],
    ],
    uniqueBy: ['email'],
    update: ['name', 'votes']
);
```

### UPDATE

```php theme={null}
$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

```php theme={null}
$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.

```php theme={null}
$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

```php theme={null}
$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();
```

<Tip>
  `dd()` est utile en dev, mais à proscrire en production. Préférez `toSql()` + `getBindings()`.
</Tip>

## Récapitulatif

<AccordionGroup>
  <Accordion title="Méthodes courantes">
    | Méthode           | Description                     |
    | ----------------- | ------------------------------- |
    | `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é                      |
  </Accordion>

  <Accordion title="Query Builder vs Eloquent">
    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.

    ```php theme={null}
    // 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;
    ```
  </Accordion>
</AccordionGroup>


## Related topics

- [MongoDB](/fr/mongodb.md)
- [Configuration de la base de données](/fr/database.md)
- [Attributs PHP](/fr/advanced/php-attributes.md)
- [Trait ForwardsCalls](/fr/advanced/forwards-calls.md)
- [Trait Conditionable](/fr/advanced/conditionable.md)
