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 &lt; ?', $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');
	}
};
Version: 4.0