mitmario.dev

Abfragen

PHP Sandbox 4 Min Lesezeit 3 BeispieleLektion 3 von 9

In Lektion 14.2 hast du Daten hineingeschrieben. Jetzt holst du sie wieder heraus, und dafür gibt es in PDO eine Methode und drei Arten, das Ergebnis entgegenzunehmen.

$db->query($sql) schickt die Abfrage los und gibt dir ein Ergebnisobjekt zurück, ein PDOStatement. Darin stecken die gefundenen Zeilen, aber du kommst nicht direkt an sie heran: Du musst sie abholen. Genau dieser zweite Schritt ist der, den man am Anfang vergisst.

Merk dir für diese Lektion eine Einschränkung: query() ist nur für Abfragen gedacht, in denen kein Wert von außen steckt. Sobald ein Suchwort aus einem Formular oder eine Nummer aus der Adresszeile in die Abfrage soll, ist query() der falsche Weg, und der richtige steht in Lektion 14.4. Alle Abfragen hier stehen deshalb fest im Quelltext.

fetchAll oder fetch

Einmal alles, einmal die erste Zeile
<?php

// Dieselbe Abfrage, zweimal abgeholt.

require __DIR__ . "/vorbereiten.php";

$alle = $db->query("SELECT id, titel FROM notizen ORDER BY id LIMIT 3")->fetchAll();

echo "fetchAll() liefert eine Liste von Zeilen:\n";
print_r($alle);

$eine = $db->query("SELECT id, titel FROM notizen ORDER BY id LIMIT 3")->fetch();

echo "\nfetch() liefert nur die erste, und zwar direkt als Zeile:\n";
print_r($eine);

echo "\nDeshalb kommst du an den Titel unterschiedlich heran:\n";
echo "  aus fetchAll: ", $alle[0]["titel"], "\n";
echo "  aus fetch:    ", $eine["titel"], "\n";

fetchAll() holt alle Zeilen auf einmal und gibt dir ein Array von Arrays. Genau die Form, die du in Lektion 5.3 als verschachteltes Array kennengelernt hast: außen eine Liste mit Nummern, innen je eine Zuordnung von Spaltenname auf Wert. Ein foreach darüber gibt dir eine Zeile nach der anderen.

fetch() holt eine Zeile, nämlich die nächste, und gibt sie dir direkt als Zuordnung. Du kommst also mit $zeile["titel"] an den Wert und nicht mit $zeilen[0]["titel"].

Die Faustregel dazu ist kurz: Erwartest du eine Liste, nimm fetchAll(). Erwartest du genau eine Zeile, etwa weil du nach einer id suchst, nimm fetch().

Es gibt noch einen dritten Weg, und der ist bequem, wenn dich nur ein einziger Wert interessiert. fetchColumn() gibt dir die erste Spalte der ersten Zeile, also aus SELECT count(*) FROM notizen direkt die Zahl statt eines Arrays mit einem Eintrag darin.

Vier Wörter, mit denen du fast alles fragen kannst

Vier Stufen bis zur Antwort
<?php

// Dieselbe Abfrage in vier Stufen. Sieh zu, wie das Ergebnis
// kleiner wird und sich dreht.

require __DIR__ . "/vorbereiten.php";

$stufen = [
    "ohne Zusatz" => "SELECT titel FROM notizen",
    "WHERE erledigt = 0" => "SELECT titel FROM notizen WHERE erledigt = 0",
    "dazu ORDER BY angelegt DESC" => "SELECT titel FROM notizen WHERE erledigt = 0 ORDER BY angelegt DESC",
    "dazu LIMIT 3" => "SELECT titel FROM notizen WHERE erledigt = 0 ORDER BY angelegt DESC LIMIT 3",
];

foreach ($stufen as $was => $sql) {
    $titel = [];

    foreach ($db->query($sql) as $zeile) {
        $titel[] = $zeile["titel"];
    }

    echo $was, "\n";
    echo "  ", count($titel), " Zeilen: ", implode(", ", $titel), "\n\n";
}

Das Beispiel baut dieselbe Abfrage in vier Stufen auf, und du siehst bei jeder, was sie ändert.

SELECT titel FROM notizen ist die Grundform: Welche Spalten aus welcher Tabelle. Ein * statt der Spaltennamen heißt „alle Spalten” und ist zum Ausprobieren praktisch. In fertigem Code schreibst du lieber auf, was du wirklich brauchst, dann fällt beim Lesen sofort auf, womit die Zeile arbeitet.

WHERE erledigt = 0 ist die Bedingung. Aus acht Zeilen werden sechs. Du kannst Bedingungen mit AND und OR verbinden, und es gibt LIKE für Textsuche mit Platzhalter, wo % für beliebig viele Zeichen steht.

ORDER BY angelegt DESC sortiert. Die Zahl der Zeilen ändert sich nicht, nur die Reihenfolge dreht sich um: DESC heißt absteigend, ASC aufsteigend, und ohne Angabe gilt aufsteigend. Sortiert wird nach dem, was in der Spalte steht, und weil unsere Daten als 2026-08-24 abgelegt sind, sortiert sich das Datum von selbst richtig.

LIMIT 3 schneidet ab. Erst jetzt werden aus sechs Zeilen drei, und die Reihenfolge davor entscheidet, welche drei. Ein LIMIT ohne ORDER BY gibt dir irgendwelche drei Zeilen, und heute sind es vielleicht andere als morgen.

Im Reiter Debug siehst du die vier Stufen an titel: Die Liste wächst beim ersten Durchlauf auf acht Einträge, beim zweiten auf sechs, beim dritten wieder auf sechs, nur fängt sie jetzt mit Milch an, und beim vierten bleibt es bei drei.

Was zurückkommt, wenn nichts da ist

Wenn nichts da ist
<?php

// Eine Abfrage, die nichts findet. Drei Wege, drei Rueckgaben.

require __DIR__ . "/vorbereiten.php";

$sql = "SELECT * FROM notizen WHERE titel = 'Marmelade'";

echo "fetch():       "; var_dump($db->query($sql)->fetch());
echo "fetchAll():    "; var_dump($db->query($sql)->fetchAll());
echo "fetchColumn(): "; var_dump($db->query($sql)->fetchColumn());

echo "\nfalse ist nicht null:\n";
echo "  false === null:  ", var_export(false === null, true), "\n";
echo "  false == null:   ", var_export(false == null, true), "\n";

echo "\nUnd so sieht es aus, wenn du den Fall vergisst:\n";

$nichts = $db->query($sql)->fetch();

echo "  Titel: ";
var_dump($nichts["titel"]);
echo "  Sieh dir dazu den Fehlerkanal im Terminal an.\n";

Hier lauert die Falle dieser Lektion. fetchAll() gibt dir bei null Treffern ein leeres Array, und damit läuft ein foreach einfach nullmal durch. Kein Sonderfall, nichts zu tun.

fetch() gibt dir dagegen false. Nicht null, nicht ein leeres Array, sondern false. Und weil du in Lektion 3.6 gelernt hast, wie großzügig == in PHP vergleicht, ist die Zeile aus dem Beispiel wichtig: false == null ist wahr, false === null ist falsch. Prüfst du mit == null, fängst du also mehr, als du wolltest.

Deshalb lautet die Regel: Prüf das Ergebnis von fetch() mit === false. Das ist eindeutig, es verwechselt nichts, und es hält auch dann, wenn in einer Spalte tatsächlich einmal eine 0 oder eine leere Zeichenkette steht.

Vergisst du die Prüfung, greift dein Code mit $zeile["titel"] auf false zu. PHP macht daraus ein null und schreibt eine Warnung auf den Fehlerkanal: Trying to access array offset on false. Die letzte Zeile des Beispiels zeigt genau das, und im Terminal steht die Warnung mitten in der Ausgabe. Mit php index.php 2>/dev/null siehst du die Ausgabe ohne den Fehlerkanal, dann bleibt dort nur Titel: NULL.

Direkt über das Ergebnis laufen

Im zweiten Beispiel ist dir vielleicht aufgefallen, dass dort kein fetchAll() steht. Das PDOStatement lässt sich nämlich auch direkt in ein foreach stecken, und dann kommt eine Zeile nach der anderen.

Der Unterschied ist der Speicher. fetchAll() holt alles auf einmal, und bei zwanzigtausend Zeilen liegen die alle gleichzeitig da. Das foreach über das Statement holt sich eine Zeile, wenn es sie braucht. Bei drei Zeilen ist das egal, und fetchAll() liest sich besser. Bei sehr vielen Zeilen ist es der Unterschied zwischen läuft und läuft nicht.

Zum Mitnehmen

Eine Abfrage besteht aus zwei Hälften, und die zweite vergisst man leicht: Erst fragst du, dann holst du das Ergebnis ab. Wie du abholst, entscheidet, was du in der Hand hast.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.