Eloquent: null prüfen, Existenz, zählen und Spalten wählen

Der Eloquent-ORM von Laravel macht den Umgang mit der Datenbank einfach. Dieses Tutorial zeigt, wie du prüfst, ob eine Spalte null ist, ob ein Datensatz existiert, wie du zählst und wie du nur die nötigen Spalten lädst.

Eloquent: null prüfen, Existenz, zählen und Spalten wählen
Schnelle Antwort

Für einen Nullwert nimmst du whereNull() und whereNotNull(). Ob eine Zeile existiert, klärt exists() statt count() > 0: Die Datenbank liefert einen booleschen Wert, statt zu zählen. Auf einer bereits geladenen Collection prüfst du isEmpty() und niemals if ($collection), was immer wahr ist.

Vier Fragen tauchen beim Schreiben von Eloquent-Abfragen immer wieder auf: Ist diese Spalte null, existiert dieser Datensatz, wie viele sind es, und wie holt man nur die Spalten, die man wirklich braucht? Dieser Leitfaden behandelt alle vier, mit dem tatsächlich abgesetzten SQL und den Fallen, die eine Abfrage zu viel kosten.

Dieser Artikel gehört zum Lernpfad Webentwicklung. Alle Beispiele liefen auf Laravel 13.30.1 mit PHP 8.4.25, auf einer Tabelle tasks mit den Spalten id, title, assignee (nullable), active, priority und den Timestamp-Spalten, deren Fehlen beim ersten Speichern einen Fehler auslöst und deren Umwandlung in Carbon-Objekte automatisch geschieht.

Prüfen, ob eine Spalte null ist: whereNull und whereNotNull

In SQL schreibt man IS NULL und IS NOT NULL, weil = NULL nie wahr ergibt. Eloquent stellt beide Bedingungen als Methoden bereit:

php
// die Aufgaben ohne Verantwortlichen
Task::whereNull('assignee')->get();

// die Aufgaben, die einen haben
Task::whereNotNull('assignee')->get();

Das erzeugte SQL ist genau das erwartete:

sql
select * from "tasks" where "assignee" is not null

Dasselbe funktioniert auf dem Query Builder, ganz ohne Modell:

php
use Illuminate\Support\Facades\DB;

DB::table('tasks')->whereNull('assignee')->get();

Du kannst ein Array übergeben, um mehrere Spalten auf einmal zu prüfen, die Bedingung gilt dann für alle:

php
Task::whereNull(['assignee', 'closed_at'])->get();
Ein Irrtum, der sich hartnäckig hält

Oft liest man, where('assignee', null) funktioniere nicht. Das ist seit Langem falsch: Laravel erkennt den Nullwert und erzeugt is null. Auf Laravel 13 geprüft, ergibt Task::where('assignee', null) genau select * from "tasks" where "assignee" is null und liefert dieselben Zeilen wie whereNull(). Nimm trotzdem lieber whereNull(): Die Absicht steht damit ausdrücklich im Code und hängt nicht an dieser Normalisierung.

Die Varianten folgen der gewohnten Logik des Builders, mit or und der Negation:

php
Task::whereNull('assignee')->orWhereNotNull('closed_at')->get();

Prüfen, ob ein Datensatz existiert

Um zu wissen, ob eine Zeile passt, musst du sie nicht holen. exists() lässt die Datenbank eine boolesche Antwort liefern:

php
if (Task::where('title', 'Publier')->exists()) {
    // mindestens eine Zeile passt
}

if (Task::where('title', 'Publier')->doesntExist()) {
    // keine Zeile
}

Der Unterschied zu count() zeigt sich im Query-Log. Das schicken die drei Schreibweisen tatsächlich an die Datenbank:

sql
-- 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" = ?

Alle drei beantworten die Frage, aber die dritte holt sämtliche Zeilen und hydriert sie zu Objekten, nur um den Inhalt sofort wegzuwerfen. Auf einer großen Tabelle ist das der Unterschied zwischen einer Abfrage mit konstanten Kosten und einer, die mit den Daten mitwächst.

Nur anlegen, wenn der Datensatz nicht existiert

Das Paar „erst prüfen, dann einfügen“ hat einen Namen: firstOrCreate(). Das erste Array dient der Suche, das zweite liefert die Werte, die nur beim Anlegen verwendet werden.

php
$post = Post::firstOrCreate(
    ['slug' => $slug],                                  // Suchkriterium
    ['title' => $title, 'body' => $body],               // nur beim Anlegen verwendet
);

Existiert die Zeile bereits, wird das zweite Array ignoriert: Der Datensatz kommt unverändert zurück, ohne Schreibzugriff. Zwei Varianten ergänzen die Familie:

php
// gleiche Logik, aber ohne in der Datenbank zu speichern
$post = Post::firstOrNew(['slug' => $slug], ['title' => $title]);
$post->exists;   // false, solange du save() nicht aufrufst

// findet und aktualisiert, oder legt an
$post = Post::updateOrCreate(['slug' => $slug], ['title' => $title]);
Das ist nicht atomar

firstOrCreate() führt ein SELECT und danach ein INSERT aus. Zwei gleichzeitige Anfragen können das SELECT zur selben Zeit passieren und beide einfügen wollen. Setz über eine Migration einen Unique-Constraint auf die betroffene Spalte: Er garantiert die Eindeutigkeit, nicht der PHP-Code.

Zählen und eine leere Collection erkennen

Es gibt zwei count(), die du auseinanderhalten musst. Auf dem Query Builder ist es ein COUNT(*), das die Datenbank ausführt. Auf einer bereits geladenen Collection ist es schlicht ein Zählen im Speicher.

php
// COUNT(*) in der Datenbank, keine Zeile wird geladen
Task::where('active', true)->count();      // 3

// die Zeilen werden geladen und danach in PHP gezählt
$tasks = Task::where('active', true)->get();
$tasks->count();                           // 3

Brauchst du die Zeilen ohnehin, ist die zweite Form die richtige: Setz keine zusätzliche Zählabfrage ab. Willst du nur die Anzahl, nimm die erste.

Ob eine Collection leer ist, prüfen zwei ausdrückliche Methoden:

php
$tasks = Task::where('title', 'Inexistant')->get();

$tasks->isEmpty();      // true
$tasks->isNotEmpty();   // false
Eine leere Collection ist nicht falsy

if ($tasks) ist immer wahr, auch wenn die Collection nichts enthält: Sie ist ein Objekt. Der Test muss über isEmpty(), isNotEmpty() oder blank() laufen. Das ist der häufigste Fehler in diesem Abschnitt.

Nebenbei: isEmpty() nimmt kein Argument entgegen. Man sieht häufig $posts->isEmpty($posts), was nur zufällig funktioniert, weil das Argument ignoriert wird.

In einer Blade-View erledigt @forelse den leeren Fall ohne ausdrückliche Bedingung:

resources/views/tasks/index.blade.php
@forelse ($tasks as $task)
    <li>{{ $task->title }}</li>
@empty
    <li>Aucune tâche pour le moment.</li>
@endforelse

Nur die nötigen Spalten auswählen

Standardmäßig macht Eloquent ein select *. Bei einer breiten Tabelle oder wenn du nur zwei Felder anzeigst, sind das Übertragung und Hydration für nichts. Drei Schreibweisen schränken die Spalten ein:

php
// 1. select() auf dem Query Builder
Task::select('title', 'priority')->where('active', true)->get();

// 2. als Argument von get() oder first()
Task::where('active', true)->get(['title']);
Task::where('assignee', 'Damien')->first(['title', 'priority']);

// 3. eine bestehende Auswahl ergänzen
Task::select('id')->addSelect('title')->first();

Wenn du nur einen einzigen Wert brauchst, ersparen dir zwei Methoden den Umweg über ein vollständiges Modell:

php
// ein einzelner Skalarwert
Task::where('assignee', 'Damien')->value('title');   // 'Écrire le guide'

// eine Liste von Werten
Task::where('active', true)->pluck('title');         // ['Écrire le guide', 'Relire']

// eine Liste, indexiert über eine andere Spalte
Task::pluck('title', 'id');                          // [1 => 'Écrire le guide', …]

value() ersetzt das reflexhaft geschriebene first(['title'])->title mit Gewinn: Es gibt direkt den String zurück und null, wenn keine Zeile passt, während die andere Schreibweise auf einem Nullobjekt einen Fehler auslöst.

Eine nicht ausgewählte Spalte ist nicht null, sie fehlt

Nach einem first(['title']) gibt es das Attribut priority auf dem Modell nicht. Der Zugriff darauf liefert null ohne Fehler, und das Problem bleibt bis zur Anzeige verborgen. Nimm außerdem den Primärschlüssel in deine Auswahl auf: ohne id finden die Relationen und save() die Zeile nicht mehr wieder.

Häufige Fehler

if ($collection) ist immer wahr Eine leere Collection bleibt ein Objekt und wird deshalb als wahr bewertet. Prüfe isEmpty(), isNotEmpty() oder blank().nfirstOrCreate() ist nicht atomar | Ein SELECT, danach ein INSERT: Zwei gleichzeitige Anfragen können zweimal einfügen. Ergänze einen Unique-Constraint in der Datenbank.
Eine nicht ausgewählte Spalte fehlt, sie ist nicht null Nach first(['title']) liefert das Lesen von priority null ohne Fehler. Nimm immer den Primärschlüssel mit auf, sonst funktionieren Relationen und save() nicht mehr.nget()->count() holt alles | Brauchst du nur die Anzahl, nutze count() auf dem Builder: ein COUNT(*), das keine einzige Zeile überträgt.nisEmpty() nimmt kein Argument | $posts->isEmpty($posts) sieht man oft, es funktioniert nur zufällig, weil das Argument ignoriert wird.

EloquentLaravelPHPSQL

Damien Flandrin Webentwickler seit 2010, Gründer von Gekkode und Email Impact. Jeder Artikel wird vor der Veröffentlichung an einem echten Projekt getestet. Kontakt
Newsletter

Neue Tests, Tutorials und Projekte, per E-Mail.

Reproduzierbare Tests, versionierter Code, datierte Ergebnisse. Niemals Spam.