Άμεση SQL

Μπορείτε να εργαστείτε με τη βάση δεδομένων Nette με δύο τρόπους: γράφοντας απευθείας ερωτήματα SQL (Άμεση πρόσβαση) ή αφήνοντας την SQL να δημιουργηθεί αυτόματα(Εξερευνητική πρόσβαση). Η Άμεση πρόσβαση σας επιτρέπει να δημιουργείτε με ασφάλεια ερωτήματα διατηρώντας τον πλήρη έλεγχο της δομής τους.

Για πληροφορίες σχετικά με τη δημιουργία μιας σύνδεσης και τη διαμόρφωσή της, ανατρέξτε στην ξεχωριστή σελίδα.

Βασικές ερωτήσεις

Η μέθοδος query() εκτελεί ερωτήματα στη βάση δεδομένων και επιστρέφει ένα αντικείμενο ResultSet που αντιπροσωπεύει το αποτέλεσμα. Εάν το ερώτημα αποτύχει, η μέθοδος πετάει μια εξαίρεση. Μπορείτε να κάνετε βρόχο στο αποτέλεσμα του ερωτήματος χρησιμοποιώντας έναν βρόχο foreach ή να χρησιμοποιήσετε μια από τις βοηθητικές συναρτήσεις.

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

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

Για την ασφαλή εισαγωγή τιμών σε ερωτήματα SQL, χρησιμοποιήστε παραμετροποιημένα ερωτήματα. Η Nette Database το κάνει αυτό πολύ απλό: απλά προσθέστε ένα κόμμα και την τιμή στο ερώτημα SQL.

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

Για πολλαπλές παραμέτρους, μπορείτε είτε να παρεμβάλλετε το ερώτημα SQL με παραμέτρους:

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

Ή να γράψετε πρώτα ολόκληρο το ερώτημα SQL και στη συνέχεια να επισυνάψετε όλες τις παραμέτρους:

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

Προστασία κατά της εισβολής SQL

Γιατί είναι σημαντικό να χρησιμοποιείτε παραμετροποιημένα ερωτήματα; Επειδή σας προστατεύουν από επιθέσεις έγχυσης SQL, όπου οι επιτιθέμενοι μπορούν να εισάγουν κακόβουλες εντολές SQL για να χειριστούν ή να αποκτήσουν πρόσβαση σε δεδομένα βάσης δεδομένων.

Ποτέ μην εισάγετε μεταβλητές απευθείας σε ένα ερώτημα SQL! Χρησιμοποιείτε πάντα παραμετροποιημένα ερωτήματα για να προστατεύεστε από SQL injection.

// ❌ UNSAFE CODE - ευάλωτος σε έγχυση SQL
$database->query("SELECT * FROM users WHERE name = '$name'");

// ✅ Ασφαλές παραμετροποιημένο ερώτημα
$database->query('SELECT * FROM users WHERE name = ?', $name);

Φροντίστε να εξοικειωθείτε με τους πιθανούς κινδύνους ασφαλείας.

Τεχνικές αναζήτησης

WHERE Συνθήκες

Μπορείτε να γράψετε τις συνθήκες WHERE ως συσχετιστικό πίνακα, όπου τα κλειδιά είναι τα ονόματα των στηλών και οι τιμές είναι τα δεδομένα προς σύγκριση. Η Nette Database επιλέγει αυτόματα τον καταλληλότερο τελεστή SQL με βάση τον τύπο της τιμής.

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

Μπορείτε επίσης να καθορίσετε ρητά τον τελεστή στο κλειδί:

$database->query('SELECT * FROM users WHERE', [
	'age >' => 25,           // χρησιμοποιεί τον τελεστή >
	'name LIKE' => '%John%', // χρησιμοποιεί τον τελεστή LIKE
	'email NOT LIKE' => '%example.com%', // χρησιμοποιεί τον τελεστή NOT LIKE
]);
// WHERE "age" > 25 AND "name" LIKE "%John%" AND "email" NOT LIKE "%example.com%

Ειδικές περιπτώσεις όπως οι τιμές null ή οι πίνακες αντιμετωπίζονται αυτόματα:

$database->query('SELECT * FROM products WHERE', [
	'name' => 'Laptop',         // χρησιμοποιεί τον τελεστή =
	'category_id' => [1, 2, 3], // χρησιμοποιεί IN
	'description' => null,      // χρησιμοποιεί IS NULL
]);
// WHERE `name` = 'Laptop' AND `category_id` IN (1, 2, 3) AND `description` IS NULL

Για αρνητικές συνθήκες, χρησιμοποιήστε τον τελεστή NOT:

$database->query('SELECT * FROM products WHERE', [
	'name NOT' => 'Laptop',         // χρησιμοποιεί τον τελεστή <>
	'category_id NOT' => [1, 2, 3], // χρησιμοποιεί NOT IN
	'description NOT' => null,      // χρησιμοποιεί IS NOT NULL
	'id' => [],                     // skipped
]);
// WHERE `name` <> 'Laptop' AND `category_id` NOT IN (1, 2, 3) AND `description` IS NOT NULL

Από προεπιλογή, οι όροι συνδυάζονται με τον τελεστή AND. Μπορείτε να αλλάξετε αυτή τη συμπεριφορά χρησιμοποιώντας τον τελεστή ?or.

ORDER BY Κανόνες

Η ρήτρα ORDER BY μπορεί να οριστεί ως πίνακας, όπου τα κλειδιά αντιπροσωπεύουν στήλες και οι τιμές είναι booleans που υποδεικνύουν αύξουσα σειρά:

$database->query('SELECT id FROM author ORDER BY', [
	'id' => true,  // αύξουσα
	'name' => false, // φθίνουσα
]);
// SELECT id FROM author ORDER BY `id`, `name` DESC

Εισαγωγή δεδομένων (INSERT)

Για να εισαγάγετε εγγραφές, χρησιμοποιήστε τη δήλωση SQL INSERT.

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

Η μέθοδος getInsertId() επιστρέφει το αναγνωριστικό της τελευταίας εισαχθείσας γραμμής. Για ορισμένες βάσεις δεδομένων (π.χ. PostgreSQL), πρέπει να καθορίσετε το όνομα της ακολουθίας χρησιμοποιώντας την εντολή $database->getInsertId($sequenceId).

Μπορείτε επίσης να περάσετε ειδικές τιμές, όπως αρχεία, αντικείμενα DateTime ή τύπους enum, ως παραμέτρους.

Εισαγωγή πολλών εγγραφών ταυτόχρονα:

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

Η εκτέλεση μιας INSERT δέσμης είναι πολύ πιο γρήγορη, επειδή εκτελείται μόνο ένα ερώτημα στη βάση δεδομένων αντί για πολλαπλά μεμονωμένα ερωτήματα.

Σημείωση ασφαλείας: Ποτέ μην χρησιμοποιείτε μη επικυρωμένα δεδομένα ως $values. Εξοικειωθείτε με τους πιθανούς κινδύνους.

Ενημέρωση δεδομένων (UPDATE)

Για να ενημερώσετε τις εγγραφές, χρησιμοποιήστε την εντολή SQL UPDATE.

// Ενημέρωση μίας εγγραφής
$values = [
	'name' => 'John Smith',
];
$result = $database->query('UPDATE users SET ? WHERE id = ?', $values, 1);

Μπορείτε να ελέγξετε τον αριθμό των επηρεαζόμενων γραμμών χρησιμοποιώντας την εντολή $result->getRowCount().

Μπορείτε να χρησιμοποιήσετε τους τελεστές += και -= στο UPDATE:

$database->query('UPDATE users SET ? WHERE id = ?', [
	'login_count+=' => 1, // αύξηση login_count
], 1);

Για να εισαγάγετε ή να ενημερώσετε μια εγγραφή εάν υπάρχει ήδη, χρησιμοποιήστε την τεχνική 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

Σημειώστε ότι η Nette Database αναγνωρίζει το πλαίσιο της εντολής SQL στο οποίο χρησιμοποιείται μια παράμετρος με πίνακα και παράγει τον κώδικα SQL αναλόγως. Για παράδειγμα, κατασκεύασε το (id, name, year) VALUES (123, 'Jim', 1978) από τον πρώτο πίνακα, ενώ μετέτρεψε τον δεύτερο σε name = 'Jim', year = 1978. Αυτό καλύπτεται λεπτομερέστερα στην ενότητα Υποδείξεις για την κατασκευή της SQL.

Διαγραφή δεδομένων (DELETE)

Για να διαγράψετε εγγραφές, χρησιμοποιήστε την εντολή SQL DELETE. Παράδειγμα με τον αριθμό των διαγραμμένων γραμμών:

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

Υποδείξεις κατασκευής SQL

Τα SQL placeholders σας επιτρέπουν να ελέγχετε τον τρόπο με τον οποίο οι τιμές των παραμέτρων ενσωματώνονται στις εκφράσεις SQL:

Υπόδειξη Περιγραφή Αυτόματη χρήση για
?name Χρησιμοποιείται για ονόματα πινάκων ή στηλών
?values Δημιουργεί (key, ...) VALUES (value, ...) INSERT ... ?, REPLACE ... ?
?set Δημιουργεί αναθέσεις key = value, ... SET ?, KEY UPDATE ?
?and Ενώνει όρους σε έναν πίνακα με AND WHERE ?, HAVING ?
?or Συνδέει όρους σε πίνακα με OR
?order Δημιουργεί τη ρήτρα ORDER BY ORDER BY ?, GROUP BY ?

Για τη δυναμική εισαγωγή ονομάτων πινάκων ή στηλών, χρησιμοποιήστε το ?name. Η Nette Database διασφαλίζει τη σωστή αποφυγή σύμφωνα με τις συμβάσεις της βάσης δεδομένων (π.χ., περικλείοντας σε backticks για τη MySQL).

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

Προειδοποίηση: Χρησιμοποιείτε τον πλαστό φορέα ?name μόνο για επικυρωμένα ονόματα πινάκων και στηλών. Διαφορετικά, διακινδυνεύετε ευπάθειες ασφαλείας.

Άλλες υποδείξεις δεν είναι συνήθως απαραίτητο να καθοριστούν, καθώς η Nette χρησιμοποιεί έξυπνη αυτόματη ανίχνευση κατά την κατασκευή ερωτημάτων SQL (βλ. τρίτη στήλη του πίνακα). Ωστόσο, μπορείτε να τις χρησιμοποιήσετε σε περιπτώσεις όπου θέλετε να συνδυάσετε συνθήκες χρησιμοποιώντας OR αντί για 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'

Ειδικές αξίες

Εκτός από τους τυπικούς κλιμακωτούς τύπους (π.χ. string, int, bool), μπορείτε επίσης να περάσετε ειδικές τιμές ως παραμέτρους:

  • Αρχεία: Χρησιμοποιήστε το fopen('file.png', 'r') για να εισαγάγετε το δυαδικό περιεχόμενο ενός αρχείου.
  • Ημερομηνία και ώρα: Τα αντικείμενα DateTime μετατρέπονται αυτόματα στη μορφή ημερομηνίας της βάσης δεδομένων.
  • Τιμές Enum: Οι περιπτώσεις του enum μετατρέπονται στις αντίστοιχες τιμές τους.
  • SQL Literals: Δημιουργούνται με τη χρήση του Connection::literal('NOW()'), εισάγονται απευθείας στο ερώτημα.
$database->query('INSERT INTO articles ?', [
	'title' => 'My Article',
	'published_at' => new DateTime,
	'content' => fopen('image.png', 'r'),
	'state' => Status::Draft,
]);

Για βάσεις δεδομένων που δεν υποστηρίζουν εγγενώς τον τύπο datetime (π.χ. SQLite και Oracle), οι τιμές DateTime μετατρέπονται σύμφωνα με την επιλογή ρύθμισης formatDateTime (προεπιλογή: U για Unix timestamp).

Κυριολεκτικές λέξεις SQL

Σε ορισμένες περιπτώσεις, μπορεί να χρειαστεί να εισαγάγετε ακατέργαστο κώδικα SQL ως τιμή χωρίς να τον αντιμετωπίσετε ως συμβολοσειρά ή να τον διαφυλάξετε. Για το σκοπό αυτό, χρησιμοποιήστε αντικείμενα της κλάσης Nette\Database\SqlLiteral, τα οποία μπορείτε να δημιουργήσετε χρησιμοποιώντας τη μέθοδο Connection::literal().

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

Εναλλακτικά:

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

SQL literals μπορούν επίσης να περιέχουν παραμέτρους:

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

Αυτό επιτρέπει ευέλικτους συνδυασμούς:

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

Λήψη δεδομένων

Συντομεύσεις για ερωτήματα SELECT

Για την απλούστευση της ανάκτησης δεδομένων, η κλάση Connection παρέχει διάφορες συντομεύσεις που συνδυάζουν μια κλήση query() με μια επακόλουθη κλήση fetch*(). Αυτές οι μέθοδοι δέχονται τις ίδιες παραμέτρους με την query(), δηλαδή ένα ερώτημα SQL και προαιρετικές παραμέτρους. Μια λεπτομερής περιγραφή των μεθόδων fetch*() μπορεί να βρεθεί παρακάτω.

fetch($sql, ...$params): ?Row Εκτελεί το ερώτημα και ανακτά την πρώτη γραμμή ως αντικείμενο Row.
fetchAll($sql, ...$params): array Εκτελεί το ερώτημα και ανακτά όλες τις γραμμές ως πίνακα αντικειμένων Row.
fetchPairs($sql, ...$params): array Εκτελεί το ερώτημα και ανακτά έναν συσχετιστικό πίνακα όπου η πρώτη στήλη είναι το κλειδί και η δεύτερη η τιμή.
fetchField($sql, ...$params): mixed Εκτελεί το ερώτημα και ανακτά την τιμή του πρώτου κελιού της πρώτης γραμμής.
fetchList($sql, ...$params): ?array Εκτελεί το ερώτημα και ανακτά την πρώτη γραμμή ως δεικτοδοτούμενο πίνακα.

Παράδειγμα:

// fetchField() - επιστρέφει την τιμή του πρώτου κελιού
$count = $database->query('SELECT COUNT(*) FROM articles')
	->fetchField();

foreach – Γραμμές

Μετά την εκτέλεση ενός ερωτήματος, επιστρέφεται ένα αντικείμενο ResultSet, το οποίο σας επιτρέπει να επαναλαμβάνετε τα αποτελέσματα με διάφορους τρόπους. Ο απλούστερος και πιο αποδοτικός στη μνήμη τρόπος ανάκτησης γραμμών είναι η επανάληψη σε έναν βρόχο foreach. Αυτή η μέθοδος επεξεργάζεται τις γραμμές μία προς μία και αποφεύγει την αποθήκευση όλων των δεδομένων στη μνήμη ταυτόχρονα.

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

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

Το ResultSet μπορεί να επαναληφθεί μόνο μία φορά. Εάν πρέπει να την επαναλάβετε πολλές φορές, πρέπει πρώτα να φορτώσετε τα δεδομένα σε έναν πίνακα, για παράδειγμα, χρησιμοποιώντας τη μέθοδο fetchAll().

fetch(): ?Row

Εκτελεί το ερώτημα και ανακτά μια μόνο γραμμή ως αντικείμενο Row. Εάν δεν υπάρχουν άλλες διαθέσιμες γραμμές, επιστρέφει το null. Αυτή η μέθοδος προωθεί τον εσωτερικό δείκτη στην επόμενη γραμμή.

$result = $database->query('SELECT * FROM users');
$row = $result->fetch(); // φέρνει την πρώτη σειρά
if ($row) {
	echo $row->name;
}

fetchAll(): array

Λαμβάνει όλες τις υπόλοιπες γραμμές από το ResultSet ως πίνακα αντικειμένων Row.

$result = $database->query('SELECT * FROM users');
$rows = $result->fetchAll(); // φέρνει όλες τις γραμμές
foreach ($rows as $row) {
	echo $row->name;
}

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

Αντλεί τα αποτελέσματα ως συσχετιστικό πίνακα. Το πρώτο όρισμα καθορίζει τη στήλη που θα χρησιμοποιηθεί ως κλειδί και το δεύτερο όρισμα καθορίζει τη στήλη που θα χρησιμοποιηθεί ως τιμή:

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

Εάν παρέχεται μόνο η πρώτη παράμετρος, η τιμή θα είναι ολόκληρη η γραμμή (ως αντικείμενο Row ):

$rows = $result->fetchPairs('id');
// [1 => Row(id: 1, name: 'John'), 2 => Row(id: 2, name: 'Jane'), ...]

Εάν το null δίνεται ως κλειδί, ο πίνακας θα έχει αριθμητική ευρετηρίαση ξεκινώντας από το μηδέν:

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

fetchPairs (Closure $callback)array

Εναλλακτικά, μπορείτε να παρέχετε μια επανάκληση που καθορίζει τα ζεύγη κλειδιών-τιμών ή τις τιμές για κάθε γραμμή.

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

// Η επανάκληση μπορεί επίσης να επιστρέψει έναν πίνακα με ένα ζεύγος κλειδιών και τιμών:
$names = $result->fetchPairs(fn($row) => [$row->name, $row->age]);
// ['John' => 46, 'Jane' => 21, ...]

fetchField(): mixed

Λαμβάνει την τιμή του πρώτου κελιού της τρέχουσας γραμμής. Αν δεν υπάρχουν άλλες διαθέσιμες γραμμές, επιστρέφει null. Αυτή η μέθοδος προωθεί τον εσωτερικό δείκτη στην επόμενη γραμμή.

$result = $database->query('SELECT name FROM users');
$name = $result->fetchField(); // ανακτά το όνομα από την πρώτη σειρά

fetchList(): ?array

Λαμβάνει τη γραμμή ως δεικτοδοτούμενο πίνακα. Αν δεν υπάρχουν άλλες διαθέσιμες γραμμές, επιστρέφει null. Αυτή η μέθοδος προωθεί τον εσωτερικό δείκτη στην επόμενη γραμμή.

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

getRowCount(): ?int

Επιστρέφει τον αριθμό των γραμμών που επηρεάστηκαν από το τελευταίο ερώτημα UPDATE ή DELETE. Για τα ερωτήματα SELECT, επιστρέφει τον αριθμό των γραμμών που ανακτήθηκαν, αλλά αυτό μπορεί να μην είναι πάντα γνωστό – σε τέτοιες περιπτώσεις, επιστρέφει null.

getColumnCount(): ?int

Επιστρέφει τον αριθμό των στηλών στο ResultSet.

Πληροφορίες ερωτήματος

Για να ανακτήσετε λεπτομέρειες σχετικά με το πιο πρόσφατα εκτελεσμένο ερώτημα, χρησιμοποιήστε:

echo $database->getLastQueryString(); // εξάγει το ερώτημα SQL

$result = $database->query('SELECT * FROM articles');
echo $result->getQueryString();    // εξάγει το ερώτημα SQL
echo $result->getTime();           // εξάγει το χρόνο εκτέλεσης σε δευτερόλεπτα

Για να εμφανίσετε το αποτέλεσμα ως πίνακα HTML, χρησιμοποιήστε:

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

Μπορείτε επίσης να ανακτήσετε πληροφορίες σχετικά με τους τύπους των στηλών από τη διεύθυνση ResultSet:

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

foreach ($types as $column => $type) {
	echo "$column is of type $type->type"; // π.χ., 'id is of type int'
}

Καταγραφή ερωτημάτων

Μπορείτε να υλοποιήσετε προσαρμοσμένη καταγραφή ερωτημάτων. Το συμβάν onQuery είναι ένας πίνακας από callbacks που καλούνται μετά από κάθε εκτέλεση ερωτήματος:

$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');
	}
};
έκδοση: 4.0