Πρόσβαση SQL
Η Nette Database προσφέρει δύο τρόπους: μπορείτε να γράψετε μόνοι σας ερωτήματα SQL (πρόσβαση SQL) ή να τα αφήσετε να δημιουργηθούν αυτόματα (βλ. Explorer). Η πρόσβαση SQL σάς δίνει πλήρη έλεγχο των ερωτημάτων, εξασφαλίζοντας ταυτόχρονα την ασφαλή σύνταξή τους.
Λεπτομέρειες σχετικά με τη σύνδεση και τη διαμόρφωση της βάσης δεδομένων θα βρείτε στο κεφάλαιο Σύνδεση και διαμόρφωση.
Βασικά ερωτήματα
Για την υποβολή ερωτημάτων στη βάση δεδομένων, χρησιμοποιείται η
μέθοδος query()
. Αυτή επιστρέφει ένα αντικείμενο ResultSet, το οποίο
αντιπροσωπεύει το αποτέλεσμα του ερωτήματος. Σε περίπτωση αποτυχίας, η
μέθοδος προκαλεί εξαίρεση. Μπορούμε να διατρέξουμε το
αποτέλεσμα του ερωτήματος χρησιμοποιώντας έναν βρόχο foreach
ή να
χρησιμοποιήσουμε κάποια από τις βοηθητικές
συναρτήσεις.
Για την ασφαλή εισαγωγή τιμών σε ερωτήματα SQL, χρησιμοποιούμε παραμετροποιημένα ερωτήματα. Η Nette Database τα καθιστά εξαιρετικά απλά – αρκεί να προσθέσετε ένα κόμμα και την τιμή μετά το ερώτημα SQL:
Με περισσότερες παραμέτρους, έχετε δύο επιλογές σύνταξης. Μπορείτε είτε να „διανθίσετε“ το ερώτημα SQL με παραμέτρους:
Ή να γράψετε πρώτα ολόκληρο το ερώτημα SQL και στη συνέχεια να επισυνάψετε όλες τις παραμέτρους:
Προστασία από SQL injection
Γιατί είναι σημαντικό να χρησιμοποιείτε παραμετροποιημένα ερωτήματα; Επειδή σας προστατεύουν από την επίθεση που ονομάζεται SQL injection, κατά την οποία ο εισβολέας θα μπορούσε να εισάγει δικές του εντολές SQL και έτσι να αποκτήσει ή να καταστρέψει δεδομένα στη βάση δεδομένων.
Ποτέ μην εισάγετε μεταβλητές απευθείας στο ερώτημα SQL! Πάντα να χρησιμοποιείτε παραμετροποιημένα ερωτήματα, τα οποία σας προστατεύουν από το SQL injection.
Ενημερωθείτε για τους πιθανούς κινδύνους ασφαλείας.
Τεχνικές ερωτημάτων
Συνθήκες WHERE
Μπορείτε να γράψετε τις συνθήκες WHERE ως έναν συσχετιστικό πίνακα (associative array), όπου τα κλειδιά είναι τα ονόματα των στηλών και οι τιμές είναι τα δεδομένα για σύγκριση. Η Nette Database επιλέγει αυτόματα τον καταλληλότερο τελεστή SQL ανάλογα με τον τύπο της τιμής.
Στο κλειδί, μπορείτε επίσης να καθορίσετε ρητά τον τελεστή για σύγκριση:
Το Nette χειρίζεται αυτόματα ειδικές περιπτώσεις όπως τιμές null
ή
πίνακες.
Για αρνητικές συνθήκες, χρησιμοποιήστε τον τελεστή NOT
:
Για τη σύνδεση συνθηκών, χρησιμοποιείται ο τελεστής AND
. Αυτό
μπορεί να αλλάξει χρησιμοποιώντας το placeholder
?or.
Κανόνες ORDER BY
Η ταξινόμηση ORDER BY
μπορεί να γραφτεί χρησιμοποιώντας έναν
πίνακα. Στα κλειδιά, αναφέρουμε τις στήλες και η τιμή θα είναι μια boolean
τιμή που καθορίζει εάν θα ταξινομηθεί αύξουσα:
Εισαγωγή δεδομένων (INSERT)
Για την εισαγωγή εγγραφών, χρησιμοποιείται η εντολή SQL INSERT
.
Η μέθοδος getInsertId()
επιστρέφει το ID της τελευταίας εισαχθείσας
γραμμής. Σε ορισμένες βάσεις δεδομένων (π.χ. PostgreSQL), είναι απαραίτητο να
καθορίσετε ως παράμετρο το όνομα της ακολουθίας (sequence) από την οποία θα
δημιουργηθεί το ID χρησιμοποιώντας $database->getInsertId($sequenceId)
.
Ως παραμέτρους μπορούμε επίσης να περάσουμε Ειδικές τιμές όπως αρχεία, αντικείμενα DateTime ή τύπους enum.
Εισαγωγή πολλαπλών εγγραφών ταυτόχρονα:
Η πολλαπλή INSERT είναι πολύ ταχύτερη, επειδή εκτελείται ένα μόνο ερώτημα βάσης δεδομένων, αντί για πολλά μεμονωμένα.
Προειδοποίηση ασφαλείας: Ποτέ μην χρησιμοποιείτε μη επικυρωμένα
δεδομένα ως $values
. Ενημερωθείτε για τους πιθανούς κινδύνους.
Ενημέρωση δεδομένων (UPDATE)
Για την ενημέρωση εγγραφών, χρησιμοποιείται η εντολή SQL UPDATE
.
Ο αριθμός των επηρεασμένων γραμμών επιστρέφεται από το
$result->getRowCount()
.
Για το UPDATE, μπορούμε να χρησιμοποιήσουμε τους τελεστές +=
και
-=
:
Παράδειγμα εισαγωγής ή τροποποίησης εγγραφής, εάν υπάρχει ήδη.
Χρησιμοποιούμε την τεχνική ON DUPLICATE KEY UPDATE
:
Παρατηρήστε ότι η Nette Database αναγνωρίζει σε ποιο πλαίσιο της εντολής SQL
εισάγουμε την παράμετρο με τον πίνακα και ανάλογα συνθέτει τον κώδικα
SQL. Έτσι, από τον πρώτο πίνακα συνέθεσε (id, name, year) VALUES (123, 'Jim', 1978)
,
ενώ τον δεύτερο τον μετέτρεψε στη μορφή name = 'Jim', year = 1978
. Αυτό το
εξετάζουμε λεπτομερέστερα στην ενότητα Hints για
τη σύνταξη SQL.
Διαγραφή δεδομένων (DELETE)
Για τη διαγραφή εγγραφών, χρησιμοποιείται η εντολή SQL DELETE
.
Παράδειγμα με λήψη του αριθμού των διαγραμμένων γραμμών:
Hints για τη σύνταξη SQL
Ένα hint είναι ένα ειδικό placeholder στο ερώτημα SQL που λέει πώς πρέπει να μεταγραφεί η τιμή της παραμέτρου σε έκφραση SQL:
Hint | Περιγραφή | Χρησιμοποιείται αυτόματα |
---|---|---|
?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 ? |
Για τη δυναμική εισαγωγή ονομάτων πινάκων και στηλών στο ερώτημα,
χρησιμοποιείται το placeholder ?name
. Η Nette Database φροντίζει για τη σωστή
επεξεργασία των αναγνωριστικών σύμφωνα με τις συμβάσεις της
συγκεκριμένης βάσης δεδομένων (π.χ. κλείσιμο σε ανάποδα εισαγωγικά
` ` `
στην MySQL).
Προειδοποίηση: χρησιμοποιήστε το σύμβολο ?name
μόνο για
ονόματα πινάκων και στηλών από επικυρωμένες εισόδους, διαφορετικά
εκτίθεστε σε κίνδυνο ασφαλείας.
Τα υπόλοιπα hints συνήθως δεν χρειάζεται να αναφέρονται, καθώς το Nette
χρησιμοποιεί έξυπνη αυτόματη ανίχνευση κατά τη σύνθεση του ερωτήματος
SQL (βλ. τρίτη στήλη του πίνακα). Αλλά μπορείτε να το χρησιμοποιήσετε, για
παράδειγμα, σε μια κατάσταση όπου θέλετε να συνδέσετε συνθήκες
χρησιμοποιώντας OR
αντί για AND
:
Ειδικές τιμές
Εκτός από τους συνήθεις σκαλωτούς τύπους (string, int, bool), μπορείτε να περάσετε και ειδικές τιμές ως παραμέτρους:
- αρχεία:
fopen('image.gif', 'r')
εισάγει το δυαδικό περιεχόμενο του αρχείου - ημερομηνία και ώρα: τα αντικείμενα
DateTime
μετατρέπονται στη μορφή της βάσης δεδομένων - τύποι enum: οι παρουσίες
enum
μετατρέπονται στην τιμή τους - SQL literals: δημιουργημένα με
Connection::literal('NOW()')
εισάγονται απευθείας στο ερώτημα
Σε βάσεις δεδομένων που δεν έχουν εγγενή υποστήριξη για τον τύπο
δεδομένων datetime
(όπως SQLite και Oracle), το DateTime
μετατρέπεται
στην τιμή που καθορίζεται στη διαμόρφωση της βάσης
δεδομένων με την επιλογή formatDateTime
(η προεπιλεγμένη τιμή είναι
U
– unix timestamp).
SQL Literals
Σε ορισμένες περιπτώσεις, πρέπει να αναφέρετε απευθείας κώδικα SQL ως
τιμή, ο οποίος όμως δεν πρέπει να θεωρηθεί ως συμβολοσειρά και να
υποστεί escaping. Για αυτό χρησιμεύουν τα αντικείμενα της κλάσης
Nette\Database\SqlLiteral
. Τα δημιουργεί η μέθοδος Connection::literal()
.
Ή εναλλακτικά:
Τα SQL literals μπορούν να περιέχουν παραμέτρους:
Χάρη σε αυτό, μπορούμε να δημιουργήσουμε ενδιαφέροντες συνδυασμούς:
Λήψη δεδομένων
Συντομεύσεις για ερωτήματα 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 |
Εκτελεί το ερώτημα και επιστρέφει την πρώτη γραμμή ως αριθμημένο πίνακα |
Παράδειγμα:
foreach
– επανάληψη μέσω γραμμών
Μετά την εκτέλεση του ερωτήματος, επιστρέφεται ένα αντικείμενο ResultSet, το οποίο επιτρέπει την
περιήγηση στα αποτελέσματα με διάφορους τρόπους. Ο ευκολότερος τρόπος
για να εκτελέσετε ένα ερώτημα και να λάβετε τις γραμμές είναι με
επανάληψη σε έναν βρόχο foreach
. Αυτός ο τρόπος είναι ο πιο
αποδοτικός από πλευράς μνήμης, καθώς επιστρέφει τα δεδομένα σταδιακά
και δεν τα αποθηκεύει όλα στη μνήμη ταυτόχρονα.
Το ResultSet
μπορεί να επαναληφθεί μόνο μία φορά. Εάν
χρειάζεται να επαναλάβετε πολλές φορές, πρέπει πρώτα να φορτώσετε τα
δεδομένα σε έναν πίνακα, για παράδειγμα χρησιμοποιώντας τη μέθοδο
fetchAll()
.
fetch(): ?Row
Επιστρέφει μια γραμμή ως αντικείμενο Row
. Εάν δεν υπάρχουν
άλλες γραμμές, επιστρέφει null
. Μετακινεί τον εσωτερικό δείκτη
στην επόμενη γραμμή.
fetchAll(): array
Επιστρέφει όλες τις υπόλοιπες γραμμές από το ResultSet
ως πίνακα
αντικειμένων Row
.
fetchPairs(string|int|null $key = null, string|int|null $value = null): array
Επιστρέφει τα αποτελέσματα ως συσχετιστικό πίνακα. Το πρώτο όρισμα καθορίζει το όνομα της στήλης που θα χρησιμοποιηθεί ως κλειδί στον πίνακα, το δεύτερο όρισμα καθορίζει το όνομα της στήλης που θα χρησιμοποιηθεί ως τιμή:
Εάν αναφέρουμε μόνο την πρώτη παράμετρο, η τιμή θα είναι ολόκληρη η
γραμμή, δηλαδή το αντικείμενο Row
:
Σε περίπτωση διπλότυπων κλειδιών, χρησιμοποιείται η τιμή από την
τελευταία γραμμή. Κατά τη χρήση null
ως κλειδί, ο πίνακας θα
αριθμηθεί αριθμητικά από το μηδέν (τότε δεν προκύπτουν συγκρούσεις):
fetchPairs(Closure $callback): array
Εναλλακτικά, μπορείτε να δώσετε ως παράμετρο ένα callback, το οποίο θα επιστρέφει για κάθε γραμμή είτε την ίδια την τιμή, είτε ένα ζεύγος κλειδιού-τιμής.
fetchField(): mixed
Επιστρέφει την τιμή του πρώτου πεδίου από την τρέχουσα γραμμή. Εάν
δεν υπάρχουν άλλες γραμμές, επιστρέφει null
. Μετακινεί τον
εσωτερικό δείκτη στην επόμενη γραμμή.
fetchList(): ?array
Επιστρέφει μια γραμμή ως αριθμημένο πίνακα. Εάν δεν υπάρχουν άλλες
γραμμές, επιστρέφει null
. Μετακινεί τον εσωτερικό δείκτη στην
επόμενη γραμμή.
getRowCount(): ?int
Επιστρέφει τον αριθμό των επηρεασμένων γραμμών από το τελευταίο
ερώτημα UPDATE
ή DELETE
. Για το SELECT
, είναι ο αριθμός των
επιστρεφόμενων γραμμών, αλλά αυτός μπορεί να μην είναι γνωστός – σε
αυτή την περίπτωση, η μέθοδος επιστρέφει null
.
getColumnCount(): ?int
Επιστρέφει τον αριθμό των στηλών στο ResultSet
.
Πληροφορίες για τα ερωτήματα
Για σκοπούς εντοπισμού σφαλμάτων, μπορούμε να λάβουμε πληροφορίες σχετικά με το τελευταίο εκτελεσμένο ερώτημα:
Για την εμφάνιση του αποτελέσματος ως πίνακα HTML, μπορείτε να χρησιμοποιήσετε:
Το ResultSet προσφέρει πληροφορίες σχετικά με τους τύπους των στηλών:
Καταγραφή ερωτημάτων
Μπορούμε να υλοποιήσουμε τη δική μας καταγραφή ερωτημάτων. Το συμβάν
onQuery
είναι ένας πίνακας callbacks που καλούνται μετά από κάθε
εκτελεσμένο ερώτημα: