Join tabulek s použitím DiscoveredReflection
- Drake
- Člen | 13
Jak správně nastavit DiscoveredReflection a databázi, aby fungoval join tabulek? Problém předvedu na minimálním příkladě:
<?php
include __DIR__ . '/../nette2b/loader.php';
use Nette\Diagnostics\Debugger;
Debugger::enable();
$connection = new \Nette\Database\Connection(...);
$connection->setDatabaseReflection(new \Nette\Database\Reflection\DiscoveredReflection());
$tabulka = $connection->table("tabulka")->select("tabulka.*, ciselnik.cislo");
echo $tabulka->getSql();
foreach ($tabulka as $radek) {
echo $radek->text." ".$radek->cislo."<BR>";
}
?>
Databázi mám nastavenou takto:
CREATE TABLE IF NOT EXISTS `ciselnik` (
`id` int(11) NOT NULL,
`cislo` varchar(20) COLLATE utf8_czech_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;
INSERT INTO `ciselnik` (`id`, `cislo`) VALUES
(0, 'nula');
CREATE TABLE IF NOT EXISTS `tabulka` (
`id` char(10) COLLATE utf8_czech_ci NOT NULL,
`text` char(12) COLLATE utf8_czech_ci NOT NULL,
`popis` text COLLATE utf8_czech_ci NOT NULL,
`ciselnik_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `ciselnik_id` (`ciselnik_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;
INSERT INTO `tabulka` (`id`, `text`, `popis`, `ciselnik_id`) VALUES
('TABU000001', 'test', 'popis', 0);
ALTER TABLE `tabulka`
ADD CONSTRAINT `tabulka_ibfk_1` FOREIGN KEY (`ciselnik_id`) REFERENCES `ciselnik` (`id`);
Bez DiscoveredReflection to funguje, ale s DiscoveredReflection to
hlásí:
„PDOException: No reference found for $tabulka->tabulka“
Používám PHP 5.3.5 a Nette Framework 2.0-beta pro PHP 5.3 ze dne 12. 12. 2011
EDIT 3.1. 22:35
Řešení
- Použít nejnovější verzi Nette/Database (použil jsem night build z 3.1.2012)
- Aby to fungovalo bez konvencí, tak se odkazovat se přes název cizího klíče, u mě:
select("tabulka.*, ciselnik_id.cislo")
Cizí klíč se pak v tabulce může jmenovat jakkoliv.
Editoval Drake (3. 1. 2012 22:36)
- Drake
- Člen | 13
hrach napsal(a):
Notorm/ndb pristup je takovyto:
$tabulka = $connection->table("tabulka"); foreach ($tabulka as $radek) { echo $radek->text." ".$radek->ciselnik->cislo."<BR>"; }
Proc ti to tvoje nefunguje od poheldu nevim, muzu se pak na to podivat
Vyzkoušel jsem a pořád to hlásí tu samou chybu.
- ViPEr*CZ*
- Člen | 817
Zkusil bych si stáhnout novější Nette… i když podle klíčů používáte spíše ConventionalReflection. V DiscoveredReflection by to mělo spíše brát nekonvenční klíče, ale zatím tam je drobná chybka … viz.: https://forum.nette.org/…azvu-sloupce
- Drake
- Člen | 13
Narazil jsem na další zásadní chybu rychleji než jsem čekal. Ono to funguje, ale jenom, když tabulky dodržují konvence. Bez konvencí to opět nefunguje, s tou samou chybovou hláškou: „PDOException: No reference found for $tabulka->ciselnik“
Použil jsem tuto definici databáze:
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
CREATE TABLE IF NOT EXISTS `ciselnik` (
`id_cis` int(11) NOT NULL,
`cislo` varchar(20) COLLATE utf8_czech_ci NOT NULL,
PRIMARY KEY (`id_cis`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;
INSERT INTO `ciselnik` (`id_cis`, `cislo`) VALUES
(0, 'nula');
CREATE TABLE IF NOT EXISTS `tabulka` (
`id_tab` char(10) COLLATE utf8_czech_ci NOT NULL,
`text` char(12) COLLATE utf8_czech_ci NOT NULL,
`popis` text COLLATE utf8_czech_ci NOT NULL,
`id_cis` int(11) NOT NULL,
PRIMARY KEY (`id_tab`),
KEY `id_cis` (`id_cis`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci;
INSERT INTO `tabulka` (`id_tab`, `text`, `popis`, `id_cis`) VALUES
('TABU000001', 'test', 'popis', 0),
('TABU000002', 'aaaa', 'sss', 0),
('TABU000003', 'zzz', 'aaa', 0),
('TABU000005', 'aaa', 'ttt', 0),
('TABU000006', '1', '2', 0);
ALTER TABLE `tabulka`
ADD CONSTRAINT `tabulka_ibfk_1` FOREIGN KEY (`id_cis`) REFERENCES `ciselnik` (`id_cis`);
DiscoveredReflection potřebuji použít proto, že mám přes sto tabulek, které vůbec nedodržují konvence a zadavatel je nechce upravovat.
- hrach
- Člen | 1838
To, že to nejde, není chyba! Je to holt úděl logiky. Vysvětlím.
- v notorm a starém nette database se vyžaduje, aby
I v DiscoveredReflection byl vazební tvořen takhle:
$row->ciselnik
pouzije vazebni sloupecciselnik_id
a podle discovery reflection to najde cilovou tabulku, ktera se slovem ciselnik nemusi mit nic spolecneho - v novem nette database to je v DiscoveryReflection vylepseno: pri
$row->ciselnik
se hleda v aktualni tabulce sloupec, ktery obsahujeciselnik
, tzn. muze to byt klidneid_ciselnik
, muze to bytciselnik_id
, muze to byt jenomciselnik
. Podle foreign klice se pak zjisti cilova tabulka, ktera opet slovociselnik
nemusi vubec obsahovat.
Urcite ted vyvstava otazka, proc se pri $row->ciselnik
nedivam, ktery sloupec smeruje do tabulky ciselnik
. Duvodu
je vic:
- pokud bude z aktualni tabulky smerovat do cilove tabulky
(
ciselnik
) vice sloupcu, nepujde urcit, ktery se ma pouzit. - ikdyz bude smerovat do cilove tabulky jen jeden sloupec, bude to dosti
nelogicke, vezmeme v uvahu napri tabulku
posts
se sloupcemauthor_id
, ta bude provazana pres klice na tabulkuusers
. Je preci mnohem logictejsi volat$post->author->name
, nez$post->user->name
.
- Filip Procházka
- Moderator | 4668
Souhlasím s @**hrach**em, začni používat konvence a nebudeš mít problémy.
- Drake
- Člen | 13
Díky za radu. Bohužel, konvence používat nemůžu. Pracuji v týmu a není to moje rozhodnutí a prostě tam zavést nejdou.
Jediné řešení využívající notORM přístup je, si dopsat knihovny tak, aby to fungovalo jako $row->cizi_klic misto $row->tabulka. Pak by to bylo jednoznačné i pro více cizích klíčů směřujících do jedné tabulky a nebylo by třeba konvencí.
Jednodušší ale bude použít na join tabulek normální SQL dotaz, měl jsem to udělat už dávno.
- hrach
- Člen | 1838
Pletes pate pres devate. Prave ze aktualni reseni vyuziva
$row->cizi_klic.
Dale ti nic nebrani udelat:
class MyReflection extends Nette\Database\Reflection\DiscroveryReflection
{
public function getBelongsTo($table, $key) {
if ($key == 'ciselnik')
return array('ciselnik', 'id_cis');
return parent::getBelongsTo($table, $key);
}
}
pisu z hlavy, melo by to tak nejak byt.
Editoval hrach (4. 1. 2012 15:09)
- Drake
- Člen | 13
hrach napsal(a):
Pletes pate pres devate. Prave ze aktualni reseni vyuziva $row->cizi_klic.
Díky, vážně to funguje. Stačí místo
select("tabulka.*, ciselnik.cislo")
dát
select("tabulka.*, id_cis.cislo")
a už to jede. Toto jsem zkoušel už v dřívější verzi, ale nefungovalo to a nikde jsem neviděl napsané, že to jde i přes cizí klíče, takže jsem to znova nezkoušel. Ještě jednou díky za pomoc, snad už mě nečekají žádná další nepříjemná překvapení.
Editoval Drake (3. 1. 2012 22:38)
- jan.reges
- Člen | 10
Zdravim.
Nova verzia DiscoveredReflection funguje vyborne, hrach, dobra praca!
Narazili sme ale na jednu issue. Pouzivame u niektorych nasich projektov v nazvoch tabuliek dve podtrzitka __ ako oddelovac namespace/package a discovery u tabuliek s dvoma podtrzitkami nefunguje spravne.
Priklad, ktory funguje (vypis sezon spolu s nazvom zeme, do ktorej hotel patri):
foreach($this->db->table('hotel__sezona')->limit(10) as $sezona) {
echo "{$sezona->nazev} - {$sezona->hotel->destinace->zeme->nazev}\n";
}
Priklad, ktory nefunguje (vypis popisu fotiek spolu s nazvom zeme):
foreach($this->db->table('hotel__sezona__fotka')->limit(10) as $fotka) {
echo "{$fotka->popis} - {$fotka->hotel__sezona->hotel->destinace->zeme->nazev}\n";
}
U tabulky hotel__sezona__fotka je standardne definovany FK u id_hotel_sezona na hotel__sezona, napriek tomu to vyhodi PDOException „No reference found for $hotel__sezona__fotka->hotel__sezona.“. Skusal som to aj na inych tabulkach, ktere maju tiez spravne nastavene FK, ale problem zjavne robia 2 podtrzitka v nazvoch.
V ladenke vidim, ze pomocny discovery select do information_schema vratil spravne info o FK, problem bude niekde dalej..
COLUMN_NAME | REFERENCED_TABLE_NAME
id_hotel_sezona | hotel__sezona
Mozete to prosim otestovat u seba a pripadne odladit?
Dakujem.
PS: ak sa niekto pytate, preco pouzivame 2 podtrzitka v nazvoch tabuliek,
uvediem priklad. Tabulka hotel__sezona__max_obsazenost obsahuje
informacie o max. obsadenosti hotelu v jeho konkretnej sezone.
Na zaklade tejto konvencie potom nas generator generuje VO a veci suvisiace
s CRUD napr. Projekt_Hotel_Sezona_MaxObsazenost do
/libs/Projekt/Hotel/Sezona/MaxObsazenost.php. U projektov, kde je
rozsiahla struktura, sa nam to osvedcilo (priklad:
Zeme->Destinace->Hotel->Sezona->Fotka->Komentar->Hodnoceni)
Databazy niektorych rozsiahlejsich projektov maju kludne cez 100–200 tabuliek a stovky views/procedur, preto u niektorych pouzivame tuto konvenciu. V niektorych pripadoch sa da projekt rozdelit do viacerych databaz, v niektorych sa to ale nehodi.
AKTUALIZACIA 1: cely problem nizsie plynie z toho, ze metoda getBelongsToReference() interne povazuje za referenciu len pripad, kedy nazov $column obsahuje $key, to ale v nasom pripade neplati, pretoze v nazve stlpcov s FK uz 2 podtrzitka nepouzivame. Problem je teda na nasom prijimaci. Dovodov, kvoli ktorym to tak funguje som si vedomy, preto to u tohto projektu vyriesime dedenim DiscoveredReflection a overridom getBelongsToReference(), aby v specifickych pripadoch nevyzadovala „$column contains $key“. Alebo vas napada ine riesenie? Refaktoring nazvu vsetkych tych stlpcov v celom projekte by bol casovo narocny.
AKTUALIZACIA 2: znovu som si precital prispevok #8, pozrel sa na metodu getBelongsToReference() a uvedomil som si suvislosti. Autor to navrhol naozaj chytro! Staci ak sa pri definicii $record->cast_nazvu_stlpca zisti, ze v tabulke existuje stlpec s FK, ktory obsahuje „cast_nazvu_stlpca“ a ono to pripoji cielovu tabulku. Takze spravne zafunguje $record->autor, aj ked sa stlpec vola „id_uzivatel_autor“ (alebo iba „id_autor“) a FK smeruje do tabulky „uzivatel“.
V mojom nefunkcnom kode teda staci zmenit „hotel__sezona“ na „hotel_sezona“, pretoze stlpec s FK sa vola „id_hotel_sezona“ (s jednym podtrzitkom). Mozeme si zato sami, nemali sme urobit vynimku v tej konvencii :)
Funkcne riesenie:
foreach($this->db->table('hotel__sezona__fotka')->limit(10) as $fotka) {
echo "{$fotka->popis} - {$fotka->hotel_sezona->hotel->destinace->zeme->nazev}\n";
}
Editoval jan.reges (8. 1. 2012 18:38)
- hrach
- Člen | 1838
@jan.reges
- proti dvou podtrzitkum nic nemam, taky obcas pouzivame ;)
- zkusil jsem upravit nette_test.sql aby vsude pouzival dve potrzitka a spustit nad dim discoveryReflection.phpt a probehlo to v poradku. imo bude chyba nekde jinde; pripadne zaloz nove vlakno a proloz relevantni definice tabulek. (vcetne vsech klicu!).
- jan.reges
- Člen | 10
hrach napsal(a):
@jan.reges
- proti dvou podtrzitkum nic nemam, taky obcas pouzivame ;)
- zkusil jsem upravit nette_test.sql aby vsude pouzival dve potrzitka a spustit nad dim discoveryReflection.phpt a probehlo to v poradku. imo bude chyba nekde jinde; pripadne zaloz nove vlakno a proloz relevantni definice tabulek. (vcetne vsech klicu!).
Uz som na to prisiel, vid AKTUALIZACIA 1+2 v mojom prispevku. Navrhol si to naozaj sikovne a univerzalne.
- hrach
- Člen | 1838
No to víš, že sem to udělal chytře ;) :P Je tam mj. takový malý
chyták:
Pokud tabulka obsahuje vazby, ktere jsou si pojmenovanim podobne, pouziva se
vzdy ta kratsi, ktera v danou chvili vyhovuje.
- mejme tedy
Posts(..., id_user, id_user_edit, id_user_editor)
- matchuje:
$post->user
→id_user
$post->user_edit
→id_user_edit
$post->edit
→id_user_edit
$post->editor
→id_user_editor
$post->r_ed
→id_user_edit
- prostě to celkem předpokladatelně vybere nejkratší match…
- David Grudl
- Nette Core | 8228
Nebylo by lepší to ověřovat pomocí reguláru tak, aby název musel být
ohraničen \W, tj. aby edit matchnul, editor matchnul, ale edito
vyhodil chybu?
- hrach
- Člen | 1838
Nu budiz, asi to bude lepsi, jen tak nejak premyslim,jak to chces naprogramovat :) ;) Udelas to? Mam to udelat ja?
Jen mi pripada, ze pak se bude muset dodrzovat case-sensitive klice. Jestli to dobre chapu, chces
- editor
->
preg_match('#(^|_)editor([A-Z_]|$)#', $foreign_column)
Tedy, nefungovalo by pak volani:
- ediTor
Coz asi nebude velky problem, nicmene dokazu si predstavit situace, kdy by se mi to hodilo:
- myKey, klic ale pojmenovany
mykey_id
- David Grudl
- Nette Core | 8228
Jde o to, že současné řešení dává zbytečnou volnost, která by se mohla vymstít, ať už jde o odchytávání chyb nebo budoucí rozšíření db tabulky o nové sloupce. Já bych byl za inteligentní, ale velmi striktní výchozí chování.
- err
- Člen | 77
hrach napsal(a):
No to víš, že sem to udělal chytře ;) :P Je tam mj. takový malý chyták:
Pokud tabulka obsahuje vazby, ktere jsou si pojmenovanim podobne, pouziva se vzdy ta kratsi, ktera v danou chvili vyhovuje.
- mejme tedy
Posts(..., id_user, id_user_edit, id_user_editor)
- matchuje:
$post->user
→id_user
$post->user_edit
→id_user_edit
$post->edit
→id_user_edit
$post->editor
→id_user_editor
$post->r_ed
→id_user_edit
- prostě to celkem předpokladatelně vybere nejkratší match…
Ahoj, chytl bych se toho. Uz druhy den zapasim s obycejnym joinem dvou tabulek a uz se chystam na ->query(…). Ale nez to vzdam:
Tabulky mam nejak podobne:
groups(id, typ(int3), …), FK groups_type_id → groups_type sloupec id
groups_type(id(int3), name, …)
V modelu mam:
function getGroups() {
...
return $this->database->table('groups')->select('groups.*, groups_type.name AS type_name');
}
V presenteru:
public function renderGroup()
{
$this->template->groups = $this->model->getGroups();
}
V sablone:
…
{foreach $groups as $group}
{$groups->name}
{$group->type_name}
{/foreach}
Z tabulky groups mam vypis vsech sloupcu v pohode, ale $group->type_name je prazdny. Error nehlasi ani ladenka, ani neni nic v logu, dubug zapnuty mam. Zkousel jsem pristupovat i pres cizi klic, pak to ale pise to, co resite vyse. Tedy neznama tabulka. Pokud dam v sablone $group->groups_type->name , mam error o neznamem sloupci.
Nenakopl bys me prosim spravnym smerem ?
Zkousel jsem na beta 2 i na poslednim dev . Chystal jsem se take pouzit NotORM, ale include do Nette bylo na muj cca tydenni zacatek asi prilis.
Diky.
Editoval err (10. 1. 2012 0:01)
- hrach
- Člen | 1838
@err:
- trochu cti, spravny ndb pristup: https://forum.nette.org/…edreflection#…
- provadi se s nejvetsi pravdepobnosti left join, takze to muze byt prazdny
- err
- Člen | 77
To jsem zkousel, ale asi jsem to zcela nepochopil. Pokud v modelu ve funkci getGroups() volam jen:
function getGroups() {
return $this->database->table('groups');
}
V resenteru:
public function renderGroup()
{
$this->template->groups = $this->model->getGroups();
}
V sablone:
{foreach $groups as $group}
{$group->groups_type->name}
{/foreach}
Ladenka zahlasi Trying to get property of non-object a log napise, ze tabulka
groups_type_id neexistuje, coz je pravda, existuje pouze groups_type
Zkousel jsem to i pres cizi_klic.nazevSloupce , ale to neni take funkcni. Kde
delam chybu ?
Editoval err (10. 1. 2012 16:50)
- err
- Člen | 77
Posledni verzi mam. Myslim, ze je to vse tim, ze jsem to dostatecne nepochopil … Uz to nepise ani do logu nic o spatne tabulce, pouze ladenka hlasi neexistujici objekt pri odkazu v sablone. Vyresim to SQL dotazem, ale jsem tak trochu zklamany. Ocekaval jsem usporu prace, ale zatim to vypada tak, ze se uspora nekona a spise prichazeji omezeni.