Direktes SQL
Sie können mit der Nette-Datenbank auf zwei Arten arbeiten: indem Sie SQL-Abfragen direkt schreiben (Direct Access) oder indem Sie SQL automatisch generieren lassen(Explorer Access). Der Direktzugriff ermöglicht es Ihnen, Abfragen sicher zu erstellen und gleichzeitig die volle Kontrolle über ihre Struktur zu behalten.
Informationen zur Erstellung einer Verbindung und deren Konfiguration finden Sie auf einer separaten Seite.
Grundlegende Abfrage
Die Methode query()
führt Datenbankabfragen aus und gibt ein ResultSet-Objekt zurück, das das Ergebnis
darstellt. Wenn die Abfrage fehlschlägt, löst die Methode eine Ausnahme aus. Sie können das Abfrageergebnis mit einer
foreach
-Schleife durchlaufen oder eine der Hilfsfunktionen verwenden.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
}
Um Werte sicher in SQL-Abfragen einzufügen, verwenden Sie parametrisierte Abfragen. Nette Database macht dies sehr einfach: Fügen Sie einfach ein Komma und den Wert an die SQL-Abfrage an.
$database->query('SELECT * FROM users WHERE name = ?', $name);
Für mehrere Parameter können Sie entweder die SQL-Abfrage mit Parametern verschachteln:
$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);
Oder Sie schreiben zuerst die gesamte SQL-Abfrage und hängen dann alle Parameter an:
$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);
Schutz vor SQL-Injektion
Warum ist es wichtig, parametrisierte Abfragen zu verwenden? Weil sie Sie vor SQL-Injection-Angriffen schützen, bei denen Angreifer bösartige SQL-Befehle einschleusen können, um Datenbankdaten zu manipulieren oder darauf zuzugreifen.
Fügen Sie niemals Variablen direkt in eine SQL-Abfrage ein! Verwenden Sie immer parametrisierte Abfragen, um sich vor SQL-Injection zu schützen.
// ❌ UNSAFE CODE - anfällig für SQL-Injection
$database->query("SELECT * FROM users WHERE name = '$name'");
// ✅ Sichere parametrisierte Abfrage
$database->query('SELECT * FROM users WHERE name = ?', $name);
Machen Sie sich unbedingt mit potenziellen Sicherheitsrisiken vertraut.
Abfragetechniken
WHERE-Bedingungen
Sie können WHERE
Bedingungen als ein assoziatives Array schreiben, wobei die Schlüssel Spaltennamen und die
Werte die zu vergleichenden Daten sind. Nette Database wählt automatisch den am besten geeigneten SQL-Operator basierend auf dem
Wertetyp.
$database->query('SELECT * FROM users WHERE', [
'name' => 'John',
'active' => true,
]);
// WHERE `Name` = 'John' AND `active` = 1
Sie können den Operator auch explizit im Schlüssel angeben:
$database->query('SELECT * FROM users WHERE', [
'age >' => 25, // verwendet den Operator >
'name LIKE' => '%John%', // verwendet den LIKE-Operator
'email NOT LIKE' => '%example.com%', // verwendet den Operator NOT LIKE
]);
// WHERE `Alter` > 25 AND `Name` LIKE '%John%' AND `E-Mail` NOT LIKE '%beispiel.com%'
Sonderfälle wie null
Werte oder Arrays werden automatisch behandelt:
$database->query('SELECT * FROM products WHERE', [
'name' => 'Laptop', // verwendet den Operator =
'category_id' => [1, 2, 3], // verwendet IN
'description' => null, // uses IS NULL
]);
// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL
Für negative Bedingungen verwenden Sie den Operator NOT
:
$database->query('SELECT * FROM products WHERE', [
'name NOT' => 'Laptop', // verwendet den <>-Operator
'category_id NOT' => [1, 2, 3], // verwendet NOT IN
'description NOT' => null, // verwendet IS NOT NULL
'id' => [], // übersprungen
]);
// WHERE `Name` <> 'Laptop' AND `Kategorie_id` NOT IN (1, 2, 3) AND `Beschreibung` IS NOT NULL
Standardmäßig werden die Bedingungen mit dem Operator AND
kombiniert. Sie können dieses Verhalten mit dem Platzhalter ?or ändern.
ORDER BY Regeln
Die ORDER BY
Klausel kann als Array definiert werden, wobei die Schlüssel Spalten darstellen und die Werte
Boolesche Werte sind, die eine aufsteigende Reihenfolge angeben:
$database->query('SELECT id FROM author ORDER BY', [
'id' => true, // aufsteigend
'name' => false, // absteigend
]);
// SELECT id FROM autor ORDER BY `id`, `name` DESC
Einfügen von Daten (INSERT)
Um Datensätze einzufügen, verwenden Sie die SQL-Anweisung INSERT
.
$values = [
'name' => 'John Doe',
'email' => 'john@example.com',
];
$database->query('INSERT INTO users ?', $values);
$userId = $database->getInsertId();
Die Methode getInsertId()
gibt die ID der zuletzt eingefügten Zeile zurück. Bei bestimmten Datenbanken (z. B.
PostgreSQL) müssen Sie den Sequenznamen mit $database->getInsertId($sequenceId)
angeben.
Sie können auch spezielle Werte, wie Dateien, DateTime-Objekte oder Enum-Typen, als Parameter übergeben.
Einfügen mehrerer Datensätze auf einmal:
$database->query('INSERT INTO users ?', [
['name' => 'User 1', 'email' => 'user1@mail.com'],
['name' => 'User 2', 'email' => 'user2@mail.com'],
]);
Das Ausführen eines Batch INSERT ist viel schneller, da nur eine einzige Datenbankabfrage anstelle mehrerer einzelner Abfragen ausgeführt wird.
Sicherheitshinweis: Verwenden Sie niemals nicht validierte Daten als $values
. Machen Sie sich mit den möglichen Risiken vertraut.
Daten aktualisieren (UPDATE)
Um Datensätze zu aktualisieren, verwenden Sie die SQL-Anweisung UPDATE
.
// Einen einzelnen Datensatz aktualisieren
$values = [
'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);
Sie können die Anzahl der betroffenen Zeilen mit $result->getRowCount()
überprüfen.
Sie können die Operatoren +=
und -=
in UPDATE
verwenden:
$database->query('UPDATE users SET ? WHERE id = ?', [
'login_count+=' => 1, // inkrementieren login_count
], 1);
Um einen Datensatz einzufügen oder zu aktualisieren, wenn er bereits existiert, verwenden Sie die Technik
ON DUPLICATE KEY UPDATE
:
$values = [
'name' => $name,
'year' => $year,
];
$database->query('INSERT INTO users ? ON DUPLICATE KEY UPDATE ?',
$values + ['id' => $id],
$values,
);
// INSERT INTO users (`id`, `Name`, `Jahr`) VALUES (123, 'Jim', 1978)
// ON DUPLICATE KEY UPDATE `Name` = 'Jim', `Jahr` = 1978
Beachten Sie, dass Nette Database den Kontext des SQL-Befehls, in dem ein Parameter mit einem Array verwendet wird, erkennt und
den SQL-Code entsprechend generiert. Zum Beispiel konstruiert sie (id, name, year) VALUES (123, 'Jim', 1978)
aus dem
ersten Array, während sie das zweite in name = 'Jim', year = 1978
umwandelt. Dies wird im Abschnitt Hinweise zur Erstellung von SQL ausführlicher behandelt.
Löschen von Daten (DELETE)
Um Datensätze zu löschen, verwenden Sie die SQL-Anweisung DELETE
. Beispiel mit der Anzahl der gelöschten
Zeilen:
$count = $database->query('DELETE FROM users WHERE id = ?', 1)
->getRowCount();
Hinweise zur SQL-Konstruktion
Mit SQL-Platzhaltern können Sie steuern, wie Parameterwerte in SQL-Ausdrücke integriert werden:
Hinweis | Beschreibung | Automatisch verwendet für |
---|---|---|
?name |
Verwendet für Tabellen- oder Spaltennamen | – |
?values |
Erzeugt (key, ...) VALUES (value, ...) |
INSERT ... ? , REPLACE ... ? |
?set |
Erzeugt Zuweisungen key = value, ... |
SET ? , KEY UPDATE ? |
?and |
Verknüpft Bedingungen in einem Array mit AND |
WHERE ? , HAVING ? |
?or |
Verbindet Bedingungen in einem Array mit OR |
– |
?order |
Erzeugt die Klausel ORDER BY |
ORDER BY ? , GROUP BY ? |
Um Tabellen- oder Spaltennamen dynamisch einzufügen, verwenden Sie den Platzhalter ?name
. Nette Database stellt
sicher, dass das Escaping entsprechend den Konventionen der Datenbank erfolgt (z. B. Einschließen in Backticks für MySQL).
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
// SELECT `Name` FROM `Benutzer` WHERE id = 1 (in MySQL)
Warnung: Verwenden Sie den Platzhalter ?name
nur für geprüfte Tabellen- und Spaltennamen. Andernfalls
riskieren Sie Sicherheitslücken.
Andere Hinweise müssen normalerweise nicht angegeben werden, da Nette beim Erstellen von SQL-Abfragen eine intelligente
automatische Erkennung verwendet (siehe dritte Spalte der Tabelle). Sie können sie jedoch in Situationen verwenden, in denen Sie
Bedingungen mit OR
anstelle von AND
kombinieren möchten:
$database->query('SELECT * FROM users WHERE ?or', [
'name' => 'John',
'email' => 'john@example.com',
]);
// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com'
Besondere Werte
Zusätzlich zu den Standard-Skalar-Typen (z.B. string
, int
, bool
) können Sie auch
spezielle Werte als Parameter übergeben:
- Dateien: Verwenden Sie
fopen('file.png', 'r')
, um den binären Inhalt einer Datei einzufügen. - Datum und Uhrzeit:
DateTime
Objekte werden automatisch in das Datumsformat der Datenbank konvertiert. - Enum-Werte: Instanzen von
enum
werden in ihre entsprechenden Werte konvertiert. - SQL-Literale: Diese werden mit
Connection::literal('NOW()')
erstellt und direkt in die Abfrage eingefügt.
$database->query('INSERT INTO articles ?', [
'title' => 'My Article',
'published_at' => new DateTime,
'content' => fopen('image.png', 'r'),
'state' => Status::Draft,
]);
Bei Datenbanken, die keine native Unterstützung für den Typ datetime
bieten (z. B. SQLite und Oracle), werden
DateTime
-Werte gemäß der Konfigurationsoption formatDateTime
konvertiert (Standard: U
für Unix-Zeitstempel).
SQL-Literale
In einigen Fällen kann es erforderlich sein, rohen SQL-Code als Wert einzufügen, ohne ihn als Zeichenkette zu behandeln oder
ihn zu escapen. Verwenden Sie dazu Objekte der Klasse Nette\Database\SqlLiteral
, die mit der Methode
Connection::literal()
erstellt werden können.
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`Name` = 'Jim') AND (`Jahr` > YEAR())
Alternativ dazu:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`Name` = 'Jim') AND (Jahr > YEAR())
SQL-Literale können auch Parameter enthalten:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('year > ? AND year < ?', $min, $max),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (Jahr > 1978 AND Jahr < 2017)
Dies ermöglicht flexible Kombinationen:
$result = $database->query('SELECT * FROM users WHERE', [
'name' => $name,
$database::literal('?or', [
'active' => true,
'role' => $role,
]),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (`active` = 1 OR `role` = 'admin')
Abrufen von Daten
Abkürzungen für SELECT-Abfragen
Um den Datenabruf zu vereinfachen, bietet die Klasse Connection
mehrere Shortcuts, die einen Aufruf von
query()
mit einem nachfolgenden Aufruf von fetch*()
kombinieren. Diese Methoden akzeptieren die gleichen
Parameter wie query()
, d.h. eine SQL-Abfrage und optionale Parameter. Eine detaillierte Beschreibung der
fetch*()
Methoden finden Sie weiter unten.
fetch($sql, ...$params): ?Row |
Führt die Abfrage aus und holt sich die erste Zeile als Row Objekt. |
fetchAll($sql, ...$params): array |
Führt die Abfrage aus und holt alle Zeilen als ein Array von Row Objekten. |
fetchPairs($sql, ...$params): array |
Führt die Abfrage aus und ruft ein assoziatives Array ab, bei dem die erste Spalte der Schlüssel und die zweite der Wert ist. |
fetchField($sql, ...$params): mixed |
Führt die Abfrage aus und ruft den Wert der ersten Zelle in der ersten Zeile ab. |
fetchList($sql, ...$params): ?array |
Führt die Abfrage aus und ruft die erste Zeile als indiziertes Array ab. |
Beispiel:
// fetchField() - gibt den Wert der ersten Zelle zurück
$count = $database->query('SELECT COUNT(*) FROM articles')
->fetchField();
foreach
– Iteration über Zeilen
Nach der Ausführung einer Abfrage wird ein ResultSet-Objekt zurückgegeben, mit dem Sie auf
verschiedene Weise über die Ergebnisse iterieren können. Die einfachste und speichereffizienteste Methode zum Abrufen von Zeilen
ist die Iteration in einer foreach
-Schleife. Diese Methode verarbeitet eine Zeile nach der anderen und vermeidet die
gleichzeitige Speicherung aller Daten im Speicher.
$result = $database->query('SELECT * FROM users');
foreach ($result as $row) {
echo $row->id;
echo $row->name;
//...
}
Die ResultSet
-Schleife kann nur einmal durchlaufen werden. Wenn Sie die Schleife mehrfach
durchlaufen müssen, müssen Sie die Daten zunächst in ein Array laden, zum Beispiel mit der Methode fetchAll()
.
fetch(): ?Row
Führt die Abfrage aus und holt eine einzelne Zeile als Row
Objekt. Wenn keine weiteren Zeilen verfügbar sind,
wird null
zurückgegeben. Diese Methode verschiebt den internen Zeiger auf die nächste Zeile.
$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // holt die erste Zeile ab
if ($row) {
echo $row->name;
}
fetchAll(): array
Holt alle verbleibenden Zeilen aus ResultSet
als Array von Row
Objekten.
$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // holt alle Zeilen ab
foreach ($rows as $row) {
echo $row->name;
}
fetchPairs (string|int|null $key = null, string|int|null $value = null): array
Holt die Ergebnisse als assoziatives Array. Das erste Argument gibt die Spalte an, die als Schlüssel verwendet werden soll, und das zweite Argument die Spalte, die als Wert verwendet werden soll:
$result = $database->query('SELECT id, name FROM users');
$names = $result->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]
Wenn nur der erste Parameter angegeben wird, ist der Wert die gesamte Zeile (als Row
Objekt):
$rows = $result->fetchPairs('id');
// [1 => Zeile(id: 1, Name: 'John'), 2 => Zeile(id: 2, Name: 'Jane'), ...]
Wenn null
als Schlüssel übergeben wird, wird das Array numerisch, beginnend bei Null, indiziert:
$names = $result->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]
fetchPairs (Closure $callback): array
Alternativ können Sie auch einen Callback bereitstellen, der die Schlüssel-Wert-Paare oder Werte für jede Zeile bestimmt.
$result = $database->query('SELECT * FROM users');
$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
// ['1 - John', '2 - Jane', ...]
// Der Callback kann auch ein Array mit einem Schlüssel- und Wertepaar zurückgeben:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]
fetchField(): mixed
Holt den Wert der ersten Zelle in der aktuellen Zeile. Wenn keine weiteren Zeilen verfügbar sind, wird null
zurückgegeben. Diese Methode verschiebt den internen Zeiger auf die nächste Zeile.
$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // holt den Namen aus der ersten Zeile
fetchList(): ?array
Holt die Zeile als indiziertes Array. Wenn keine weiteren Zeilen verfügbar sind, wird null
zurückgegeben. Diese
Methode schiebt den internen Zeiger auf die nächste Zeile vor.
$result = $database->query('SELECT name, email FROM users');
$row = $result->fetchList(); // ['John', 'john@example.com']
getRowCount(): ?int
Gibt die Anzahl der Zeilen zurück, die von der letzten UPDATE
oder DELETE
Abfrage betroffen sind.
Bei SELECT
-Abfragen wird die Anzahl der abgerufenen Zeilen zurückgegeben, die jedoch nicht immer bekannt ist –
in solchen Fällen wird null
zurückgegeben.
getColumnCount(): ?int
Gibt die Anzahl der Spalten in der Datei ResultSet
zurück.
Abfrage von Informationen
Um Details über die zuletzt ausgeführte Abfrage abzurufen, verwenden Sie:
echo $database->getLastQueryString(); // gibt die SQL-Abfrage aus
$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString(); // gibt die SQL-Abfrage aus
echo $result->getTime(); // gibt die Ausführungszeit in Sekunden aus
Um das Ergebnis als HTML-Tabelle anzuzeigen, verwenden Sie:
$result = $database->query('SELECT * FROM articles');
$result->dump();
Informationen über die Spaltentypen können Sie auch über die Seite ResultSet
abrufen:
$result = $database->query('SELECT * FROM articles');
$types = $result->getColumnTypes();
foreach ($types as $column => $type) {
echo "$column is of type $type->type"; // z.B. 'id ist vom Typ int'
}
Abfrage-Protokollierung
Sie können eine benutzerdefinierte Abfrageprotokollierung implementieren. Das Ereignis onQuery
ist ein Array von
Callbacks, die nach jeder Abfrageausführung aufgerufen werden:
$database->onQuery[] = function ($database, $result) use ($logger) {
$logger->info('Query: ' . $result->getQueryString());
$logger->info('Time: ' . $result->getTime());
if ($result->getRowCount() > 1000) {
$logger->warning('Large result set: ' . $result->getRowCount() . ' rows');
}
};