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

> Grundlagen und fortgeschrittene Nutzung des Query Builders von Laravel mit DB::table(). Wann Sie direkt SQL bauen und wann Sie Eloquent verwenden.

## Was ist der Query Builder

Der Query Builder von Laravel stellt eine flüssige Schnittstelle zum Aufbauen und Ausführen von Datenbankabfragen bereit. Sie starten mit der Methode `table()` der Fassade `DB` und kombinieren die Abfrage per Method-Chaining.

```php theme={null}
use Illuminate\Support\Facades\DB;

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

Intern werden PDO-Parameterbindings verwendet – SQL-Injection wird also automatisch verhindert.

<Info>
  Der Query Builder funktioniert auf allen von Laravel unterstützten Datenbanken (MySQL, MariaDB, PostgreSQL, SQLite, SQL Server). Der Code bleibt beim Wechsel der Datenbank unverändert.
</Info>

## Wann Query Builder, wann Eloquent

| Situation                                     | Empfehlung    |
| --------------------------------------------- | ------------- |
| Modelle und Beziehungen erforderlich          | Eloquent      |
| Komplexe Aggregationen und Reports            | Query Builder |
| Performance-kritische Massendaten             | Query Builder |
| Einfache Operationen auf bestehenden Tabellen | Query Builder |
| Innerhalb von Migrationen oder Seedern        | Query Builder |

## Daten abfragen

### Alle Datensätze holen

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

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

`get()` liefert eine `Illuminate\Support\Collection` zurück. Jeder Datensatz ist ein PHP-`stdClass`-Objekt.

### Einen Datensatz holen

```php theme={null}
// Ersten Datensatz holen (null, wenn nicht gefunden)
$user = DB::table('users')->where('name', 'Max Mustermann')->first();

// Exception werfen, wenn nicht gefunden (liefert automatisch eine 404)
$user = DB::table('users')->where('name', 'Max Mustermann')->firstOrFail();

// Nur den Wert einer bestimmten Spalte
$email = DB::table('users')->where('name', 'Max Mustermann')->value('email');

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

### Werte einer Spalte als Liste

```php theme={null}
// Collection der E-Mail-Werte
$emails = DB::table('users')->pluck('email');

// Assoziative Collection: Name als Schlüssel, E-Mail als Wert
$emailByName = DB::table('users')->pluck('email', 'name');
```

### Große Datenmengen in Blöcken verarbeiten

```php theme={null}
use Illuminate\Support\Collection;

// In Blöcken von 100 verarbeiten
DB::table('users')->orderBy('id')->chunk(100, function (Collection $users) {
    foreach ($users as $user) {
        // Verarbeitung...
    }
});

// Beim Aktualisieren während des Chunking chunkById verwenden
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>
  Wenn Sie während des Chunking Datensätze aktualisieren oder löschen, verwenden Sie statt `chunk()` immer `chunkById()`. Mit `chunk()` können Datensätze verschoben werden und übersprungen werden.
</Warning>

### Streaming (LazyCollection)

```php theme={null}
DB::table('users')->orderBy('id')->lazy()->each(function (object $user) {
    // Datensatz für Datensatz verarbeiten
});
```

## Aggregate

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

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

### Existenz prüfen

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

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

## SELECT

```php theme={null}
// Spalten auswählen
$users = DB::table('users')
    ->select('name', 'email as user_email')
    ->get();

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

// Spalten nachträglich hinzufügen
$query = DB::table('users')->select('name');
$users = $query->addSelect('age')->get();
```

## WHERE

### Grundlegende Bedingungen

```php theme={null}
// Gleichheit (das `=` kann entfallen)
$users = DB::table('users')->where('votes', 100)->get();

// Vergleichsoperator angeben
$users = DB::table('users')->where('votes', '>=', 100)->get();
$users = DB::table('users')->where('name', 'like', 'Max%')->get();

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

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

### Bedingungen gruppieren

```php theme={null}
use Illuminate\Database\Query\Builder;

// OR-Bedingungen zusammenklammern und mit AND kombinieren
$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}
// 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();

// NULL-Prüfung
$users = DB::table('users')->whereNull('deleted_at')->get();
$users = DB::table('users')->whereNotNull('email_verified_at')->get();
```

### whereLike (Musterabgleich)

```php theme={null}
// Standard: nicht case-sensitiv
$users = DB::table('users')
    ->whereLike('name', '%Mustermann%')
    ->get();

// Case-sensitiv
$users = DB::table('users')
    ->whereLike('name', '%Mustermann%', caseSensitive: true)
    ->get();
```

### whereAny / whereAll (gleiche Bedingung auf mehreren Spalten)

```php theme={null}
// Eine der Spalten erfüllt die LIKE-Bedingung
$users = DB::table('users')
    ->where('active', true)
    ->whereAny(['name', 'email', 'bio'], 'like', '%Laravel%')
    ->get();

// Alle Spalten erfüllen die LIKE-Bedingung
$posts = DB::table('posts')
    ->whereAll(['title', 'content'], 'like', '%Laravel%')
    ->get();
```

### whereNullSafeEquals (NULL-sicherer Gleichheitsvergleich)

`whereNullSafeEquals` und `orWhereNullSafeEquals` behandeln **zwei NULL-Werte als gleich**.

Beim gewöhnlichen `=`-Operator ist `NULL = NULL` `false`, `whereNullSafeEquals` betrachtet `NULL` und `NULL` als gleich. Es entspricht dem Operator `<=>` in MySQL und `IS NOT DISTINCT FROM` in PostgreSQL.

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

// Auch wenn $lastLoginIp null ist, werden Benutzer mit last_login_ip IS NULL gefunden
$users = DB::table('users')
    ->whereNullSafeEquals('last_login_ip', $lastLoginIp)
    ->get();
```

<Info>
  Ein normales `where('column', null)` wird zu `WHERE column IS NULL`; `whereNullSafeEquals('column', $value)` verhält sich hingegen konsistent, egal ob der gebundene Wert `null` ist oder nicht. Besonders praktisch, wenn Nutzereingaben `null` sein können.
</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();

// Mehrere Tabellen joinen
$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();
```

### Subquery-JOIN

```php theme={null}
// JOIN mit einer Subquery
$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();
```

## Sortieren, gruppieren, limitieren

```php theme={null}
// Sortieren
$users = DB::table('users')
    ->orderBy('name', 'asc')
    ->get();

// Nach mehreren Spalten sortieren
$users = DB::table('users')
    ->orderBy('last_name')
    ->orderBy('first_name', 'desc')
    ->get();

// Zufällige Reihenfolge
$users = DB::table('users')->inRandomOrder()->get();

// Gruppieren
$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 und OFFSET
$users = DB::table('users')
    ->skip(10)   // OFFSET
    ->take(5)    // LIMIT
    ->get();
```

## Subqueries

```php theme={null}
// Subquery in einer WHERE-Klausel
$activeUsers = DB::table('users')->select('id')->where('is_active', 1);

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

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

## Raw-Ausdrücke

<Warning>
  Raw-Ausdrücke werden als SQL-String direkt eingebettet. Nutzerinput hier ungeprüft weiterzugeben, birgt SQL-Injection-Risiko. Verwenden Sie unbedingt Bindings.
</Warning>

```php theme={null}
// DB::raw() — beliebigen SQL-Ausdruck einbetten
$users = DB::table('users')
    ->select(DB::raw('count(*) as user_count, status'))
    ->groupBy('status')
    ->get();

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

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

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

// orderByRaw — Raw-Ausdruck in ORDER BY
$orders = DB::table('orders')
    ->orderByRaw('updated_at - created_at DESC')
    ->get();
```

## INSERT / UPDATE / DELETE

### INSERT

```php theme={null}
// Ein Datensatz
DB::table('users')->insert([
    'email' => 'max@example.com',
    'name'  => 'Max Mustermann',
]);

// Mehrere Datensätze
DB::table('users')->insert([
    ['email' => 'max@example.com', 'name' => 'Max Mustermann'],
    ['email' => 'erika@example.com', 'name' => 'Erika Musterfrau'],
]);

// Nach dem Einfügen die AUTO_INCREMENT-ID abrufen
$id = DB::table('users')->insertGetId([
    'email' => 'jan@example.com',
    'name'  => 'Jan Beispiel',
]);
```

### UPSERT (INSERT OR UPDATE)

```php theme={null}
// Aktualisieren, wenn vorhanden; sonst einfügen
DB::table('users')->upsert(
    [
        ['email' => 'max@example.com', 'name' => 'Max Mustermann', 'votes' => 5],
        ['email' => 'erika@example.com', 'name' => 'Erika Musterfrau', 'votes' => 10],
    ],
    uniqueBy: ['email'],    // Spalten für die Duplikatsprüfung
    update: ['name', 'votes'] // Zu aktualisierende Spalten
);
```

### UPDATE

```php theme={null}
// Aktualisieren mit Bedingung
$affected = DB::table('users')
    ->where('id', 1)
    ->update(['name' => 'Max M.', 'updated_at' => now()]);

// Inkrementieren/Dekrementieren
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

```php theme={null}
// Löschen mit Bedingung
$deleted = DB::table('users')->where('status', 'inactive')->delete();

// Gesamte Tabelle leeren (AUTO_INCREMENT wird zurückgesetzt)
DB::table('users')->truncate();
```

## Bedingte Abfragen (`when`)

Für dynamische Bedingungen ist `when()` übersichtlicher.

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

## Debugging

```php theme={null}
// Erzeugten SQL-String anzeigen
$sql = DB::table('users')->where('active', true)->toSql();
// "select * from `users` where `active` = ?"

// SQL und Bindings prüfen
$bindings = DB::table('users')->where('active', true)->getBindings();

// Abfrage ausführen und dumpen (Ausführung fortsetzen)
DB::table('users')->where('active', true)->dump();

// Abfrage ausführen, dumpen und beenden
DB::table('users')->where('active', true)->dd();
```

<Tip>
  `dd()` ist zum Debuggen praktisch, sollte in Produktion aber niemals eingesetzt werden. Sicher ist der Blick auf SQL und Bindings über `toSql()` und `getBindings()`.
</Tip>

## Zusammenfassung

<AccordionGroup>
  <Accordion title="Häufig verwendete Methoden">
    | Methode           | Beschreibung                         |
    | ----------------- | ------------------------------------ |
    | `get()`           | Alle Datensätze abrufen (Collection) |
    | `first()`         | Ersten Datensatz abrufen             |
    | `find($id)`       | Datensatz per ID                     |
    | `value($column)`  | Einzelnen Spaltenwert abrufen        |
    | `pluck($column)`  | Liste der Spaltenwerte               |
    | `count()`         | Anzahl                               |
    | `sum($col)`       | Summe                                |
    | `avg($col)`       | Mittelwert                           |
    | `max($col)`       | Maximum                              |
    | `min($col)`       | Minimum                              |
    | `exists()`        | Existenz prüfen                      |
    | `insert([...])`   | Einfügen                             |
    | `update([...])`   | Aktualisieren                        |
    | `delete()`        | Löschen                              |
    | `chunk($n, fn)`   | Blockweise verarbeiten               |
    | `when($cond, fn)` | Bedingte Abfrage                     |
    | `toSql()`         | Erzeugten SQL anzeigen               |
  </Accordion>

  <Accordion title="Query Builder vs. Eloquent">
    Der Query Builder ist eine niedrigere Ebene als Eloquent und gibt statt Model-Instanzen `stdClass`-Objekte zurück.
    Wenn Sie Beziehungen oder Modell-Events (Observer) nicht benötigen, ist der Query Builder einfacher und schneller.

    ```php theme={null}
    // Eloquent: Gibt User-Instanzen zurück
    $users = User::where('active', true)->get();
    echo $users[0]->name; // User-Objekt

    // Query Builder: Gibt stdClass zurück
    $users = DB::table('users')->where('active', true)->get();
    echo $users[0]->name; // stdClass-Objekt
    ```
  </Accordion>
</AccordionGroup>


## Related topics

- [MongoDB](/de/mongodb.md)
- [Datenbankkonfiguration](/de/database.md)
- [PHP-Attribute](/de/advanced/php-attributes.md)
- [Conditionable-Trait](/de/advanced/conditionable.md)
- [Übersicht der neuen Features in Laravel 13](/de/blog/laravel-13-new-features.md)
