Άμεση 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 < ?', $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');
}
};