SQL direct

Puteți lucra cu Nette Database în două moduri: scriind interogări SQL direct (Direct Access) sau lăsând SQL să fie generat automat(Explorer Access). Direct Access vă permite să construiți în siguranță interogări, păstrând în același timp controlul deplin asupra structurii acestora.

Pentru informații privind crearea unei conexiuni și configurarea acesteia, consultați pagina separată.

Interogare de bază

Metoda query() execută interogări ale bazei de date și returnează un obiect ResultSet care reprezintă rezultatul. Dacă interogarea eșuează, metoda aruncă o excepție. Puteți parcurge în buclă rezultatul interogării folosind o buclă foreach sau folosind una dintre funcțiile de ajutor.

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
}

Pentru a introduce valori în siguranță în interogările SQL, utilizați interogări parametrizate. Nette Database face acest lucru foarte simplu: trebuie doar să adăugați o virgulă și valoarea la interogarea SQL.

$database->query('SELECT * FROM users WHERE name = ?', $name);

Pentru parametrii multipli, puteți fie să intercalați interogarea SQL cu parametrii:

$database->query('SELECT * FROM users WHERE name = ?', $name, 'AND age > ?', $age);

Sau scrieți mai întâi întreaga interogare SQL și apoi adăugați toți parametrii:

$database->query('SELECT * FROM users WHERE name = ? AND age > ?', $name, $age);

Protecție împotriva injectării SQL

De ce este important să utilizați interogări parametrizate? Pentru că vă protejează de atacurile de tip SQL injection, în care atacatorii pot injecta comenzi SQL rău intenționate pentru a manipula sau a accesa datele din baza de date.

Niciodată nu introduceți variabile direct într-o interogare SQL! Utilizați întotdeauna interogări parametrizate pentru a vă proteja împotriva injecțiilor SQL.

// ❌ UNSAFE CODE - vulnerabil la injectarea SQL
$database->query("SELECT * FROM users WHERE name = '$name'");

// ✅ Interogare parametrizată sigură
$database->query('SELECT * FROM users WHERE name = ?', $name);

Asigurați-vă că vă familiarizați cu potențialele riscuri de securitate.

Tehnici de interogare

Condiții WHERE

Puteți scrie condițiile WHERE ca un array asociativ, în care cheile sunt numele coloanelor, iar valorile sunt datele de comparat. Nette Database selectează automat cel mai adecvat operator SQL în funcție de tipul valorii.

$database->query('SELECT * FROM users WHERE', [
	'name' => 'John',
	'active' => true,
]);
// WHERE `name` = 'John' AND `active` = 1

De asemenea, puteți specifica explicit operatorul în cheie:

$database->query('SELECT * FROM users WHERE', [
	'age >' => 25,           // utilizează operatorul >
	'name LIKE' => '%John%', // utilizează operatorul LIKE
	'email NOT LIKE' => '%example.com%', // utilizează operatorul NOT LIKE
]);
// WHERE `age` > 25 AND `name` LIKE '%John%' AND `email` NOT LIKE '%example.com%'

Cazurile speciale, cum ar fi valorile null sau matricele, sunt gestionate automat:

$database->query('SELECT * FROM products WHERE', [
	'name' => 'Laptop',         // utilizează operatorul =
	'category_id' => [1, 2, 3], // utilizează IN
	'description' => null,      // uses IS NULL
]);
// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL

Pentru condiții negative, utilizați operatorul NOT:

$database->query('SELECT * FROM products WHERE', [
	'name NOT' => 'Laptop',         // utilizează operatorul <>
	'category_id NOT' => [1, 2, 3], // utilizează NOT IN
	'description NOT' => null,      // uses IS NOT NULL
	'id' => [],                     // sărit
]);
// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL

În mod implicit, condițiile sunt combinate utilizând operatorul AND. Puteți schimba acest comportament utilizând marcajul ?or.

ORDER BY Reguli

Clauza ORDER BY poate fi definită ca un array, unde cheile reprezintă coloane, iar valorile sunt booleeni care indică ordinea crescătoare:

$database->query('SELECT id FROM author ORDER BY', [
	'id' => true,  // ascendentă
	'name' => false, // descrescător
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC

Inserarea datelor (INSERT)

Pentru a introduce înregistrări, utilizați instrucțiunea SQL INSERT.

$values = [
	'name' => 'John Doe',
	'email' => 'john@example.com',
];
$database->query('INSERT INTO users ?', $values);
$userId = $database->getInsertId();

Metoda getInsertId() returnează ID-ul ultimului rând introdus. Pentru anumite baze de date (de exemplu, PostgreSQL), trebuie să specificați numele secvenței utilizând $database->getInsertId($sequenceId).

De asemenea, puteți trece valori speciale, cum ar fi fișiere, obiecte DateTime sau tipuri enum, ca parametri.

Inserarea simultană a mai multor înregistrări:

$database->query('INSERT INTO users ?', [
	['name' => 'User 1', 'email' => 'user1@mail.com'],
	['name' => 'User 2', 'email' => 'user2@mail.com'],
]);

Efectuarea unei INSERTĂRI pe loturi este mult mai rapidă deoarece se execută o singură interogare a bazei de date în loc de mai multe interogări individuale.

Nota de securitate: Nu utilizați niciodată date nevalidate ca $values. Familiarizați-vă cu riscurile posibile.

Actualizarea datelor (UPDATE)

Pentru a actualiza înregistrările, utilizați instrucțiunea SQL UPDATE.

// Actualizarea unei singure înregistrări
$values = [
	'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);

Puteți verifica numărul de rânduri afectate utilizând $result->getRowCount().

Puteți utiliza operatorii += și -= în UPDATE:

$database->query('UPDATE users SET ? WHERE id = ?', [
	'login_count+=' => 1, // increment login_count
], 1);

Pentru a introduce sau actualiza o înregistrare dacă aceasta există deja, utilizați tehnica 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`, `year`) VALUES (123, 'Jim', 1978)
//  ON DUPLICATE KEY UPDATE `name` = 'Jim', `year` = 1978

Rețineți că Nette Database recunoaște contextul comenzii SQL în care este utilizat un parametru cu un array și generează codul SQL în consecință. De exemplu, a construit (id, name, year) VALUES (123, 'Jim', 1978) din primul array, în timp ce l-a convertit pe al doilea în name = 'Jim', year = 1978. Acest aspect este tratat mai detaliat în secțiunea Sfaturi pentru construirea SQL.

Ștergerea datelor (DELETE)

Pentru a șterge înregistrări, utilizați instrucțiunea SQL DELETE. Exemplu cu numărul de rânduri șterse:

$count = $database->query('DELETE FROM users WHERE id = ?', 1)
	->getRowCount();

Sugestii de construcție SQL

Lockerele SQL vă permit să controlați modul în care valorile parametrilor sunt încorporate în expresiile SQL:

Indicație Descriere Folosit automat pentru
?name Folosit pentru nume de tabele sau coloane
?values Generează (key, ...) VALUES (value, ...) INSERT ... ?, REPLACE ... ?
?set Generează atribuiri key = value, ... SET ?, KEY UPDATE ?
?and Unește condițiile dintr-un array cu AND WHERE ?, HAVING ?
?or Unește condițiile într-un array cu OR
?order Generează clauza ORDER BY ORDER BY ?, GROUP BY ?

Pentru inserarea dinamică a numelor de tabele sau coloane, utilizați marcajul ?name. Nette Database asigură scăparea corectă în conformitate cu convențiile bazei de date (de exemplu, includerea în backticks pentru MySQL).

$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name WHERE id = 1', $column, $table);
// SELECT `name` FROM `users` WHERE id = 1 (în MySQL)

Avertisment: Folosiți marcajul ?name numai pentru numele validate ale tabelelor și coloanelor. În caz contrar, riscați vulnerabilități de securitate.

De obicei, nu este necesar să specificați alte indicii, deoarece Nette utilizează detectarea automată inteligentă atunci când construiește interogări SQL (a se vedea a treia coloană din tabel). Cu toate acestea, le puteți utiliza în situațiile în care doriți să combinați condițiile utilizând OR în loc de AND:

$database->query('SELECT * FROM users WHERE ?or', [
	'name' => 'John',
	'email' => 'john@example.com',
]);
// SELECT * FROM users WHERE `name` = 'John' OR `email` = 'john@example.com'

Valori speciale

În plus față de tipurile scalare standard (de exemplu, string, int, bool), puteți trece și valori speciale ca parametri:

  • Fișiere: Utilizați fopen('file.png', 'r') pentru a introduce conținutul binar al unui fișier.
  • Data și ora: Obiectele DateTime sunt convertite automat în formatul de dată al bazei de date.
  • Valori enum: Instanțele din enum sunt convertite în valorile corespunzătoare.
  • Literale SQL: Create utilizând Connection::literal('NOW()'), acestea sunt inserate direct în interogare.
$database->query('INSERT INTO articles ?', [
	'title' => 'My Article',
	'published_at' => new DateTime,
	'content' => fopen('image.png', 'r'),
	'state' => Status::Draft,
]);

Pentru bazele de date care nu oferă suport nativ pentru tipul datetime (de exemplu, SQLite și Oracle), valorile DateTime sunt convertite în conformitate cu opțiunea de configurare formatDateTime (implicit: U pentru timestamp Unix).

Literali SQL

În unele cazuri, poate fi necesar să introduceți cod SQL brut ca valoare fără a-l trata ca pe un șir de caractere sau fără a-l scăpa. Pentru aceasta, utilizați obiecte din clasa Nette\Database\SqlLiteral, care pot fi create utilizând metoda Connection::literal().

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	'year >' => $database::literal('YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (`year` > YEAR())

Alternativ:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > YEAR()'),
]);
// SELECT * FROM users WHERE (`name` = 'Jim') AND (year > YEAR())

Literalele SQL pot conține și parametri:

$result = $database->query('SELECT * FROM users WHERE', [
	'name' => $name,
	$database::literal('year > ? AND year &lt; ?', $min, $max),
]);
// SELECT * FROM users WHERE `name` = 'Jim' AND (an > 1978 AND an < 2017)

Acest lucru permite combinații flexibile:

$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')

Preluarea datelor

Scurtături pentru interogările SELECT

Pentru a simplifica recuperarea datelor, clasa Connection oferă mai multe scurtături care combină un apel la query() cu un apel ulterior la fetch*(). Aceste metode acceptă aceiași parametri ca și query(), și anume o interogare SQL și parametri opționali. O descriere detaliată a metodelor fetch*() poate fi găsită mai jos.

fetch($sql, ...$params): ?Row Execută interogarea și extrage primul rând ca obiect Row.
fetchAll($sql, ...$params): array Execută interogarea și extrage toate rândurile sub forma unei serii de obiecte Row.
fetchPairs($sql, ...$params): array Execută interogarea și extrage un array asociativ în care prima coloană este cheia, iar a doua este valoarea.
fetchField($sql, ...$params): mixed Execută interogarea și extrage valoarea primei celule din primul rând.
fetchList($sql, ...$params): ?array Execută interogarea și extrage primul rând ca un array indexat.

Exemplu:

// fetchField() - returnează valoarea primei celule
$count = $database->query('SELECT COUNT(*) FROM articles')
	->fetchField();

foreach – Iterarea peste rânduri

După executarea unei interogări, este returnat un obiect ResultSet, care vă permite să iterați peste rezultate în diverse moduri. Cel mai simplu și mai eficient mod de a prelua rânduri din memorie este prin iterarea într-o buclă foreach. Această metodă procesează rândurile unul câte unul și evită stocarea simultană a tuturor datelor în memorie.

$result = $database->query('SELECT * FROM users');

foreach ($result as $row) {
	echo $row->id;
	echo $row->name;
	//...
}

Rețeaua ResultSet poate fi iterată o singură dată. Dacă trebuie să o iterați de mai multe ori, trebuie mai întâi să încărcați datele într-un array, de exemplu, utilizând metoda fetchAll().

fetch(): ?Row

Execută interogarea și extrage un singur rând ca obiect Row. Dacă nu mai sunt disponibile alte rânduri, se returnează null. Această metodă avansează pointerul intern la rândul următor.

$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // preia primul rând
if ($row) {
	echo $row->name;
}

fetchAll(): array

Preia toate rândurile rămase din ResultSet sub forma unei serii de obiecte Row.

$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // preia toate rândurile
foreach ($rows as $row) {
	echo $row->name;
}

fetchPairs (string|int|null $key = null, string|int|null $value = null)array

Preia rezultatele sub forma unui array asociativ. Primul argument specifică coloana care urmează să fie utilizată ca cheie, iar al doilea specifică coloana care urmează să fie utilizată ca valoare:

$result = $database->query('SELECT id, name FROM users');
$names = $result->fetchPairs('id', 'name');
// [1 => "John Doe", 2 => "Jane Doe", ...]

Dacă este furnizat doar primul parametru, valoarea va fi întregul rând (ca obiect Row ):

$rows = $result->fetchPairs('id');
// [1 => rând (id: 1, nume: "John"), 2 => rând (id: 2, nume: "Jane"), ...]

Dacă null este trecut ca cheie, matricea va fi indexată numeric începând de la zero:

$names = $result->fetchPairs(null, 'name');
// [0 => "John Doe", 1 => "Jane Doe", ...]

fetchPairs (Closure $callback)array

Alternativ, puteți furniza un callback care determină perechile cheie-valoare sau valorile pentru fiecare rând.

$result = $database->query('SELECT * FROM users');
$items = $result->fetchPairs(fn($row) => "$row->id - $row->name");
// ['1 - John', '2 - Jane', ...]

// Callback-ul poate returna, de asemenea, un array cu o pereche cheie/valoare:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]

fetchField(): mixed

Preia valoarea primei celule din rândul curent. Dacă nu mai sunt disponibile alte rânduri, se returnează null. Această metodă avansează pointerul intern la rândul următor.

$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // preia numele din primul rând

fetchList(): ?array

Preia rândul ca un array indexat. Dacă nu mai sunt disponibile alte rânduri, se returnează null. Această metodă avansează pointerul intern la rândul următor.

$result = $database->query('SELECT name, email FROM users');
$row = $result->fetchList(); // ['John', 'john@example.com']

getRowCount(): ?int

Returnează numărul de rânduri afectate de ultima interogare UPDATE sau DELETE. Pentru interogările SELECT, se returnează numărul de rânduri extrase, dar acesta nu poate fi întotdeauna cunoscut – în astfel de cazuri, se returnează null.

getColumnCount(): ?int

Returnează numărul de coloane din ResultSet.

Informații despre interogare

Pentru a obține detalii despre cea mai recentă interogare executată, utilizați:

echo $database->getLastQueryString(); // afișează interogarea SQL

$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString();    // afișează interogarea SQL
echo $result->getTime();           // afișează timpul de execuție în secunde

Pentru a afișa rezultatul ca un tabel HTML, utilizați:

$result = $database->query('SELECT * FROM articles');
$result->dump();

De asemenea, puteți obține informații despre tipurile de coloane din ResultSet:

$result = $database->query('SELECT * FROM articles');
$types = $result->getColumnTypes();

foreach ($types as $column => $type) {
	echo "$column is of type $type->type"; // de exemplu, "id este de tip int
}

Înregistrarea interogărilor

Puteți implementa înregistrarea interogărilor personalizate. Evenimentul onQuery este o serie de callback-uri care sunt invocate după fiecare execuție a interogării:

$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');
	}
};
versiune: 4.0