Raziskovalec zbirke podatkov
Nette Database Explorer bistveno poenostavi pridobivanje podatkov iz podatkovne zbirke brez pisanja poizvedb SQL.
- uporablja učinkovite poizvedbe
- podatki se ne prenašajo po nepotrebnem
- ima elegantno sintakso
Če želite uporabiti Raziskovalca podatkovne zbirke, začnite s tabelo – na objektu Nette\Database\Explorer pokličite
table()
. Najlažji način za pridobitev instance objekta konteksta je opisan tukaj, za primer, ko Nette Database
Explorer uporabljate kot samostojno orodje, pa ga lahko ustvarite ročno.
$books = $explorer->table('book'); // ime tabele db je 'book'
Klic vrne primerek objekta Izbor, nad katerim lahko iterirate, da pridobite vse knjige. Vsak element (vrstica) je predstavljen z instanco ActiveRow s podatki, ki so preslikani na njegove lastnosti:
foreach ($books as $book) {
echo $book->title;
echo $book->author_id;
}
Za pridobitev samo ene določene vrstice se uporabi metoda get()
, ki neposredno vrne primerek ActiveRow.
$book = $explorer->table('book')->get(2); // vrne knjigo z id 2
echo $book->title;
echo $book->author_id;
Oglejmo si pogost primer uporabe. Pridobiti morate knjige in njihove avtorje. To je običajno razmerje 1:N. Pogosto uporabljena rešitev je pridobivanje podatkov z eno poizvedbo SQL z združevanjem tabel. Druga možnost je, da podatke pridobite ločeno, zaženete eno poizvedbo za pridobitev knjig in nato z drugo poizvedbo (npr. v ciklu foreach) pridobite avtorja za vsako knjigo. To bi lahko zlahka optimizirali tako, da bi izvedli le dve poizvedbi, eno za knjige in drugo za potrebne avtorje – in točno tako to počne Nette Database Explorer.
V spodnjih primerih bomo delali s shemo podatkovne zbirke na sliki. Obstajajo povezave OneHasMany (1:N) (avtor knjige
author_id
in morebitni prevajalec translator_id
, ki je lahko null
) in ManyHasMany (M:N) med
knjigo in njenimi oznakami.
Primer, vključno s shemo, je na voljo na GitHubu.
Struktura zbirke podatkov, uporabljena v primerih
V naslednji kodi je za vsako knjigo navedeno ime avtorja in vse njegove oznake. O tem, kako to deluje interno, bomo razpravljali v naslednjem trenutku.
$books = $explorer->table('book');
foreach ($books as $book) {
echo 'title: ' . $book->title;
echo 'written by: ' . $book->author->name; // $book->author je vrstica iz tabele 'avtor'
echo 'tags: ';
foreach ($book->related('book_tag') as $bookTag) {
echo $bookTag->tag->name . ', '; // $bookTag->tag je vrstica iz tabele 'tag'
}
}
Zadovoljni boste, kako učinkovito deluje plast podatkovne zbirke. Zgornji primer izvaja stalno število zahtevkov, ki so videti takole:
SELECT * FROM `book`
SELECT * FROM `author` WHERE (`author`.`id` IN (11, 12))
SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3))
SELECT * FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23))
Če uporabljate predpomnilnik (privzeto vklopljen), ne boste po nepotrebnem poizvedovali po nobenem stolpcu. Po prvi poizvedbi bo predpomnilnik shranil uporabljena imena stolpcev in Nette Database Explorer bo izvajal poizvedbe samo s potrebnimi stolpci:
SELECT `id`, `title`, `author_id` FROM `book`
SELECT `id`, `name` FROM `author` WHERE (`author`.`id` IN (11, 12))
SELECT `book_id`, `tag_id` FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 4, 2, 3))
SELECT `id`, `name` FROM `tag` WHERE (`tag`.`id` IN (21, 22, 23))
Izbori
Oglejte si možnosti za filtriranje in omejevanje vrstic Nette\Database\Table\Selection:
$table->where($where[, $param[, ...]]) |
Nastavite WHERE z uporabo AND kot lepilo, če sta podana dva ali več pogojev |
$table->whereOr($where) |
Nastavitev WHERE z uporabo OR kot veziva, če sta podana dva ali več pogojev |
$table->order($columns) |
Nastavitev ORDER BY, lahko je izraz ('column DESC, id DESC') |
$table->select($columns) |
Nastavitev pridobljenih stolpcev, lahko izraz ('col, MD5(col) AS hash') |
$table->limit($limit[, $offset]) |
Nastavite LIMIT in OFFSET |
$table->page($page, $itemsPerPage[, &$lastPage]) |
Omogoča paginiranje |
$table->group($columns) |
Nastavitev GROUP BY |
$table->having($having) |
Nastavitev HAVING |
Uporabimo lahko tako imenovani tekoči vmesnik, na primer
$table->where(...)->order(...)->limit(...)
. Več pogojev where
ali whereOr
povežemo z operatorjem AND
.
kjer()
Nette Database Explorer lahko samodejno doda potrebne operatorje za posredovane vrednosti:
$table->where('field', $value) |
polje = $vrednost |
$table->where('field', null) |
polje JE NULL |
$table->where('field > ?', $val) |
polje > $val |
$table->where('field', [1, 2]) |
field IN (1, 2) |
$table->where('id = ? OR name = ?', 1, $name) |
id = 1 ALI ime = ‘Jon Snow’ |
$table->where('field', $explorer->table($tableName)) |
field IN (SELECT $primary FROM $tableName) |
$table->where('field', $explorer->table($tableName)->select('col')) |
polje IN (SELECT col FROM $tableName) |
Namestno oznako lahko zagotovite tudi brez operatorja stolpca. Ti klici so enaki.
$table->where('id = ? OR id = ?', 1, 2);
$table->where('id ? OR id ?', 1, 2);
Ta funkcija omogoča ustvarjanje pravilnega operatorja na podlagi vrednosti:
$table->where('id ?', 2); // id = 2
$table->where('id ?', null); // id IS NULL
$table->where('id', $ids); // id IN (...)
Izbira pravilno obravnava tudi negativne pogoje, deluje tudi pri praznih poljih:
$table->where('id', []); // id IS NULL AND FALSE
$table->where('id NOT', []); // id IS NULL OR TRUE
$table->where('NOT (id ?)', $ids); // NOT (id IS NULL AND FALSE)
// to vrže izjemo, ker ta sintaksa ni podprta
$table->where('NOT id ?', $ids);
whereOr()
Primer uporabe brez parametrov:
// WHERE (user_id IS NULL) OR (SUM(`field1`) > SUM(`field2`))
$table->whereOr([
'user_id IS NULL',
'SUM(field1) > SUM(field2)',
]);
Uporabljamo parametre. Če ne določite operaterja, bo Nette Database Explorer samodejno dodal ustreznega:
// WHERE (`field1` IS NULL) OR (`field2` IN (3, 5)) OR (`amount` > 11)
$table->whereOr([
'field1' => null,
'field2' => [3, 5],
'amount >' => 11,
]);
Ključ lahko vsebuje izraz, ki vsebuje nadomestne vprašalnike, nato pa v vrednosti posredujemo parametre:
// WHERE (`id` > 12) OR (ROUND(`id`, 5) = 3)
$table->whereOr([
'id > ?' => 12,
'ROUND(id, ?) = ?' => [5, 3],
]);
Naročilo()
Primeri uporabe:
$table->order('field1'); // ORDER BY `field1`
$table->order('field1 DESC, field2'); // ORDER BY `field1` DESC, `field2`
$table->order('field = ? DESC', 123); // ORDER BY `field` = 123 DESC
select()
Primeri uporabe:
$table->select('field1'); // SELECT `polje1`
$table->select('col, UPPER(col) AS abc'); // SELECT `col`, UPPER(`col`) AS abc
$table->select('SUBSTR(title, ?)', 3); // SELECT SUBSTR(`title`, 3)
limit()
Primeri uporabe:
$table->limit(1); // LIMIT 1
$table->limit(1, 10); // LIMIT 1 OFFSET 10
stran()
Alternativni način za nastavitev meje in odmika:
$page = 5;
$itemsPerPage = 10;
$table->page($page, $itemsPerPage); // LIMIT 10 OFFSET 40
Pridobitev številke zadnje strani, ki je posredovana v spremenljivko $lastPage
:
$table->page($page, $itemsPerPage, $lastPage);
skupina()
Primeri uporabe:
$table->group('field1'); // GROUP BY `field1`
$table->group('field1, field2'); // GROUP BY `field1`, `field2`
having()
Primeri uporabe:
$table->having('COUNT(items) >', 100); // HAVING COUNT(`items`) > 100
Filtriranje po vrednosti druge tabele
Pogosto morate rezultate filtrirati glede na pogoj, ki vključuje drugo tabelo podatkovne zbirke. Te vrste pogojev zahtevajo združitev tabel. Vendar vam jih ni treba več pisati.
Recimo, da morate dobiti vse knjige, katerih avtor je “Jon”. Vse, kar morate napisati, je ključ združevanja relacije in
ime stolpca v združeni tabeli. Ključ združevanja izhaja iz stolpca, ki se nanaša na tabelo, ki jo želite združiti.
V našem primeru (glej shemo db) je to stolpec author_id
, zato je dovolj, da uporabimo samo njegov prvi del –
author
(končnico _id
lahko izpustimo). name
je stolpec v tabeli author
, ki
bi ga radi uporabili. Pogoj za knjižni prevajalnik (ki ga povezuje stolpec translator_id
) lahko ustvarimo prav tako
preprosto.
$books = $explorer->table('book');
$books->where('author.name LIKE ?', '%Jon%');
$books->where('translator.name', 'David Grudl');
Logiko povezovalnega ključa poganja izvajanje funkcije Conventions. Priporočamo uporabo programa DiscoveredConventions, ki analizira vaše tuje ključe in vam omogoča enostavno delo s temi povezavami.
Razmerje med knjigo in njenim avtorjem je 1:N. Možno je tudi obratno razmerje. Imenujemo ga povratno povezovanje.
Oglejte si še en primer. Želimo pridobiti vse avtorje, ki so napisali več kot 3 knjige. Za obratno združevanje uporabimo
izjavo :
(colon). Colon means that the joined relationship means hasMany (and it's quite logical too, as two dots
are more than one dot). Unfortunately, the Selection class isn't smart enough, so we have to help with the aggregation and provide
a GROUP BY
, tudi pogoj mora biti zapisan v obliki izjave HAVING
.
$authors = $explorer->table('author');
$authors->group('author.id')
->having('COUNT(:book.id) > 3');
Morda ste opazili, da se izraz za združevanje nanaša na knjigo, vendar ni jasno, ali se združujemo prek
author_id
ali translator_id
. V zgornjem primeru se Selection združuje prek stolpca
author_id
, ker je bilo najdeno ujemanje z izvorno tabelo – tabelo author
. Če takšnega ujemanja ne
bi bilo in bi bilo več možnosti, bi Nette vrgel AmbiguousReferenceKeyException.
Če želite združitev opraviti prek stolpca translator_id
, v izrazu za združevanje navedite izbirni
parameter.
$authors = $explorer->table('author');
$authors->group('author.id')
->having('COUNT(:book(translator).id) > 3');
Oglejmo si nekaj zahtevnejših izrazov za združevanje.
Radi bi našli vse avtorje, ki so napisali kaj o PHP. Vse knjige imajo oznake, zato bi morali izbrati tiste avtorje, ki so napisali katero koli knjigo z oznako PHP.
$authors = $explorer->table('author');
$authors->where(':book:book_tags.tag.name', 'PHP')
->group('author.id')
->having('COUNT(:book:book_tags.tag.id) > 0');
Zbirne poizvedbe
$table->count('*') |
Pridobi število vrstic |
$table->count("DISTINCT $column") |
Pridobi število različnih vrednosti |
$table->min($column) |
Pridobi najmanjšo vrednost |
$table->max($column) |
Pridobi največjo vrednost |
$table->sum($column) |
Pridobi vsoto vseh vrednosti |
$table->aggregation("GROUP_CONCAT($column)") |
Izvedba katere koli funkcije združevanja |
Metoda count()
brez določenih parametrov izbere vse zapise in vrne velikost polja, kar je zelo
neučinkovito. Če morate na primer izračunati število vrstic za listanje, vedno navedite prvi argument.
Pobeg in citiranje
Raziskovalec podatkovnih zbirk je pameten in za vas izloči parametre in identifikatorje narekovajev. Kljub temu je treba upoštevati ta osnovna pravila:
- ključne besede, funkcije, postopki morajo biti zapisani z velikimi črkami
- stolpci in tabele morajo biti pisani z malimi črkami
- spremenljivke se posredujejo kot parametri, ne smejo se združevati
->where('name like ?', 'John'); // Napačno! generira: `name` `like` ?
->where('name LIKE ?', 'John'); // PRAVILNO
->where('KEY = ?', $value); // NAPAČNO! KEY je ključna beseda
->where('key = ?', $value); // PRAVILNO. generira: `key` = ?
->where('name = ' . $name); // NAPAKA! vbrizgavanje sql!
->where('name = ?', $name); // PRAVILNO
->select('DATE_FORMAT(created, "%d.%m.%Y")'); // NAPAKA! spremenljivke se posredujejo kot parametri, ne združujejo se
->select('DATE_FORMAT(created, ?)', '%d.%m.%Y'); // KORISTNO
nepravilna uporaba lahko povzroči varnostne luknje
Pridobivanje podatkov
foreach ($table as $id => $row) |
Iterirajte po vseh vrsticah v rezultatu |
$row = $table->get($id) |
Pridobi posamezno vrstico z ID $id iz tabele |
$row = $table->fetch() |
Pridobi naslednjo vrstico iz rezultata |
$array = $table->fetchPairs($key, $value) |
Prevzem vseh vrednosti v asociativno polje |
$array = $table->fetchPairs($value) |
Prevzem vseh vrstic v asociativno polje |
count($table) |
Pridobi število vrstic v nizu rezultatov |
Vstavljanje, posodabljanje in brisanje
Metoda insert()
sprejme polje objektov Traversable (na primer ArrayHash, ki vrne obrazce):
$row = $explorer->table('users')->insert([
'name' => $name,
'year' => $year,
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978)
Če je v tabeli določen primarni ključ, se vrne objekt ActiveRow, ki vsebuje vstavljeno vrstico.
Večkratno vstavljanje:
$explorer->table('users')->insert([
[
'name' => 'Jim',
'year' => 1978,
], [
'name' => 'Jack',
'year' => 1987,
],
]);
// INSERT INTO users (`name`, `year`) VALUES ('Jim', 1978), ('Jack', 1987)
Datoteke ali objekti DateTime se lahko posredujejo kot parametri:
$explorer->table('users')->insert([
'name' => $name,
'created' => new DateTime, // ali $explorer::literal('NOW()')
'avatar' => fopen('image.gif', 'r'), // vstavi datoteko
]);
Posodabljanje (vrne število prizadetih vrstic):
$count = $explorer->table('users')
->where('id', 10) // je treba poklicati pred funkcijo update()
->update([
'name' => 'Ned Stark'
]);
// UPDATE `users` SET `name`='Ned Stark' WHERE (`id` = 10)
Za posodabljanje lahko uporabimo operatorje +=
a -=
:
$explorer->table('users')
->update([
'age+=' => 1, // glej +=
]);
// UPDATE users SET `age` = `age` + 1
Brisanje (vrne število izbrisanih vrstic):
$count = $explorer->table('users')
->where('id', 10)
->delete();
// DELETE FROM `users` WHERE (`id` = 10)
Delo z razmerji
Ima eno razmerje
Ima eno razmerje je pogost primer uporabe. Knjiga ima enega avtorja. Knjiga ima enega* prevajalca. Pridobivanje povezanih
vrstic se večinoma izvaja z metodo ref()
. Sprejme dva argumenta: ime ciljne tabele in izvorni povezovalni stolpec.
Oglejte si primer:
$book = $explorer->table('book')->get(1);
$book->ref('author', 'author_id');
V zgornjem primeru smo iz tabele author
pridobili povezan vnos avtorja, primarni ključ avtorja pa smo poiskali
po stolpcu book.author_id
. Metoda Ref() vrne primerek ActiveRow ali nič, če ni ustreznega vnosa. Vrnjena vrstica je
primerek ActiveRow, zato lahko z njo delamo enako kot z vnosom knjige.
$author = $book->ref('author', 'author_id');
$author->name;
$author->born;
// ali neposredno
$book->ref('author', 'author_id')->name;
$book->ref('author', 'author_id')->born;
Knjiga ima tudi en prevajalnik, zato je pridobivanje imena prevajalnika precej enostavno.
$book->ref('author', 'translator_id')->name
Vse to je v redu, vendar je nekoliko okorno, se vam ne zdi? Raziskovalec zbirke podatkov že vsebuje definicije tujih ključev, zakaj jih torej ne bi uporabljali samodejno? Naredimo to!
Če kličemo lastnost, ki ne obstaja, ActiveRow poskuša ime kličoče lastnosti razrešiti kot relacijo ‘ima eno’. Pridobitev te lastnosti je enaka klicu metode ref() s samo enim argumentom. Edini argument bomo imenovali ključ. Ključ bo razrešen na določeno relacijo tujega ključa. Predani ključ se primerja s stolpci vrstice, in če se ujema, se za pridobivanje podatkov iz povezane ciljne tabele uporabi tuji ključ, ki je opredeljen v ujemajočem se stolpcu. Oglejte si primer:
$book->author->name;
// enako kot
$book->ref('author')->name;
Primer: Primerek ActiveRow nima stolpca avtor. V vseh stolpcih knjige se išče ujemanje s ključ. Ujemanje v tem
primeru pomeni, da mora ime stolpca vsebovati ključ. V zgornjem primeru torej stolpec author_id
vsebuje niz
“avtor” in se zato ujema s ključem “avtor”. Če želite pridobiti prevajalca knjige, lahko kot ključ uporabite npr.
‘translator’, saj se bo ključ ‘translator’ ujemal s stolpcem translator_id
. Več o logiki ujemanja
ključev najdete v poglavju Združevanje izrazov.
echo $book->title . ': ';
echo $book->author->name;
if ($book->translator) {
echo ' (translated by ' . $book->translator->name . ')';
}
Če želite pridobiti več knjig, morate uporabiti enak pristop. Nette Database Explorer bo poiskal avtorje in prevajalce za vse poiskane knjige naenkrat.
$books = $explorer->table('book');
foreach ($books as $book) {
echo $book->title . ': ';
echo $book->author->name;
if ($book->translator) {
echo ' (translated by ' . $book->translator->name . ')';
}
}
Koda bo izvedla samo te tri poizvedbe:
SELECT * FROM `book`;
SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- ids of fetched books from author_id column
SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- ids of fetched books from translator_id column
Ima veliko relacij
Razmerje “ima veliko” je samo obrnjeno razmerje “ima eno”. Avtor je napisal veliko knjig. Avtor je prevedel
mnogo knjig. Kot lahko vidite, je ta vrsta relacije nekoliko težja, saj je relacija ‘poimenovana’ (‘napisal’,
‘prevedel’). Primer ActiveRow ima metodo related()
, ki vrne polje povezanih vnosov. Vnosi so prav tako primerki
ActiveRow. Oglejte si primer spodaj:
$author = $explorer->table('author')->get(11);
echo $author->name . ' has written:';
foreach ($author->related('book.author_id') as $book) {
echo $book->title;
}
echo 'and translated:';
foreach ($author->related('book.translator_id') as $book) {
echo $book->title;
}
Metoda related()
Metoda sprejme celoten opis združevanja, posredovan kot dva argumenta ali kot en argument,
združen s piko. Prvi argument je ciljna tabela, drugi pa ciljni stolpec.
$author->related('book.translator_id');
// enako kot
$author->related('book', 'translator_id');
Uporabite lahko hevristiko Nette Database Explorerja, ki temelji na tujih ključih, in navedete samo argument ključ.
Ključ bo primerjan z vsemi tujimi ključi, ki kažejo na trenutno tabelo (author
tabela). Če se ujemajo, bo Nette
Database Explorer uporabil ta tuji ključ, v nasprotnem primeru bo vrgel Nette\InvalidArgumentException ali AmbiguousReferenceKeyException.
Več o logiki ujemanja ključev najdete v poglavju Združevanje izrazov.
Seveda lahko pokličete sorodne metode za vse pridobljene avtorje, Nette Database Explorer pa bo ponovno pridobil ustrezne knjige naenkrat.
$authors = $explorer->table('author');
foreach ($authors as $author) {
echo $author->name . ' has written:';
foreach ($author->related('book') as $book) {
$book->title;
}
}
Zgornji primer bo izvedel samo dve poizvedbi:
SELECT * FROM `author`;
SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- ids of fetched authors
Ročno ustvarjanje raziskovalca
Povezavo s podatkovno zbirko lahko ustvarite s konfiguracijo aplikacije. V takih primerih se ustvari storitev
Nette\Database\Explorer
, ki jo je mogoče posredovati kot odvisnost z uporabo vsebnika DI.
Če pa se Nette Database Explorer uporablja kot samostojno orodje, je treba primerek objekta
Nette\Database\Explorer
ustvariti ročno.
// $storage implements Nette\Caching\Storage:
$storage = new Nette\Caching\Storages\FileStorage($tempDir);
$connection = new Nette\Database\Connection($dsn, $user, $password);
$structure = new Nette\Database\Structure($connection, $storage);
$conventions = new Nette\Database\Conventions\DiscoveredConventions($structure);
$explorer = new Nette\Database\Explorer($connection, $structure, $conventions, $storage);