Database Explorer
Ο Explorer προσφέρει έναν διαισθητικό και αποτελεσματικό τρόπο εργασίας με τη βάση δεδομένων. Φροντίζει αυτόματα για τις σχέσεις μεταξύ των πινάκων και τη βελτιστοποίηση των ερωτημάτων (queries), ώστε να μπορείτε να επικεντρωθείτε στην εφαρμογή σας. Λειτουργεί αμέσως χωρίς καμία ρύθμιση. Αν χρειάζεστε πλήρη έλεγχο των ερωτημάτων SQL, μπορείτε να χρησιμοποιήσετε την προσέγγιση SQL.
- Η εργασία με τα δεδομένα είναι φυσική και εύκολα κατανοητή.
- Παράγει βελτιστοποιημένα ερωτήματα SQL που φορτώνουν μόνο τα απαραίτητα δεδομένα.
- Επιτρέπει εύκολη πρόσβαση σε σχετιζόμενα δεδομένα χωρίς την ανάγκη γραφής ερωτημάτων JOIN.
- Λειτουργεί άμεσα χωρίς καμία διαμόρφωση ή παραγωγή οντοτήτων (entities).
Με τον Explorer ξεκινάτε καλώντας τη μέθοδο table()
του αντικειμένου
Nette\Database\Explorer (λεπτομέρειες
για τη σύνδεση θα βρείτε στο κεφάλαιο Σύνδεση και Διαμόρφωση):
$books = $explorer->table('book'); // 'book' είναι το όνομα του πίνακα
Η μέθοδος επιστρέφει ένα αντικείμενο Selection, το οποίο
αντιπροσωπεύει ένα ερώτημα SQL. Σε αυτό το αντικείμενο μπορούμε να
συνδέσουμε περαιτέρω μεθόδους για φιλτράρισμα και ταξινόμηση των
αποτελεσμάτων. Το ερώτημα συντάσσεται και εκτελείται μόνο τη στιγμή
που αρχίζουμε να ζητάμε δεδομένα, για παράδειγμα, με τη διέλευση ενός
βρόχου foreach
. Κάθε γραμμή αντιπροσωπεύεται από ένα αντικείμενο ActiveRow:
foreach ($books as $book) {
echo $book->title; // εμφάνιση της στήλης 'title'
echo $book->author_id; // εμφάνιση της στήλης 'author_id'
}
Ο Explorer διευκολύνει θεμελιωδώς την εργασία με τις σχέσεις μεταξύ πινάκων. Το ακόλουθο παράδειγμα δείχνει πόσο εύκολα μπορούμε να εμφανίσουμε δεδομένα από συνδεδεμένους πίνακες (βιβλία και οι συγγραφείς τους). Παρατηρήστε ότι δεν χρειάζεται να γράψουμε κανένα ερώτημα JOIN, το Nette τα δημιουργεί για εμάς:
$books = $explorer->table('book');
foreach ($books as $book) {
echo 'Βιβλίο: ' . $book->title; // Book:
echo 'Συγγραφέας: ' . $book->author->name; // δημιουργεί JOIN στον πίνακα 'author' // Author:
}
Το Nette Database Explorer βελτιστοποιεί τα ερωτήματα ώστε να είναι όσο το δυνατόν πιο αποτελεσματικά. Το παραπάνω παράδειγμα εκτελεί μόνο δύο ερωτήματα SELECT, ανεξάρτητα από το αν επεξεργαζόμαστε 10 ή 10.000 βιβλία.
Επιπλέον, ο Explorer παρακολουθεί ποιες στήλες χρησιμοποιούνται στον κώδικα και φορτώνει από τη βάση δεδομένων μόνο αυτές, εξοικονομώντας έτσι περαιτέρω απόδοση. Αυτή η συμπεριφορά είναι πλήρως αυτόματη και προσαρμοστική. Αν αργότερα τροποποιήσετε τον κώδικα και αρχίσετε να χρησιμοποιείτε άλλες στήλες, ο Explorer προσαρμόζει αυτόματα τα ερωτήματα. Δεν χρειάζεται να ρυθμίσετε τίποτα, ούτε να σκεφτείτε ποιες στήλες θα χρειαστείτε – αφήστε το στο Nette.
Φιλτράρισμα και Ταξινόμηση
Η κλάση Selection
παρέχει μεθόδους για το φιλτράρισμα και την
ταξινόμηση της επιλογής δεδομένων.
where($condition, ...$params) |
Προσθέτει συνθήκη WHERE. Πολλαπλές συνθήκες συνδέονται με τον τελεστή AND |
whereOr(array $conditions) |
Προσθέτει μια ομάδα συνθηκών WHERE συνδεδεμένων με τον τελεστή OR |
wherePrimary($value) |
Προσθέτει συνθήκη WHERE βάσει του πρωτεύοντος κλειδιού |
order($columns, ...$params) |
Ορίζει την ταξινόμηση ORDER BY |
select($columns, ...$params) |
Καθορίζει τις στήλες που πρέπει να φορτωθούν |
limit($limit, $offset = null) |
Περιορίζει τον αριθμό των γραμμών (LIMIT) και προαιρετικά ορίζει το OFFSET |
page($page, $itemsPerPage, &$total = null) |
Ορίζει τη σελίδωση |
group($columns, ...$params) |
Ομαδοποιεί τις γραμμές (GROUP BY) |
having($condition, ...$params) |
Προσθέτει συνθήκη HAVING για το φιλτράρισμα των ομαδοποιημένων γραμμών |
Οι μέθοδοι μπορούν να αλυσιδωθούν (το λεγόμενο fluent interface):
$table->where(...)->order(...)->limit(...)
.
Σε αυτές τις μεθόδους μπορείτε επίσης να χρησιμοποιείτε ειδική σημειογραφία για την πρόσβαση σε δεδομένα από σχετικούς πίνακες.
Escaping και Αναγνωριστικά
Οι μέθοδοι κάνουν αυτόματα escaping τις παραμέτρους και περικλείουν σε εισαγωγικά τα αναγνωριστικά (ονόματα πινάκων και στηλών), αποτρέποντας έτσι το SQL injection. Για τη σωστή λειτουργία, είναι απαραίτητο να τηρούνται ορισμένοι κανόνες:
- Λέξεις-κλειδιά, ονόματα συναρτήσεων, διαδικασιών κ.λπ. γράφονται με κεφαλαία γράμματα.
- Ονόματα στηλών και πινάκων γράφονται με μικρά γράμματα.
- Τα strings πάντα εισάγονται μέσω παραμέτρων.
where('name = ' . $name); // ΚΡΙΣΙΜΗ ΕΥΠΑΘΕΙΑ: SQL injection
where('name LIKE "%search%"'); // ΛΑΘΟΣ: περιπλέκει την αυτόματη περικλείωση σε εισαγωγικά
where('name LIKE ?', '%search%'); // ΣΩΣΤΟ: η τιμή εισάγεται μέσω παραμέτρου
where('name like ?', $name); // ΛΑΘΟΣ: παράγει: `name` `like` ?
where('name LIKE ?', $name); // ΣΩΣΤΟ: παράγει: `name` LIKE ?
where('LOWER(name) = ?', $value);// ΣΩΣΤΟ: LOWER(`name`) = ?
where (string|array $condition, …$parameters): static
Φιλτράρει τα αποτελέσματα χρησιμοποιώντας συνθήκες WHERE. Η ισχυρή της πλευρά είναι η έξυπνη διαχείριση διαφόρων τύπων τιμών και η αυτόματη επιλογή τελεστών SQL.
Βασική χρήση:
$table->where('id', $value); // WHERE `id` = 123
$table->where('id > ?', $value); // WHERE `id` > 123
$table->where('id = ? OR name = ?', $id, $name); // WHERE `id` = 1 OR `name` = 'Jon Snow'
Χάρη στην αυτόματη ανίχνευση των κατάλληλων τελεστών, δεν χρειάζεται να ασχολούμαστε με διάφορες ειδικές περιπτώσεις. Το Nette τις λύνει για εμάς:
$table->where('id', 1); // WHERE `id` = 1
$table->where('id', null); // WHERE `id` IS NULL
$table->where('id', [1, 2, 3]); // WHERE `id` IN (1, 2, 3)
// μπορεί να χρησιμοποιηθεί και το placeholder ερωτηματικό (?) χωρίς τελεστή:
$table->where('id ?', 1); // WHERE `id` = 1
Η μέθοδος επεξεργάζεται σωστά και τις αρνητικές συνθήκες και τους κενούς πίνακες:
$table->where('id', []); // WHERE `id` IS NULL AND FALSE -- τίποτα δεν θα βρεθεί
$table->where('id NOT', []); // WHERE `id` IS NULL OR TRUE -- όλα θα βρεθούν
$table->where('NOT (id ?)', []); // WHERE NOT (`id` IS NULL AND FALSE) -- όλα θα βρεθούν
// $table->where('NOT id ?', $ids); Προσοχή - αυτή η σύνταξη δεν υποστηρίζεται
Ως παράμετρο μπορούμε να περάσουμε επίσης το αποτέλεσμα από έναν άλλο πίνακα – θα δημιουργηθεί ένα υποερώτημα (subquery):
// WHERE `id` IN (SELECT `id` FROM `tableName`)
$table->where('id', $explorer->table($tableName));
// WHERE `id` IN (SELECT `col` FROM `tableName`)
$table->where('id', $explorer->table($tableName)->select('col'));
Τις συνθήκες μπορούμε να τις περάσουμε επίσης ως πίνακα, τα στοιχεία του οποίου συνδέονται με AND:
// WHERE (`price_final` < `price_original`) AND (`stock_count` > `min_stock`)
$table->where([
'price_final < price_original',
'stock_count > min_stock',
]);
Στον πίνακα μπορούμε να χρησιμοποιήσουμε ζεύγη κλειδί ⇒ τιμή και το Nette πάλι επιλέγει αυτόματα τους σωστούς τελεστές:
// WHERE (`status` = 'active') AND (`id` IN (1, 2, 3))
$table->where([
'status' => 'active',
'id' => [1, 2, 3],
]);
Στον πίνακα μπορούμε να συνδυάσουμε εκφράσεις SQL με placeholders ερωτηματικά (?) και πολλαπλές παραμέτρους. Αυτό είναι κατάλληλο για πολύπλοκες συνθήκες με ακριβώς καθορισμένους τελεστές:
// WHERE (`age` > 18) AND (ROUND(`score`, 2) > 75.5)
$table->where([
'age > ?' => 18,
'ROUND(score, ?) > ?' => [2, 75.5], // δύο παραμέτρους τις περνάμε ως πίνακα
]);
Οι πολλαπλές κλήσεις where()
συνδέουν αυτόματα τις συνθήκες
με AND.
whereOr (array $parameters): static
Παρόμοια με το where()
, προσθέτει συνθήκες, αλλά με τη διαφορά ότι
τις συνδέει με OR:
// WHERE (`status` = 'active') OR (`deleted` = 1)
$table->whereOr([
'status' => 'active',
'deleted' => true,
]);
Και εδώ μπορούμε να χρησιμοποιήσουμε πιο πολύπλοκες εκφράσεις:
// WHERE (`price` > 1000) OR (`price_with_tax` > 1500)
$table->whereOr([
'price > ?' => 1000,
'price_with_tax > ?' => 1500,
]);
wherePrimary (mixed $key): static
Προσθέτει συνθήκη για το πρωτεύον κλειδί του πίνακα:
// WHERE `id` = 123
$table->wherePrimary(123);
// WHERE `id` IN (1, 2, 3)
$table->wherePrimary([1, 2, 3]);
Αν ο πίνακας έχει σύνθετο πρωτεύον κλειδί (π.χ. foo_id
, bar_id
),
το περνάμε ως πίνακα:
// WHERE `foo_id` = 1 AND `bar_id` = 5
$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch();
// WHERE (`foo_id`, `bar_id`) IN ((1, 5), (2, 3))
$table->wherePrimary([
['foo_id' => 1, 'bar_id' => 5],
['foo_id' => 2, 'bar_id' => 3],
])->fetchAll();
order (string $columns, …$parameters): static
Καθορίζει τη σειρά με την οποία θα επιστραφούν οι γραμμές. Μπορούμε να ταξινομήσουμε με βάση μία ή περισσότερες στήλες, σε φθίνουσα ή αύξουσα σειρά, ή με βάση μια δική μας έκφραση:
$table->order('created'); // ORDER BY `created`
$table->order('created DESC'); // ORDER BY `created` DESC
$table->order('priority DESC, created'); // ORDER BY `priority` DESC, `created`
$table->order('status = ? DESC', 'active'); // ORDER BY `status` = 'active' DESC
select (string $columns, …$parameters): static
Καθορίζει τις στήλες που θα επιστραφούν από τη βάση δεδομένων. Από
προεπιλογή, το Nette Database Explorer επιστρέφει μόνο τις στήλες που
χρησιμοποιούνται πραγματικά στον κώδικα. Τη μέθοδο select()
τη
χρησιμοποιούμε λοιπόν σε περιπτώσεις όπου χρειαζόμαστε να
επιστρέψουμε συγκεκριμένες εκφράσεις:
// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date`
$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y');
Τα ψευδώνυμα (aliases) που ορίζονται με AS
είναι στη συνέχεια
διαθέσιμα ως ιδιότητες του αντικειμένου ActiveRow:
foreach ($table as $row) {
echo $row->formatted_date; // πρόσβαση στο alias
}
limit (?int $limit, ?int $offset = null): static
Περιορίζει τον αριθμό των επιστρεφόμενων γραμμών (LIMIT) και προαιρετικά επιτρέπει τον ορισμό της μετατόπισης (offset):
$table->limit(10); // LIMIT 10 (επιστρέφει τις πρώτες 10 γραμμές)
$table->limit(10, 20); // LIMIT 10 OFFSET 20
Για σελίδωση, είναι προτιμότερο να χρησιμοποιήσετε τη μέθοδο
page()
.
page (int $page, int $itemsPerPage, &$numOfPages = null): static
Διευκολύνει τη σελίδωση των αποτελεσμάτων. Δέχεται τον αριθμό της σελίδας (μετρώντας από το 1) και τον αριθμό των στοιχείων ανά σελίδα. Προαιρετικά, μπορεί να περάσει μια αναφορά σε μια μεταβλητή, στην οποία θα αποθηκευτεί ο συνολικός αριθμός σελίδων:
$numOfPages = null;
$table->page(page: 3, itemsPerPage: 10, $numOfPages);
echo "Συνολικά σελίδες: $numOfPages";
group (string $columns, …$parameters): static
Ομαδοποιεί τις γραμμές με βάση τις καθορισμένες στήλες (GROUP BY). Χρησιμοποιείται συνήθως σε συνδυασμό με συναρτήσεις συγκέντρωσης (aggregation functions):
// Υπολογίζει τον αριθμό των προϊόντων σε κάθε κατηγορία
$table->select('category_id, COUNT(*) AS count')
->group('category_id');
having (string $having, …$parameters): static
Ορίζει συνθήκη για το φιλτράρισμα των ομαδοποιημένων γραμμών (HAVING).
Μπορεί να χρησιμοποιηθεί σε συνδυασμό με τη μέθοδο group()
και
συναρτήσεις συγκέντρωσης:
// Βρίσκει κατηγορίες που έχουν περισσότερα από 100 προϊόντα
$table->select('category_id, COUNT(*) AS count')
->group('category_id')
->having('count > ?', 100);
Ανάγνωση Δεδομένων
Για την ανάγνωση δεδομένων από τη βάση δεδομένων έχουμε στη διάθεσή μας αρκετές χρήσιμες μεθόδους:
foreach ($table as $key => $row) |
Επαναλαμβάνεται σε όλες τις γραμμές, το $key είναι η τιμή του
πρωτεύοντος κλειδιού, το $row είναι αντικείμενο ActiveRow |
$row = $table->get($key) |
Επιστρέφει μία γραμμή με βάση το πρωτεύον κλειδί |
$row = $table->fetch() |
Επιστρέφει την τρέχουσα γραμμή και μετακινεί τον δείκτη στην επόμενη |
$array = $table->fetchPairs() |
Δημιουργεί έναν συσχετιστικό πίνακα από τα αποτελέσματα |
$array = $table->fetchAll() |
Επιστρέφει όλες τις γραμμές ως πίνακα |
count($table) |
Επιστρέφει τον αριθμό των γραμμών στο αντικείμενο Selection |
Το αντικείμενο ActiveRow προορίζεται μόνο για ανάγνωση. Αυτό σημαίνει ότι δεν μπορούν να αλλάξουν οι τιμές των ιδιοτήτων του. Αυτός ο περιορισμός εξασφαλίζει τη συνέπεια των δεδομένων και αποτρέπει απροσδόκητες παρενέργειες. Τα δεδομένα φορτώνονται από τη βάση δεδομένων και οποιαδήποτε αλλαγή θα πρέπει να γίνεται ρητά και ελεγχόμενα.
foreach
– Επανάληψη σε Όλες τις
Γραμμές
Ο ευκολότερος τρόπος για να εκτελέσετε ένα ερώτημα και να λάβετε
γραμμές είναι με την επανάληψη σε έναν βρόχο foreach
. Εκτελεί
αυτόματα το ερώτημα SQL.
$books = $explorer->table('book');
foreach ($books as $key => $book) {
// το $key είναι η τιμή του πρωτεύοντος κλειδιού, το $book είναι ActiveRow
echo "$book->title ({$book->author->name})";
}
get ($key): ?ActiveRow
Εκτελεί το ερώτημα SQL και επιστρέφει τη γραμμή με βάση το πρωτεύον
κλειδί, ή null
, αν δεν υπάρχει.
$book = $explorer->table('book')->get(123); // επιστρέφει ActiveRow με ID 123 ή null
if ($book) {
echo $book->title;
}
fetch(): ?ActiveRow
Επιστρέφει μια γραμμή και μετακινεί τον εσωτερικό δείκτη στην
επόμενη. Αν δεν υπάρχουν άλλες γραμμές, επιστρέφει null
.
$books = $explorer->table('book');
while ($book = $books->fetch()) {
$this->processBook($book);
}
fetchPairs (string|int|null $key = null, string|int|null $value = null): array
Επιστρέφει τα αποτελέσματα ως συσχετιστικό πίνακα. Το πρώτο όρισμα καθορίζει το όνομα της στήλης που θα χρησιμοποιηθεί ως κλειδί στον πίνακα, το δεύτερο όρισμα καθορίζει το όνομα της στήλης που θα χρησιμοποιηθεί ως τιμή:
$authors = $explorer->table('author')->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]
Αν δώσουμε μόνο την πρώτη παράμετρο, η τιμή θα είναι ολόκληρη η
γραμμή, δηλαδή το αντικείμενο ActiveRow
:
$authors = $explorer->table('author')->fetchPairs('id');
// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...]
Σε περίπτωση διπλότυπων κλειδιών, χρησιμοποιείται η τιμή από την
τελευταία γραμμή. Κατά τη χρήση του null
ως κλειδί, ο πίνακας θα
ευρετηριαστεί αριθμητικά από το μηδέν (τότε δεν προκύπτουν
συγκρούσεις):
$authors = $explorer->table('author')->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]
fetchPairs (Closure $callback): array
Εναλλακτικά, μπορείτε να δώσετε ως παράμετρο ένα callback, το οποίο θα επιστρέφει για κάθε γραμμή είτε την ίδια την τιμή, είτε ένα ζεύγος κλειδί-τιμή.
$titles = $explorer->table('book')
->fetchPairs(fn($row) => "$row->title ({$row->author->name})");
// ['Πρώτο βιβλίο (Γιάννης Νοβάκ)', ...]
// Το Callback μπορεί επίσης να επιστρέφει έναν πίνακα με ένα ζεύγος κλειδί & τιμή:
$titles = $explorer->table('book')
->fetchPairs(fn($row) => [$row->title, $row->author->name]);
// ['Πρώτο βιβλίο' => 'Γιάννης Νοβάκ', ...]
fetchAll(): array
Επιστρέφει όλες τις γραμμές ως συσχετιστικό πίνακα αντικειμένων
ActiveRow
, όπου τα κλειδιά είναι οι τιμές των πρωτευόντων
κλειδιών.
$allBooks = $explorer->table('book')->fetchAll();
// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...]
count(): int
Η μέθοδος count()
χωρίς παράμετρο επιστρέφει τον αριθμό των
γραμμών στο αντικείμενο Selection
:
$table->where('category', 1);
$count = $table->count();
$count = count($table); // εναλλακτική
Προσοχή, το count()
με παράμετρο εκτελεί τη συνάρτηση
συγκέντρωσης COUNT στη βάση δεδομένων, βλ. παρακάτω στην ενότητα Συγκέντρωση.
ActiveRow::toArray(): array
Μετατρέπει το αντικείμενο ActiveRow
σε συσχετιστικό πίνακα, όπου
τα κλειδιά είναι τα ονόματα των στηλών και οι τιμές είναι τα αντίστοιχα
δεδομένα.
$book = $explorer->table('book')->get(1);
$bookArray = $book->toArray();
// το $bookArray θα είναι ['id' => 1, 'title' => '...', 'author_id' => ..., ...]
Συγκέντρωση
Η κλάση Selection
παρέχει μεθόδους για εύκολη εκτέλεση
συναρτήσεων συγκέντρωσης (COUNT, SUM, MIN, MAX, AVG κ.λπ.).
count($expr) |
Μετρά τον αριθμό των γραμμών |
min($expr) |
Επιστρέφει την ελάχιστη τιμή στη στήλη |
max($expr) |
Επιστρέφει τη μέγιστη τιμή στη στήλη |
sum($expr) |
Επιστρέφει το άθροισμα των τιμών στη στήλη |
aggregation($function) |
Επιτρέπει την εκτέλεση οποιασδήποτε συνάρτησης συγκέντρωσης. Π.χ.
AVG() , GROUP_CONCAT() |
count (string $expr): int
Εκτελεί ένα ερώτημα SQL με τη συνάρτηση COUNT και επιστρέφει το αποτέλεσμα. Η μέθοδος χρησιμοποιείται για να διαπιστωθεί πόσες γραμμές αντιστοιχούν σε μια συγκεκριμένη συνθήκη:
$count = $table->count('*'); // SELECT COUNT(*) FROM `table`
$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `column`) FROM `table`
Προσοχή, η μέθοδος count() χωρίς παράμετρο επιστρέφει
απλώς τον αριθμό των γραμμών στο αντικείμενο Selection
.
min (string $expr) και max(string $expr)
Οι μέθοδοι min()
και max()
επιστρέφουν την ελάχιστη και τη
μέγιστη τιμή στην καθορισμένη στήλη ή έκφραση:
// SELECT MAX(`price`) FROM `products` WHERE `active` = 1
$maxPrice = $products->where('active', true)
->max('price');
sum (string $expr)
Επιστρέφει το άθροισμα των τιμών στην καθορισμένη στήλη ή έκφραση:
// SELECT SUM(`price` * `items_in_stock`) FROM `products` WHERE `active` = 1
$totalPrice = $products->where('active', true)
->sum('price * items_in_stock');
aggregation (string $function, ?string $groupFunction = null)
Επιτρέπει την εκτέλεση οποιασδήποτε συνάρτησης συγκέντρωσης.
// μέση τιμή προϊόντων στην κατηγορία
$avgPrice = $products->where('category_id', 1)
->aggregation('AVG(price)');
// συνδέει τις ετικέτες του προϊόντος σε ένα string
$tags = $products->where('id', 1)
->aggregation('GROUP_CONCAT(tag.name) AS tags')
->fetch()
->tags;
Αν χρειαζόμαστε να συγκεντρώσουμε αποτελέσματα που ήδη προέκυψαν
από κάποια συνάρτηση συγκέντρωσης και ομαδοποίηση (π.χ. SUM(value)
σε
ομαδοποιημένες γραμμές), ως δεύτερο όρισμα δίνουμε τη συνάρτηση
συγκέντρωσης που πρέπει να εφαρμοστεί σε αυτά τα ενδιάμεσα
αποτελέσματα:
// Υπολογίζει τη συνολική τιμή των προϊόντων στο απόθεμα για μεμονωμένες κατηγορίες και στη συνέχεια αθροίζει αυτές τις τιμές μαζί.
$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total')
->group('category_id')
->aggregation('SUM(category_total)', 'SUM');
Σε αυτό το παράδειγμα, πρώτα υπολογίζουμε τη συνολική τιμή των
προϊόντων σε κάθε κατηγορία (SUM(price * stock) AS category_total
) και
ομαδοποιούμε τα αποτελέσματα με βάση το category_id
. Στη συνέχεια,
χρησιμοποιούμε το aggregation('SUM(category_total)', 'SUM')
για να αθροίσουμε αυτά
τα ενδιάμεσα αθροίσματα category_total
. Το δεύτερο όρισμα 'SUM'
δηλώνει ότι πρέπει να εφαρμοστεί η συνάρτηση SUM στα ενδιάμεσα
αποτελέσματα.
Εισαγωγή, Ενημέρωση & Διαγραφή
Το Nette Database Explorer απλοποιεί την εισαγωγή, την ενημέρωση και τη διαγραφή
δεδομένων. Όλες οι αναφερόμενες μέθοδοι, σε περίπτωση σφάλματος, θα
προκαλέσουν την εξαίρεση Nette\Database\DriverException
.
Selection::insert (iterable $data)
Εισάγει νέες εγγραφές στον πίνακα.
Εισαγωγή μιας εγγραφής:
Τη νέα εγγραφή την περνάμε ως συσχετιστικό πίνακα ή iterable αντικείμενο (για παράδειγμα ArrayHash που χρησιμοποιείται στις φόρμες), όπου τα κλειδιά αντιστοιχούν στα ονόματα των στηλών στον πίνακα.
Αν ο πίνακας έχει ορισμένο πρωτεύον κλειδί, η μέθοδος επιστρέφει ένα
αντικείμενο ActiveRow
, το οποίο επαναφορτώνεται από τη βάση
δεδομένων, ώστε να ληφθούν υπόψη τυχόν αλλαγές που έγιναν σε επίπεδο
βάσης δεδομένων (triggers, προεπιλεγμένες τιμές στηλών, υπολογισμοί
auto-increment στηλών). Έτσι εξασφαλίζεται η συνέπεια των δεδομένων και το
αντικείμενο περιέχει πάντα τα τρέχοντα δεδομένα από τη βάση δεδομένων.
Αν δεν έχει μοναδικό πρωτεύον κλειδί, επιστρέφει τα παραδοθέντα
δεδομένα με τη μορφή πίνακα.
$row = $explorer->table('users')->insert([
'name' => 'John Doe',
'email' => 'john.doe@example.com',
]);
// το $row είναι παρουσία του ActiveRow και περιέχει τα πλήρη δεδομένα της εισαχθείσας γραμμής,
// συμπεριλαμβανομένου του αυτόματα παραγόμενου ID και τυχόν αλλαγών που έγιναν από triggers
echo $row->id; // Εμφανίζει το ID του νέου εισαχθέντος χρήστη
echo $row->created_at; // Εμφανίζει τον χρόνο δημιουργίας, αν έχει οριστεί από trigger
Εισαγωγή πολλαπλών εγγραφών ταυτόχρονα:
Η μέθοδος insert()
επιτρέπει την εισαγωγή πολλαπλών εγγραφών με
ένα μόνο ερώτημα SQL. Σε αυτή την περίπτωση, επιστρέφει τον αριθμό των
εισαχθέντων γραμμών.
$insertedRows = $explorer->table('users')->insert([
[
'name' => 'John',
'year' => 1994,
],
[
'name' => 'Jack',
'year' => 1995,
],
]);
// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995)
// το $insertedRows θα είναι 2
Ως παράμετρο μπορεί επίσης να περάσει ένα αντικείμενο Selection
με
επιλογή δεδομένων.
$newUsers = $explorer->table('potential_users')
->where('approved', 1)
->select('name, email');
$insertedRows = $explorer->table('users')->insert($newUsers);
Εισαγωγή ειδικών τιμών:
Ως τιμές μπορούμε να περάσουμε και αρχεία, αντικείμενα DateTime ή SQL literals:
$explorer->table('users')->insert([
'name' => 'John',
'created_at' => new DateTime, // μετατρέπει σε μορφή βάσης δεδομένων
'avatar' => fopen('image.jpg', 'rb'), // εισάγει το δυαδικό περιεχόμενο του αρχείου
'uuid' => $explorer::literal('UUID()'), // καλεί τη συνάρτηση UUID() της βάσης δεδομένων
]);
Selection::update (iterable $data): int
Ενημερώνει γραμμές στον πίνακα σύμφωνα με το καθορισμένο φίλτρο. Επιστρέφει τον αριθμό των γραμμών που πραγματικά άλλαξαν.
Τις στήλες που αλλάζουν τις περνάμε ως συσχετιστικό πίνακα ή iterable αντικείμενο (για παράδειγμα ArrayHash που χρησιμοποιείται στις φόρμες), όπου τα κλειδιά αντιστοιχούν στα ονόματα των στηλών στον πίνακα:
$affected = $explorer->table('users')
->where('id', 10)
->update([
'name' => 'John Smith',
'year' => 1994,
]);
// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10
Για την αλλαγή αριθμητικών τιμών μπορούμε να χρησιμοποιήσουμε τους
τελεστές +=
και -=
:
$explorer->table('users')
->where('id', 10)
->update([
'points+=' => 1, // αυξάνει την τιμή της στήλης 'points' κατά 1
'coins-=' => 1, // μειώνει την τιμή της στήλης 'coins' κατά 1
]);
// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10
Selection::delete(): int
Διαγράφει γραμμές από τον πίνακα σύμφωνα με το καθορισμένο φίλτρο. Επιστρέφει τον αριθμό των διαγραμμένων γραμμών.
$count = $explorer->table('users')
->where('id', 10)
->delete();
// DELETE FROM `users` WHERE `id` = 10
Κατά την κλήση update()
και delete()
, μην ξεχάσετε να
καθορίσετε με το where()
τις γραμμές που πρέπει να
τροποποιηθούν/διαγραφούν. Αν δεν χρησιμοποιήσετε το where()
, η
λειτουργία θα εκτελεστεί σε ολόκληρο τον πίνακα!
ActiveRow::update (iterable $data): bool
Ενημερώνει τα δεδομένα στη γραμμή της βάσης δεδομένων που
αντιπροσωπεύεται από το αντικείμενο ActiveRow
. Ως παράμετρο δέχεται
ένα iterable με τα δεδομένα που πρέπει να ενημερωθούν (τα κλειδιά είναι τα
ονόματα των στηλών). Για την αλλαγή αριθμητικών τιμών μπορούμε να
χρησιμοποιήσουμε τους τελεστές +=
και -=
:
Μετά την εκτέλεση της ενημέρωσης, το ActiveRow
επαναφορτώνεται
αυτόματα από τη βάση δεδομένων, ώστε να ληφθούν υπόψη τυχόν αλλαγές που
έγιναν σε επίπεδο βάσης δεδομένων (π.χ. triggers). Η μέθοδος επιστρέφει
true
μόνο αν έγινε πραγματική αλλαγή δεδομένων.
$article = $explorer->table('article')->get(1);
$article->update([
'views += 1', // αυξάνουμε τον αριθμό προβολών
]);
echo $article->views; // Εμφανίζει τον τρέχοντα αριθμό προβολών
Αυτή η μέθοδος ενημερώνει μόνο μία συγκεκριμένη γραμμή στη βάση δεδομένων. Για μαζική ενημέρωση πολλαπλών γραμμών χρησιμοποιήστε τη μέθοδο Selection::update().
ActiveRow::delete()
Διαγράφει τη γραμμή από τη βάση δεδομένων, η οποία αντιπροσωπεύεται
από το αντικείμενο ActiveRow
.
$book = $explorer->table('book')->get(1);
$book->delete(); // Διαγράφει το βιβλίο με ID 1
Αυτή η μέθοδος διαγράφει μόνο μία συγκεκριμένη γραμμή στη βάση δεδομένων. Για μαζική διαγραφή πολλαπλών γραμμών χρησιμοποιήστε τη μέθοδο Selection::delete().
Σχέσεις μεταξύ Πινάκων
Σε σχεσιακές βάσεις δεδομένων, τα δεδομένα χωρίζονται σε πολλούς πίνακες και συνδέονται μεταξύ τους με ξένα κλειδιά. Το Nette Database Explorer φέρνει έναν επαναστατικό τρόπο εργασίας με αυτές τις σχέσεις – χωρίς να γράφετε ερωτήματα JOIN και χωρίς την ανάγκη να διαμορφώνετε ή να παράγετε οτιδήποτε.
Για την απεικόνιση της εργασίας με τις σχέσεις θα χρησιμοποιήσουμε ένα παράδειγμα βάσης δεδομένων βιβλίων (μπορείτε να το βρείτε στο GitHub). Στη βάση δεδομένων έχουμε τους πίνακες:
author
– συγγραφείς και μεταφραστές (στήλεςid
,name
,web
,born
)book
– βιβλία (στήλεςid
,author_id
,translator_id
,title
,sequel_id
)tag
– ετικέτες (στήλεςid
,name
)book_tag
– πίνακας σύνδεσης μεταξύ βιβλίων και ετικετών (στήλεςbook_id
,tag_id
)

Στο παράδειγμά μας της βάσης δεδομένων βιβλίων βρίσκουμε διάφορους τύπους σχέσεων (αν και το μοντέλο είναι απλοποιημένο σε σχέση με την πραγματικότητα):
- One-to-many (1:N) – κάθε βιβλίο έχει έναν συγγραφέα, ο συγγραφέας μπορεί να γράψει πολλά βιβλία.
- Zero-to-many (0:N) – το βιβλίο μπορεί να έχει μεταφραστή, ο μεταφραστής μπορεί να μεταφράσει πολλά βιβλία.
- Zero-to-one (0:1) – το βιβλίο μπορεί να έχει επόμενο τόμο.
- Many-to-many (M:N) – το βιβλίο μπορεί να έχει πολλές ετικέτες και η ετικέτα μπορεί να αντιστοιχιστεί σε πολλά βιβλία.
Σε αυτές τις σχέσεις υπάρχει πάντα ένας γονικός (parent) και ένας
παιδικός (child) πίνακας. Για παράδειγμα, στη σχέση μεταξύ συγγραφέα και
βιβλίου, ο πίνακας author
είναι γονικός και ο book
παιδικός –
μπορούμε να το φανταστούμε έτσι ώστε το βιβλίο πάντα “ανήκει” σε
κάποιον συγγραφέα. Αυτό εκδηλώνεται και στη δομή της βάσης δεδομένων: ο
παιδικός πίνακας book
περιέχει το ξένο κλειδί author_id
, το
οποίο αναφέρεται στον γονικό πίνακα author
.
Αν χρειαζόμαστε να εμφανίσουμε τα βιβλία συμπεριλαμβανομένων των ονομάτων των συγγραφέων τους, έχουμε δύο δυνατότητες. Είτε θα λάβουμε τα δεδομένα με ένα μόνο ερώτημα SQL χρησιμοποιώντας JOIN:
SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id
Είτε θα φορτώσουμε τα δεδομένα σε δύο βήματα – πρώτα τα βιβλία και μετά τους συγγραφείς τους – και στη συνέχεια θα τα συνθέσουμε στην PHP:
SELECT * FROM book;
SELECT * FROM author WHERE id IN (1, 2, 3); -- ids των συγγραφέων των ληφθέντων βιβλίων
Η δεύτερη προσέγγιση είναι στην πραγματικότητα πιο αποτελεσματική, αν και αυτό μπορεί να προκαλεί έκπληξη. Τα δεδομένα φορτώνονται μόνο μία φορά και μπορούν να αξιοποιηθούν καλύτερα στην cache. Ακριβώς με αυτόν τον τρόπο λειτουργεί το Nette Database Explorer – λύνει τα πάντα κάτω από την επιφάνεια και σας προσφέρει ένα κομψό API:
$books = $explorer->table('book');
foreach ($books as $book) {
echo 'τίτλος: ' . $book->title;
echo 'γράφτηκε από: ' . $book->author->name; // το $book->author είναι η εγγραφή από τον πίνακα 'author'
echo 'μεταφράστηκε από: ' . $book->translator?->name;
}
Πρόσβαση στον Γονικό Πίνακα
Η πρόσβαση στον γονικό πίνακα είναι άμεση. Πρόκειται για σχέσεις όπως
το βιβλίο έχει συγγραφέα ή το βιβλίο μπορεί να έχει
μεταφραστή. Την σχετιζόμενη εγγραφή την λαμβάνουμε μέσω της
ιδιότητας του αντικειμένου ActiveRow – το όνομά της αντιστοιχεί στο όνομα
της στήλης με το ξένο κλειδί, αφαιρώντας το _id
:
$book = $explorer->table('book')->get(1);
echo $book->author->name; // βρίσκει τον συγγραφέα με βάση τη στήλη author_id
echo $book->translator?->name; // βρίσκει τον μεταφραστή με βάση τη στήλη translator_id
Όταν αποκτούμε πρόσβαση στην ιδιότητα $book->author
, ο Explorer στον
πίνακα book
αναζητά μια στήλη της οποίας το όνομα περιέχει το string
author
(δηλαδή author_id
). Με βάση την τιμή σε αυτή τη στήλη,
φορτώνει την αντίστοιχη εγγραφή από τον πίνακα author
και την
επιστρέφει ως ActiveRow
. Παρόμοια λειτουργεί και το
$book->translator
, το οποίο χρησιμοποιεί τη στήλη translator_id
.
Επειδή η στήλη translator_id
μπορεί να περιέχει null
,
χρησιμοποιούμε στον κώδικα τον τελεστή nullsafe ?->
.
Μια εναλλακτική οδό προσφέρει η μέθοδος ref()
, η οποία δέχεται
δύο ορίσματα, το όνομα του πίνακα προορισμού και το όνομα της στήλης
σύνδεσης, και επιστρέφει μια παρουσία ActiveRow
ή null
:
echo $book->ref('author', 'author_id')->name; // σχέση με τον συγγραφέα
echo $book->ref('author', 'translator_id')->name; // σχέση με τον μεταφραστή
Η μέθοδος ref()
είναι χρήσιμη αν δεν μπορεί να χρησιμοποιηθεί η
πρόσβαση μέσω ιδιότητας, επειδή ο πίνακας περιέχει στήλη με το ίδιο
όνομα (δηλ. author
). Στις υπόλοιπες περιπτώσεις, συνιστάται η χρήση
της πρόσβασης μέσω ιδιότητας, η οποία είναι πιο ευανάγνωστη.
Ο Explorer βελτιστοποιεί αυτόματα τα ερωτήματα της βάσης δεδομένων. Όταν διατρέχουμε τα βιβλία σε έναν βρόχο και αποκτούμε πρόσβαση στις σχετιζόμενες εγγραφές τους (συγγραφείς, μεταφραστές), ο Explorer δεν παράγει ένα ερώτημα για κάθε βιβλίο ξεχωριστά. Αντ' αυτού, εκτελεί μόνο ένα SELECT για κάθε τύπο σχέσης, μειώνοντας έτσι σημαντικά το φορτίο της βάσης δεδομένων. Για παράδειγμα:
$books = $explorer->table('book');
foreach ($books as $book) {
echo $book->title . ': ';
echo $book->author->name;
echo $book->translator?->name;
}
Αυτός ο κώδικας θα καλέσει μόνο αυτά τα τρία αστραπιαία ερωτήματα στη βάση δεδομένων:
SELECT * FROM `book`;
SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- id από τη στήλη author_id των επιλεγμένων βιβλίων
SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- id από τη στήλη translator_id των επιλεγμένων βιβλίων
Η λογική εύρεσης της στήλης σύνδεσης καθορίζεται από την υλοποίηση των Conventions. Συνιστούμε τη χρήση των DiscoveredConventions, οι οποίες αναλύουν τα ξένα κλειδιά και επιτρέπουν την εύκολη εργασία με τις υπάρχουσες σχέσεις μεταξύ των πινάκων.
Πρόσβαση στον Παιδικό Πίνακα
Η πρόσβαση στον παιδικό πίνακα λειτουργεί με την αντίστροφη
κατεύθυνση. Τώρα ρωτάμε ποια βιβλία έγραψε αυτός ο συγγραφέας ή
μετέφρασε αυτός ο μεταφραστής. Για αυτόν τον τύπο ερωτήματος
χρησιμοποιούμε τη μέθοδο related()
, η οποία επιστρέφει ένα
Selection
με τις σχετιζόμενες εγγραφές. Ας δούμε ένα παράδειγμα:
$author = $explorer->table('author')->get(1);
// Εμφανίζει όλα τα βιβλία του συγγραφέα
foreach ($author->related('book.author_id') as $book) {
echo "Έγραψε: $book->title";
}
// Εμφανίζει όλα τα βιβλία που μετέφρασε ο συγγραφέας
foreach ($author->related('book.translator_id') as $book) {
echo "Μετέφρασε: $book->title";
}
Η μέθοδος related()
δέχεται την περιγραφή της σύνδεσης ως ένα
όρισμα με σημειογραφία τελείας ή ως δύο ξεχωριστά ορίσματα:
$author->related('book.translator_id'); // ένα όρισμα
$author->related('book', 'translator_id'); // δύο ορίσματα
Ο Explorer μπορεί να ανιχνεύσει αυτόματα τη σωστή στήλη σύνδεσης με βάση
το όνομα του γονικού πίνακα. Σε αυτή την περίπτωση, η σύνδεση γίνεται
μέσω της στήλης book.author_id
, επειδή το όνομα του πίνακα πηγής είναι
author
:
$author->related('book'); // χρησιμοποιεί το book.author_id
Αν υπήρχαν περισσότερες πιθανές συνδέσεις, ο Explorer θα προκαλούσε την εξαίρεση AmbiguousReferenceKeyException.
Τη μέθοδο related()
μπορούμε φυσικά να τη χρησιμοποιήσουμε και
κατά τη διέλευση πολλαπλών εγγραφών σε έναν βρόχο και ο Explorer και σε
αυτή την περίπτωση βελτιστοποιεί αυτόματα τα ερωτήματα:
$authors = $explorer->table('author');
foreach ($authors as $author) {
echo $author->name . ' έγραψε:';
foreach ($author->related('book') as $book) {
echo $book->title;
}
}
Αυτός ο κώδικας θα παράγει μόνο δύο αστραπιαία ερωτήματα SQL:
SELECT * FROM `author`;
SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- id των επιλεγμένων συγγραφέων
Σχέση Many-to-Many
Για τη σχέση many-to-many (M:N) είναι απαραίτητη η ύπαρξη ενός πίνακα
σύνδεσης (στην περίπτωσή μας book_tag
), ο οποίος περιέχει δύο στήλες
με ξένα κλειδιά (book_id
, tag_id
). Κάθε μία από αυτές τις στήλες
αναφέρεται στο πρωτεύον κλειδί ενός από τους συνδεόμενους πίνακες. Για
να λάβουμε τα σχετιζόμενα δεδομένα, πρώτα λαμβάνουμε τις εγγραφές από
τον πίνακα σύνδεσης χρησιμοποιώντας το related('book_tag')
και στη
συνέχεια συνεχίζουμε στα δεδομένα προορισμού:
$book = $explorer->table('book')->get(1);
// εμφανίζει τα ονόματα των ετικετών που έχουν αντιστοιχιστεί στο βιβλίο
foreach ($book->related('book_tag') as $bookTag) {
echo $bookTag->tag->name; // εμφανίζει το όνομα της ετικέτας μέσω του πίνακα σύνδεσης
}
$tag = $explorer->table('tag')->get(1);
// ή αντίστροφα: εμφανίζει τα ονόματα των βιβλίων που έχουν επισημανθεί με αυτή την ετικέτα
foreach ($tag->related('book_tag') as $bookTag) {
echo $bookTag->book->title; // εμφανίζει το όνομα του βιβλίου
}
Ο Explorer πάλι βελτιστοποιεί τα ερωτήματα SQL σε αποτελεσματική μορφή:
SELECT * FROM `book`;
SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- id των επιλεγμένων βιβλίων
SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- id των ετικετών που βρέθηκαν στο book_tag
Ερωτήματα μέσω Σχετικών Πινάκων
Στις μεθόδους where()
, select()
, order()
και group()
μπορούμε να χρησιμοποιούμε ειδικές σημειογραφίες για την πρόσβαση σε
στήλες από άλλους πίνακες. Ο Explorer δημιουργεί αυτόματα τα
απαραίτητα JOINs.
Σημειογραφία τελείας (parent_table.column
) χρησιμοποιείται για τη
σχέση 1:N από την οπτική γωνία του παιδικού πίνακα:
$books = $explorer->table('book');
// Βρίσκει βιβλία των οποίων ο συγγραφέας έχει όνομα που αρχίζει από 'Jon'
$books->where('author.name LIKE ?', 'Jon%');
// Ταξινομεί τα βιβλία με βάση το όνομα του συγγραφέα φθίνουσα
$books->order('author.name DESC');
// Εμφανίζει τον τίτλο του βιβλίου και το όνομα του συγγραφέα
$books->select('book.title, author.name');
Σημειογραφία άνω και κάτω τελείας (:child_table.column
)
χρησιμοποιείται για τη σχέση 1:N από την οπτική γωνία του γονικού
πίνακα:
$authors = $explorer->table('author');
// Βρίσκει συγγραφείς που έγραψαν βιβλίο με 'PHP' στον τίτλο
$authors->where(':book.title LIKE ?', '%PHP%');
// Μετρά τον αριθμό των βιβλίων για κάθε συγγραφέα
$authors->select('*, COUNT(:book.id) AS book_count')
->group('author.id');
Στο παραπάνω παράδειγμα με τη σημειογραφία άνω και κάτω τελείας
(:book.title
) δεν καθορίζεται η στήλη με το ξένο κλειδί. Ο Explorer
ανιχνεύει αυτόματα τη σωστή στήλη με βάση το όνομα του γονικού πίνακα.
Σε αυτή την περίπτωση, η σύνδεση γίνεται μέσω της στήλης
book.author_id
, επειδή το όνομα του πίνακα πηγής είναι author
. Αν
υπήρχαν περισσότερες πιθανές συνδέσεις, ο Explorer θα προκαλούσε την
εξαίρεση AmbiguousReferenceKeyException.
Η στήλη σύνδεσης μπορεί να δηλωθεί ρητά σε παρένθεση:
// Βρίσκει συγγραφείς που μετέφρασαν βιβλίο με 'PHP' στον τίτλο
$authors->where(':book(translator_id).title LIKE ?', '%PHP%');
Οι σημειογραφίες μπορούν να αλυσιδωθούν για πρόσβαση μέσω πολλαπλών πινάκων:
// Βρίσκει συγγραφείς βιβλίων που έχουν επισημανθεί με την ετικέτα 'PHP'
$authors->where(':book:book_tag.tag.name', 'PHP')
->group('author.id');
Επέκταση Συνθηκών για JOIN
Η μέθοδος joinWhere()
επεκτείνει τις συνθήκες που αναφέρονται κατά
τη σύνδεση πινάκων στο SQL μετά τη λέξη-κλειδί ON
.
Ας υποθέσουμε ότι θέλουμε να βρούμε βιβλία που μεταφράστηκαν από έναν συγκεκριμένο μεταφραστή:
// Βρίσκει βιβλία που μεταφράστηκαν από τον μεταφραστή με όνομα 'David'
$books = $explorer->table('book')
->joinWhere('translator', 'translator.name', 'David');
// LEFT JOIN author translator ON book.translator_id = translator.id AND (translator.name = 'David')
Στη συνθήκη joinWhere()
μπορούμε να χρησιμοποιούμε τις ίδιες
κατασκευές όπως στη μέθοδο where()
– τελεστές, placeholders ερωτηματικά
(?), πίνακες τιμών ή εκφράσεις SQL.
Για πιο πολύπλοκα ερωτήματα με πολλαπλά JOINs, μπορούμε να ορίσουμε ψευδώνυμα (aliases) πινάκων:
$tags = $explorer->table('tag')
->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950)
->alias(':book_tag.book.author', 'book_author');
// LEFT JOIN `book_tag` ON `tag`.`id` = `book_tag`.`tag_id`
// LEFT JOIN `book` ON `book_tag`.`book_id` = `book`.`id`
// LEFT JOIN `author` `book_author` ON `book`.`author_id` = `book_author`.`id`
// AND (`book_author`.`born` < 1950)
Παρατηρήστε ότι ενώ η μέθοδος where()
προσθέτει συνθήκες στην
πρόταση WHERE
, η μέθοδος joinWhere()
επεκτείνει τις συνθήκες
στην πρόταση ON
κατά τη σύνδεση των πινάκων.