Raziskovalec zbirke podatkov
Z zbirko podatkov Nette lahko delate na dva načina: pustite, da se poizvedbe SQL generirajo samodejno (pristop raziskovalca), ali pa jih napišete sami(neposredni dostop). Raziskovalec bistveno poenostavi dostop do podatkov. Obravnava razmerja med tabelami, tako da se lahko osredotočite na logiko svoje aplikacije.
- Delo s podatki je naravno in enostavno za razumevanje
- Ustvarja optimizirane poizvedbe SQL, ki poberejo le potrebne podatke
- Zagotavlja enostaven dostop do povezanih podatkov, ne da bi bilo treba pisati poizvedbe JOIN
- Deluje takoj, brez kakršne koli konfiguracije ali ustvarjanja entitet
Delo z Raziskovalcem se začne s klicem metode table() na objektu Nette\Database\Explorer (za informacije o ustvarjanju povezav in konfiguraciji glejte posebno stran):
$books = $explorer->table('book'); // 'book' je ime tabele
Metoda vrne objekt Selection, ki
predstavlja poizvedbo SQL. Na ta objekt je mogoče verižno povezati dodatne metode za filtriranje in razvrščanje rezultatov.
Poizvedba se sestavi in izvede le, ko so podatki zahtevani, na primer z iteracijo s foreach
. Vsaka vrstica je
predstavljena z objektom ActiveRow:
foreach ($books as $book) {
echo $book->title; // izpisi stolpca 'naslov'
echo $book->author_id; // izpiše stolpec 'author_id'.
}
Raziskovalec močno poenostavi delo z razmerji med tabelami. Naslednji primer prikazuje, kako preprosto lahko izpišemo podatke iz povezanih tabel (knjige in njihovi avtorji). Opazite, da ni treba pisati nobenih poizvedb JOIN; Nette jih ustvari namesto nas:
$books = $explorer->table('book');
foreach ($books as $book) {
echo 'Book: ' . $book->title;
echo 'Author: ' . $book->author->name; // ustvari povezavo JOIN s tabelo "author".
}
Nette Database Explorer optimizira poizvedbe za največjo učinkovitost. Zgornji primer izvede le dve poizvedbi SELECT, ne glede na to, ali obdelujemo 10 ali 10.000 knjig.
Poleg tega Raziskovalec spremlja, kateri stolpci se uporabljajo v kodi, in iz zbirke podatkov pobere le te, s čimer prihrani še dodatno zmogljivost. To obnašanje je popolnoma samodejno in prilagodljivo. Če pozneje spremenite kodo in uporabite dodatne stolpce, Raziskovalec samodejno prilagodi poizvedbe. Ničesar vam ni treba konfigurirati ali razmišljati o tem, kateri stolpci bodo potrebni – to prepustite Nette.
Filtriranje in razvrščanje
Razred Selection
ponuja metode za filtriranje in razvrščanje podatkov.
where($condition, ...$params) |
Doda pogoj WHERE. Več pogojev se združi z uporabo AND |
whereOr(array $conditions) |
Doda skupino pogojev WHERE, združenih z uporabo OR |
wherePrimary($value) |
Doda pogoj WHERE na podlagi primarnega ključa |
order($columns, ...$params) |
Določi razvrščanje z ORDER BY |
select($columns, ...$params) |
Določi, katere stolpce je treba poiskati |
limit($limit, $offset = null) |
Omeji število vrstic (LIMIT) in po želji določi OFFSET |
page($page, $itemsPerPage, &$total = null) |
Nastavi paginacijo |
group($columns, ...$params) |
Združi vrstice v skupine (GROUP BY) |
having($condition, ...$params) |
Doda pogoj HAVING za filtriranje združenih vrstic |
Metode je mogoče verižiti (tako imenovani tekoči vmesnik):
$table->where(...)->order(...)->limit(...)
.
Te metode omogočajo tudi uporabo posebnih zapisov za dostop do podatkov iz povezanih tabel.
Pobegi in identifikatorji
Metode samodejno izločijo parametre in identifikatorje citatov (imena tabel in stolpcev), kar preprečuje vbrizgavanje SQL. Za zagotovitev pravilnega delovanja je treba upoštevati nekaj pravil:
- ključne besede, imena funkcij, postopkov itd. pišite z nadrobnimi črkami.
- Imena stolpcev in tabel pišite z malimi črkami**.
- Nizove vedno prenašajte z uporabo parametrov.
where('name = ' . $name); // **DISASTER**: ranljiv za vbrizgavanje SQL
where('name LIKE "%search%"'); // **PRAVILNO**: otežuje samodejno citiranje
where('name LIKE ?', '%search%'); // **CORRECT**: vrednost je posredovana kot parameter
where('name like ?', $name); // **WRONG**: ustvari: ?
where('name LIKE ?', $name); // **CORRECT**: generira: `name` LIKE ?
where('LOWER(name) = ?', $value);// **CORRECT**: LOWER(`name`) = ?
where (string|array $condition, …$parameters): static
Filtriranje rezultatov z uporabo pogojev WHERE. Njegova prednost je v inteligentnem obravnavanju različnih vrst vrednosti in samodejnem izbiranju operatorjev SQL.
Osnovna uporaba:
$table->where('id', $value); // Kjer `id` = 123
$table->where('id > ?', $value); // Kjer `id` > 123
$table->where('id = ? OR name = ?', $id, $name); // KJE `id` = 1 ALI `imeno` = 'Jon Snow'
Zahvaljujoč samodejnemu zaznavanju ustreznih operatorjev vam ni treba obravnavati posebnih primerov – Nette jih uredi namesto vas:
$table->where('id', 1); // Kjer `id` = 1
$table->where('id', null); // Kjer je `id` NULL
$table->where('id', [1, 2, 3]); // Kjer `id` IN (1, 2, 3)
// Namestni znak ? se lahko uporablja brez operatorja:
$table->where('id ?', 1); // Kjer `id` = 1
Metoda pravilno obravnava tudi negativne pogoje in prazne pole:
$table->where('id', []); // Kjer `id` JE NULL IN FALSE -- ne najde ničesar
$table->where('id NOT', []); // KER je `id` NIČ ALI PRAVDA -- najde vse
$table->where('NOT (id ?)', []); // KER NE (`id` JE NULL IN FALSE) -- najde vse
// $table->where('NOT id ?', $ids); // OPOZORILO: Ta sintaksa ni podprta
Kot parameter lahko posredujete tudi rezultat druge poizvedbe po tabeli in tako ustvarite podpovpraševanje:
// Kjer `id` IN (SELECT `id` FROM `tableName`)
$table->where('id', $explorer->table($tableName));
// Kjer `id` IN (SELECT `col` FROM `tableName`)
$table->where('id', $explorer->table($tableName)->select('col'));
Pogoje lahko posredujete tudi kot polje, pri čemer elemente združite z uporabo metode AND:
// Kjer (`Cena_končna` < `Cena_prejšnja`) IN (Število zalog` > `Min_zaloge`)
$table->where([
'price_final < price_original',
'stock_count > min_stock',
]);
V polju se lahko uporabijo pari ključ-vrednost, Nette pa bo ponovno samodejno izbral pravilne operatorje:
// Kjer (`status` = 'active') IN (`id` V (1, 2, 3))
$table->where([
'status' => 'active',
'id' => [1, 2, 3],
]);
Prav tako lahko mešamo izraze SQL z nadomestnimi znaki in več parametri. To je uporabno za zapletene pogoje z natančno določenimi operatorji:
// Kjer (`starost` > 18 let) IN (ROUND(`score`, 2) > 75,5)
$table->where([
'age > ?' => 18,
'ROUND(score, ?) > ?' => [2, 75.5], // dva parametra sta posredovana kot polje
]);
Več klicev na where()
samodejno združi pogoje z uporabo AND.
whereOr (array $parameters): static
Podobno kot where()
, vendar združuje pogoje z uporabo OR:
// Kjer (`status` = 'aktiven') ALI (`izbrisano` = 1)
$table->whereOr([
'status' => 'active',
'deleted' => true,
]);
Uporabljajo se lahko tudi bolj zapleteni izrazi:
// Kjer (`cena` > 1000) ALI (`cena_z_davkom` > 1500)
$table->whereOr([
'price > ?' => 1000,
'price_with_tax > ?' => 1500,
]);
wherePrimary (mixed $key): static
Doda pogoj za primarni ključ tabele:
// Kjer `id` = 123
$table->wherePrimary(123);
// Kjer `id` IN (1, 2, 3)
$table->wherePrimary([1, 2, 3]);
Če ima tabela sestavljen primarni ključ (npr. foo_id
, bar_id
), ga posredujemo kot polje:
// Kjer `foo_id` = 1 IN `bar_id` = 5
$table->wherePrimary(['foo_id' => 1, 'bar_id' => 5])->fetch();
// Kjer (`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
Določa vrstni red vrnjenih vrstic. Razvrstite lahko po enem ali več stolpcih, v naraščajočem ali padajočem vrstnem redu ali z izrazom po meri:
$table->order('created'); // NAROČI PO `ustvarjenem`
$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
Določa stolpce, ki se vrnejo iz podatkovne zbirke. Privzeto Nette Database Explorer vrne samo stolpce, ki se dejansko
uporabljajo v kodi. Metodo select()
uporabite, kadar želite pridobiti določene izraze:
// SELECT *, DATE_FORMAT(`created_at`, "%d.%m.%Y") AS `formatted_date`
$table->select('*, DATE_FORMAT(created_at, ?) AS formatted_date', '%d.%m.%Y');
Vzdevki, določeni z uporabo AS
, so nato dostopni kot lastnosti predmeta ActiveRow
:
foreach ($table as $row) {
echo $row->formatted_date; // dostop do vzdevka
}
limit (?int $limit, ?int $offset = null): static
Omeji število vrnjenih vrstic (LIMIT) in po želji določi odmik:
$table->limit(10); // LIMIT 10 (vrne prvih 10 vrstic)
$table->limit(10, 20); // LIMIT 10 OFFSET 20
Za paginacijo je primerneje uporabiti metodo page()
.
page (int $page, int $itemsPerPage, &$numOfPages = null): static
Poenostavi paginiranje rezultatov. Sprejme številko strani (od 1 naprej) in število elementov na strani. Po želji lahko posredujete sklic na spremenljivko, v kateri bo shranjeno skupno število strani:
$numOfPages = null;
$table->page(page: 3, itemsPerPage: 10, $numOfPages);
echo "Total pages: $numOfPages";
group (string $columns, …$parameters): static
Poveže vrstice po določenih stolpcih (GROUP BY). Običajno se uporablja v kombinaciji z združevalnimi funkcijami:
// Šteje število izdelkov v vsaki kategoriji.
$table->select('category_id, COUNT(*) AS count')
->group('category_id');
having (string $having, …$parameters): static
Določi pogoj za filtriranje združenih vrstic (HAVING). Uporablja se lahko v kombinaciji z metodo group()
in
združevalnimi funkcijami:
// Poišče kategorije z več kot 100 izdelki
$table->select('category_id, COUNT(*) AS count')
->group('category_id')
->having('count > ?', 100);
Branje podatkov
Za branje podatkov iz podatkovne zbirke je na voljo več uporabnih metod:
foreach ($table as $key => $row) |
Iterira skozi vse vrstice, $key je vrednost primarnega ključa, $row je objekt ActiveRow |
$row = $table->get($key) |
Vrne posamezno vrstico po primarnem ključu |
$row = $table->fetch() |
Vrne trenutno vrstico in premakne kazalec na naslednjo |
$array = $table->fetchPairs() |
Iz rezultatov ustvari asociativno polje |
$array = $table->fetchAll() |
Vrne vse vrstice kot polje |
count($table) |
Vrne število vrstic v objektu Izbor |
Objekt ActiveRow je namenjen samo branju. To pomeni, da ne morete spreminjati vrednosti njegovih lastnosti. Ta omejitev zagotavlja doslednost podatkov in preprečuje nepričakovane stranske učinke. Podatki se pridobivajo iz podatkovne zbirke, zato je treba vse spremembe izrecno in nadzorovano izvesti.
foreach
– Iteriranje skozi vse vrstice
Najlažji način za izvajanje poizvedbe in pridobivanje vrstic je iteracija z zanko foreach
. Ta samodejno izvede
poizvedbo SQL.
$books = $explorer->table('book');
foreach ($books as $key => $book) {
// $key = primarni ključ, $book = ActiveRow
echo "$book->title ({$book->author->name})";
}
get ($key): ?ActiveRow
Izvrši poizvedbo SQL in vrne vrstico po njenem primarnem ključu ali null
, če ta ne obstaja.
$book = $explorer->table('book')->get(123); // vrne vrstico ActiveRow z ID 123 ali null
if ($book) {
echo $book->title;
}
fetch(): ?ActiveRow
Vrne eno vrstico in premakne notranji kazalec na naslednjo. Če ni več vrstic, vrne null
.
$books = $explorer->table('book');
while ($book = $books->fetch()) {
$this->processBook($book);
}
fetchPairs (string|int|null $key = null, string|int|null $value = null): array
Rezultate vrne kot asociativno polje. Prvi argument določa ime stolpca, ki se uporabi kot ključ polja, drugi argument pa ime stolpca, ki se uporabi kot vrednost:
$authors = $explorer->table('author')->fetchPairs('id', 'name');
// [1 => 'John Doe', 2 => 'Jane Doe', ...]
Če je podan samo prvi parameter, se kot vrednost uporabi celotna vrstica, ki je predstavljena kot objekt
ActiveRow
:
$authors = $explorer->table('author')->fetchPairs('id');
// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...]
Če je kot ključ naveden null
, se polje številčno indeksira od nič:
$authors = $explorer->table('author')->fetchPairs(null, 'name');
// [0 => 'John Doe', 1 => 'Jane Doe', ...]
fetchPairs (Closure $callback): array
Kot parameter lahko posredujete tudi povratni klic. Povratni klic se uporabi za vsako vrstico, da se vrne ena vrednost ali par ključ-vrednost.
$titles = $explorer->table('book')
->fetchPairs(fn($row) => "$row->title ({$row->author->name})");
// [1 => 'Prva knjiga (Jan Novak)', ...]
// Povratni klic lahko vrne tudi polje, ki vsebuje par ključ-vrednost:
$titles = $explorer->table('book')
->fetchPairs(fn($row) => [$row->title, $row->author->name]);
// ['First Book' => 'Jan Novak', ...]
fetchAll(): array
Vrne vse vrstice kot asociativno polje objektov ActiveRow
, kjer so ključi vrednosti primarnih ključev.
$allBooks = $explorer->table('book')->fetchAll();
// [1 => ActiveRow(id: 1, ...), 2 => ActiveRow(id: 2, ...), ...]
count(): int
Metoda count()
brez parametrov vrne število vrstic v objektu Selection
:
$table->where('category', 1);
$count = $table->count();
$count = count($table); // alternativa
Opomba: metoda count()
s parametrom izvede funkcijo združevanja COUNT v zbirki podatkov, kot je opisano
spodaj.
ActiveRow::toArray(): array
Objekt ActiveRow
pretvori v asociativno polje, kjer so ključi imena stolpcev, vrednosti pa ustrezni podatki.
$book = $explorer->table('book')->get(1);
$bookArray = $book->toArray();
// $bookArray bo ['id' => 1, 'title' => '...', 'author_id' => ..., ...]
Agregacija
Razred Selection
ponuja metode za enostavno izvajanje funkcij združevanja (COUNT, SUM, MIN, MAX, AVG itd.).
count($expr) |
Šteje število vrstic |
min($expr) |
Vrne najmanjšo vrednost v stolpcu |
max($expr) |
Vrne največjo vrednost v stolpcu |
sum($expr) |
Vrne vsoto vrednosti v stolpcu |
aggregation($function) |
Omogoča katero koli funkcijo združevanja, kot sta AVG() ali GROUP_CONCAT() |
count (string $expr): int
Izvede poizvedbo SQL s funkcijo COUNT in vrne rezultat. Ta metoda se uporablja za ugotavljanje, koliko vrstic ustreza določenemu pogoju:
$count = $table->count('*'); // SELECT COUNT(*) FROM `table`
$count = $table->count('DISTINCT column'); // SELECT COUNT(DISTINCT `stolpec`) FROM `table`
Opomba: funkcija count() brez parametra preprosto vrne število vrstic v objektu
Selection
.
min (string $expr) and max(string $expr)
Metodi min()
in max()
vrneta najmanjšo in največjo vrednost v navedenem stolpcu ali izrazu:
// SELECT MAX(`cena`) FROM `izdelki` WHERE `active` = 1
$maxPrice = $products->where('active', true)
->max('price');
sum (string $expr): int
Vrne vsoto vrednosti v določenem stolpcu ali izrazu:
// SELECT SUM(`cena` * `predmeti na zalogi`) FROM `izdelki` WHERE `active` = 1
$totalPrice = $products->where('active', true)
->sum('price * items_in_stock');
aggregation (string $function, ?string $groupFunction = null): mixed
Omogoča izvajanje katere koli funkcije združevanja.
// Izračuna povprečno ceno izdelkov v kategoriji.
$avgPrice = $products->where('category_id', 1)
->aggregation('AVG(price)');
// združuje oznake izdelkov v en sam niz
$tags = $products->where('id', 1)
->aggregation('GROUP_CONCAT(tag.name) AS tags')
->fetch()
->tags;
Če moramo združiti rezultate, ki so sami rezultat združevanja in grupiranja (npr. SUM(value)
nad grupiranimi
vrsticami), kot drugi argument navedemo funkcijo združevanja, ki se uporabi za te vmesne rezultate:
// Izračuna skupno ceno izdelkov na zalogi za vsako kategorijo, nato pa te cene sešteje.
$totalPrice = $products->select('category_id, SUM(price * stock) AS category_total')
->group('category_id')
->aggregation('SUM(category_total)', 'SUM');
V tem primeru najprej izračunamo skupno ceno izdelkov v vsaki kategoriji (SUM(price * stock) AS category_total
)
in rezultate združimo po category_id
. Nato za seštevanje teh vmesnih vsot uporabimo
aggregation('SUM(category_total)', 'SUM')
. Drugi argument 'SUM'
določa funkcijo združevanja, ki se
uporabi za vmesne rezultate.
Vstavljanje, posodabljanje in brisanje
Nette Database Explorer poenostavlja vstavljanje, posodabljanje in brisanje podatkov. Vse omenjene metode v primeru napake
zavržejo sporočilo Nette\Database\DriverException
.
Selection::insert (iterable $data): static
Vnese nove zapise v tabelo.
Vstavljanje posameznega zapisa:
Nov zapis se posreduje kot asociativno polje ali iterabilni objekt (kot je ArrayHash
, ki se uporablja v obrazcih), kjer se ključi ujemajo z imeni stolpcev v tabeli.
Če ima tabela določen primarni ključ, metoda vrne objekt ActiveRow
, ki se ponovno naloži iz podatkovne zbirke,
da odraža vse spremembe na ravni podatkovne zbirke (npr. sprožilce, privzete vrednosti stolpcev ali izračune samodejnega
povečanja). S tem je zagotovljena doslednost podatkov, objekt pa vedno vsebuje trenutne podatke iz zbirke podatkov. Če primarni
ključ ni izrecno določen, metoda vrne vhodne podatke kot polje.
$row = $explorer->table('users')->insert([
'name' => 'John Doe',
'email' => 'john.doe@example.com',
]);
// $row je primerek ActiveRow, ki vsebuje celotne podatke vstavljene vrstice,
// vključno s samodejno ustvarjenim ID in vsemi spremembami, ki so jih naredili sprožilci
echo $row->id; // izpiše ID novo vstavljenega uporabnika
echo $row->created_at; // izpiše čas ustvarjanja, če ga je določil sprožilec
Vstavljanje več zapisov naenkrat:
Metoda insert()
omogoča vstavljanje več zapisov z eno samo poizvedbo SQL. V tem primeru vrne število
vstavljenih vrstic.
$insertedRows = $explorer->table('users')->insert([
[
'name' => 'John',
'year' => 1994,
],
[
'name' => 'Jack',
'year' => 1995,
],
]);
// INSERT INTO `users` (`name`, `year`) VALUES ('John', 1994), ('Jack', 1995)
// $insertedRows bo 2
Kot parameter lahko posredujete tudi objekt Selection
z izbranimi podatki.
$newUsers = $explorer->table('potential_users')
->where('approved', 1)
->select('name, email');
$insertedRows = $explorer->table('users')->insert($newUsers);
Vstavljanje posebnih vrednosti:
Vrednosti lahko vključujejo datoteke, predmete DateTime
ali literale SQL:
$explorer->table('users')->insert([
'name' => 'John',
'created_at' => new DateTime, // pretvori v obliko podatkovne zbirke
'avatar' => fopen('image.jpg', 'rb'), // vstavi vsebino binarne datoteke
'uuid' => $explorer::literal('UUID()'), // pokliče funkcijo UUID()
]);
Selection::update (iterable $data): int
Posodobi vrstice v tabeli na podlagi določenega filtra. Vrne število dejansko spremenjenih vrstic.
Stolpci, ki jih je treba posodobiti, se posredujejo kot asociativno polje ali iterabilni objekt (kot je ArrayHash
,
ki se uporablja v obrazcih), kjer se ključi ujemajo z imeni stolpcev
v tabeli:
$affected = $explorer->table('users')
->where('id', 10)
->update([
'name' => 'John Smith',
'year' => 1994,
]);
// UPDATE `users` SET `name` = 'John Smith', `year` = 1994 WHERE `id` = 10
Za spreminjanje številskih vrednosti lahko uporabite operatorja +=
in -=
:
$explorer->table('users')
->where('id', 10)
->update([
'points+=' => 1, // poveča vrednost stolpca "točke" za 1.
'coins-=' => 1, // zmanjša vrednost stolpca "kovanci" za 1
]);
// UPDATE `users` SET `points` = `points` + 1, `coins` = `coins` - 1 WHERE `id` = 10
Selection::delete(): int
Iz tabele izbriše vrstice na podlagi določenega filtra. Vrne število izbrisanih vrstic.
$count = $explorer->table('users')
->where('id', 10)
->delete();
// DELETE FROM `users` WHERE `id` = 10
Ko kličete update()
ali delete()
, ne pozabite uporabiti where()
za
določitev vrstic, ki jih je treba posodobiti ali izbrisati. Če where()
ni uporabljen, bo operacija izvedena za
celotno tabelo!
ActiveRow::update (iterable $data): bool
Posodobi podatke v vrstici zbirke podatkov, ki jo predstavlja objekt ActiveRow
. Kot parameter sprejme iterabilne
podatke, pri čemer so ključi imena stolpcev. Za spreminjanje številskih vrednosti lahko uporabite operatorja +=
in
-=
:
Po izvedeni posodobitvi se objekt ActiveRow
samodejno ponovno naloži iz podatkovne zbirke, da odraža vse
spremembe, izvedene na ravni podatkovne zbirke (npr. sprožilci). Metoda vrne true
le, če je prišlo do dejanske
spremembe podatkov.
$article = $explorer->table('article')->get(1);
$article->update([
'views += 1', // poveča število ogledov
]);
echo $article->views; // izpiše trenutno število ogledov
Ta metoda posodobi samo eno določeno vrstico v zbirki podatkov. Za množične posodobitve več vrstic uporabite metodo Selection::update().
ActiveRow::delete()
Izbriše vrstico iz zbirke podatkov, ki jo predstavlja objekt ActiveRow
.
$book = $explorer->table('book')->get(1);
$book->delete(); // izbriše knjigo z ID 1
Ta metoda izbriše samo eno določeno vrstico v zbirki podatkov. Za množično brisanje več vrstic uporabite metodo Selection::delete().
Razmerja med tabelami
V relacijskih podatkovnih zbirkah so podatki razdeljeni v več tabel in povezani s tujimi ključi. Nette Database Explorer ponuja revolucionaren način dela s temi razmerji – brez pisanja poizvedb JOIN ali zahteve po konfiguraciji ali ustvarjanju entitet.
Za demonstracijo bomo uporabili podatkovno zbirko primer(na voljo na GitHubu). Podatkovna baza vsebuje naslednje tabele:
author
– avtorji in prevajalci (stolpciid
,name
,web
,born
)book
– knjige (stolpciid
,author_id
,translator_id
,title
,sequel_id
)tag
– oznake (stolpciid
,name
)book_tag
– preglednica povezav med knjigami in oznakami (stolpcibook_id
,tag_id
)
Struktura podatkovne zbirke
V tem primeru zbirke podatkov o knjigah najdemo več vrst povezav (poenostavljeno v primerjavi z resničnostjo):
- Vsaka knjiga ima enega avtorja; avtor lahko napiše več knjig.
- Zero-to-many (0:N) – Knjiga ima lahko prevajalca; prevajalec lahko prevede več knjig.
- Zero-to-one (0:1) – Knjiga ima lahko** nadaljevanje.
- Mnogo-več (M:N) – Knjiga ima lahko več** značk in ena značka je lahko dodeljena več knjigam.
V teh razmerjih vedno obstajata starševska tabela in družinska tabela. Na primer, v razmerju med avtorji in
knjigami je tabela author
starš, tabela book
pa otrok – to si lahko predstavljate tako, da knjiga
vedno “pripada” avtorju. To se odraža tudi v strukturi zbirke podatkov: podrejena tabela book
vsebuje tuj
ključ author_id
, ki se sklicuje na nadrejeno tabelo author
.
Če želimo prikazati knjige skupaj z imeni njihovih avtorjev, imamo dve možnosti. Podatke lahko pridobimo z eno samo poizvedbo SQL s povezavo JOIN:
SELECT book.*, author.name FROM book LEFT JOIN author ON book.author_id = author.id;
ali pa podatke pridobimo v dveh korakih – najprej knjige, nato njihove avtorje – in jih sestavimo v PHP:
SELECT * FROM book;
SELECT * FROM author WHERE id IN (1, 2, 3); -- IDs of authors retrieved from books
Drugi pristop je presenetljivo učinkovitejši. Podatki se pridobijo samo enkrat in jih je mogoče bolje uporabiti v predpomnilniku. Natanko tako deluje Nette Database Explorer – za vse poskrbi pod pokrovom in vam ponudi čist API:
$books = $explorer->table('book');
foreach ($books as $book) {
echo 'title: ' . $book->title;
echo 'written by: ' . $book->author->name; // $book->author je zapis iz tabele 'author'
echo 'translated by: ' . $book->translator?->name;
}
Dostop do matične tabele
Dostop do nadrejene tabele je preprost. To so razmerja, kot sta knjiga ima avtorja ali knjiga ima lahko
prevajalca. Do povezanega zapisa lahko dostopamo prek lastnosti predmeta ActiveRow
– ime lastnosti se ujema
z imenom stolpca tujega ključa brez končnice id
:
$book = $explorer->table('book')->get(1);
echo $book->author->name; // poišče avtorja prek stolpca 'author_id'.
echo $book->translator?->name; // poišče prevajalca prek stolpca "translator_id".
Pri dostopu do lastnosti $book->author
Raziskovalec išče stolpec v tabeli book
, ki vsebuje niz
author
(npr. author_id
). Na podlagi vrednosti v tem stolpcu pridobi ustrezen zapis iz tabele
author
in ga vrne kot objekt ActiveRow
. Podobno $book->translator
uporablja stolpec
translator_id
. Ker lahko stolpec translator_id
vsebuje null
, se uporabi operator
?->
.
Alternativni pristop ponuja metoda ref()
, ki sprejme dva argumenta – ime ciljne tabele in povezovalni
stolpec – in vrne primerek ActiveRow
ali null
:
echo $book->ref('author', 'author_id')->name; // povezava do avtorja
echo $book->ref('author', 'translator_id')->name; // povezava do prevajalca
Metoda ref()
je uporabna, če ni mogoče uporabiti dostopa na podlagi lastnosti, na primer kadar tabela vsebuje
stolpec z enakim imenom kot lastnost (author
). V drugih primerih je zaradi boljše berljivosti priporočljiva
uporaba dostopa na podlagi lastnosti.
Raziskovalec samodejno optimizira poizvedbe po zbirki podatkov. Pri iteraciji skozi knjige in dostopu do njihovih povezanih zapisov (avtorji, prevajalci) Raziskovalec ne ustvari poizvedbe za vsako knjigo posebej. Namesto tega za vsako vrsto povezave** izvede le **eno poizvedbo SELECT, s čimer znatno zmanjša obremenitev podatkovne zbirke. Na primer:
$books = $explorer->table('book');
foreach ($books as $book) {
echo $book->title . ': ';
echo $book->author->name;
echo $book->translator?->name;
}
Ta koda bo izvedla le tri optimizirane poizvedbe po zbirki podatkov:
SELECT * FROM `book`;
SELECT * FROM `author` WHERE (`id` IN (1, 2, 3)); -- IDs from 'author_id' column in selected books
SELECT * FROM `author` WHERE (`id` IN (2, 3)); -- IDs from 'translator_id' column in selected books
Logika za identifikacijo povezovalnega stolpca je opredeljena z implementacijo Conventions. Priporočamo uporabo DiscoveredConventions, ki analizira tuje ključe in omogoča nemoteno delo z obstoječimi povezavami tabel.
Dostop do podrejene tabele
Dostop do podrejene tabele deluje v nasprotni smeri. Zdaj vprašamo katero knjigo je napisal ta avtor ali katero
knjigo je prevedel ta prevajalec. Za takšno poizvedbo uporabimo metodo related()
, ki vrne objekt
Selection
s povezanimi zapisi. Tukaj je primer:
$author = $explorer->table('author')->get(1);
// Izpiše vse knjige, ki jih je napisal avtor
foreach ($author->related('book.author_id') as $book) {
echo "Wrote: $book->title";
}
// Izhodi vse knjige, ki jih je avtor prevedel
foreach ($author->related('book.translator_id') as $book) {
echo "Translated: $book->title";
}
Metoda related()
sprejme opis povezave kot en sam argument z uporabo pike ali kot dva ločena argumenta:
$author->related('book.translator_id'); // en sam argument
$author->related('book', 'translator_id'); // dva argumenta
Raziskovalec lahko samodejno zazna pravilen povezovalni stolpec na podlagi imena nadrejene tabele. V tem primeru se poveže
prek stolpca book.author_id
, ker je ime izvorne tabele author
:
$author->related('book'); // uporablja book.author_id
Če obstaja več možnih povezav, bo Raziskovalec vrgel izjemo AmbiguousReferenceKeyException.
Seveda lahko metodo related()
uporabimo tudi pri iteraciji skozi več zapisov v zanki in Raziskovalec bo tudi
v tem primeru samodejno optimiziral poizvedbe:
$authors = $explorer->table('author');
foreach ($authors as $author) {
echo $author->name . ' wrote:';
foreach ($author->related('book') as $book) {
echo $book->title;
}
}
Ta koda ustvari le dve učinkoviti poizvedbi SQL:
SELECT * FROM `author`;
SELECT * FROM `book` WHERE (`author_id` IN (1, 2, 3)); -- IDs of the selected authors
Razmerje veliko-več (Many-to-Many)
Za razmerje mnogo-več (M:N) je potrebna skupna tabela (v našem primeru book_tag
). Ta tabela vsebuje dva
stolpca tujih ključev (book_id
, tag_id
). Vsak stolpec se sklicuje na primarni ključ ene od povezanih
tabel. Če želimo pridobiti povezane podatke, najprej pridobimo zapise iz povezovalne tabele z uporabo
related('book_tag')
, nato pa nadaljujemo do ciljnih podatkov:
$book = $explorer->table('book')->get(1);
// Izpiše imena oznak, dodeljenih knjigi
foreach ($book->related('book_tag') as $bookTag) {
echo $bookTag->tag->name; // pridobi ime oznake prek preglednice povezav
}
$tag = $explorer->table('tag')->get(1);
// Nasprotna smer: izpiše naslove knjig s to oznako
foreach ($tag->related('book_tag') as $bookTag) {
echo $bookTag->book->title; // pridobi naslov knjige
}
Raziskovalec ponovno optimizira poizvedbe SQL v učinkovito obliko:
SELECT * FROM `book`;
SELECT * FROM `book_tag` WHERE (`book_tag`.`book_id` IN (1, 2, ...)); -- IDs of the selected books
SELECT * FROM `tag` WHERE (`tag`.`id` IN (1, 2, ...)); -- IDs of the tags found in book_tag
Poizvedovanje po sorodnih tabelah
V metodah where()
, select()
, order()
in group()
lahko uporabite posebne
zapise za dostop do stolpcev iz drugih tabel. Raziskovalec samodejno ustvari potrebne povezave JOIN.
Zaznamek s piko (parent_table.column
) se uporablja za razmerja 1:N, gledano z vidika nadrejene
tabele:
$books = $explorer->table('book');
// poišče knjige, katerih imena avtorjev se začnejo z 'Jon'
$books->where('author.name LIKE ?', 'Jon%');
// razvrsti knjige po imenu avtorja padajoče.
$books->order('author.name DESC');
// izpiše naslov knjige in ime avtorja
$books->select('book.title, author.name');
Za razmerja 1:N z vidika nadrejene tabele se uporablja zapis v stolpcu:
$authors = $explorer->table('author');
// Poišče avtorje, ki so napisali knjigo z besedo 'PHP' v naslovu
$authors->where(':book.title LIKE ?', '%PHP%');
// šteje število knjig za vsakega avtorja
$authors->select('*, COUNT(:book.id) AS book_count')
->group('author.id');
V zgornjem primeru z zapisom v dvopičju (:book.title
) stolpec tujega ključa ni izrecno določen. Raziskovalec
samodejno zazna pravilen stolpec na podlagi imena nadrejene tabele. V tem primeru se poveže prek stolpca
book.author_id
, ker je ime izvorne tabele author
. Če obstaja več možnih povezav, Raziskovalec vrže
izjemo AmbiguousReferenceKeyException.
Povezovalni stolpec je lahko izrecno naveden v oklepajih:
// Poišče avtorje, ki so prevedli knjigo z besedo 'PHP' v naslovu
$authors->where(':book(translator).title LIKE ?', '%PHP%');
Za dostop do podatkov v več tabelah je mogoče zapise verižno povezati:
// Poišče avtorje knjig z oznako 'PHP'
$authors->where(':book:book_tag.tag.name', 'PHP')
->group('author.id');
Razširitev pogojev za JOIN
Metoda joinWhere()
dodaja dodatne pogoje za združevanje tabel v jeziku SQL za ključno besedo
ON
.
Recimo, da želimo poiskati knjige, ki jih je prevedel določen prevajalec:
// Poišče knjige, ki jih je prevedel prevajalec z imenom 'David'
$books = $explorer->table('book')
->joinWhere('translator', 'translator.name', 'David');
// LEVA povezava z avtorjem prevajalcem ON book.translator_id = translator.id AND (translator.name = 'David')
V pogoju joinWhere()
lahko uporabite enake konstrukcije kot v metodi where()
– operatorje,
nadomestne znake, polja vrednosti ali izraze SQL.
Za bolj zapletene poizvedbe z več povezavami JOIN lahko določite namizne vzdevke:
$tags = $explorer->table('tag')
->joinWhere(':book_tag.book.author', 'book_author.born < ?', 1950)
->alias(':book_tag.book.author', 'book_author');
// LEVO VEZI `book_tag` ON `tag`.`id` = `book_tag`.`tag_id`
// LEVI JOIN `knjiga` ON `knjiga_tag`.`knjiga_id` = `knjiga`.`id`
// LEVI JOIN `autor` `book_author` ON `book`.`author_id` = `book_author`.`id`
// AND (`book_author`.`born` < 1950)
Upoštevajte, da metoda where()
dodaja pogoje v stavek WHERE
, metoda joinWhere()
pa
razširja pogoje v stavku ON
med združevanjem tabel.
Ročno ustvarjanje raziskovalca
Če ne uporabljate vsebnika Nette DI, lahko primerek Nette\Database\Explorer
ustvarite ročno:
use Nette\Database;
// $storage implementira Nette\Caching\Storage, npr.:
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp/dir');
// povezava s podatkovno bazo
$connection = new Database\Connection('mysql:host=127.0.0.1;dbname=mydatabase', 'user', 'password');
// upravlja odsev strukture podatkovne zbirke
$structure = new Database\Structure($connection, $storage);
// določa pravila za preslikavo imen tabel, stolpcev in tujih ključev
$conventions = new Database\Conventions\DiscoveredConventions($structure);
$explorer = new Database\Explorer($connection, $structure, $conventions, $storage);