Join tabulek s použitím DiscoveredReflection

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Drake
Člen | 13
+
0
-

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í

  1. Použít nejnovější verzi Nette/Database (použil jsem night build z 3.1.2012)
  2. 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)

hrach
Člen | 1838
+
0
-

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

Drake
Člen | 13
+
0
-

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
+
0
-

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
+
0
-

Tak jsem si nahrál dnešní nighty build a jede to, díky za pomoc. Jen doufám, že si tím nezadělám na jiné neobjevené chyby.

Editoval Drake (3. 1. 2012 19:49)

Drake
Člen | 13
+
0
-

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.

ViPEr*CZ*
Člen | 817
+
0
-

Vždyť jsem Vám to psal, že to funguje jen v konvencích a dokonce jsem Vám poslal odkaz, kde řešíme, že to bez konvencí nejde a že je to chybka.

hrach
Člen | 1838
+
0
-

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 sloupec ciselnik_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 obsahuje ciselnik, tzn. muze to byt klidne id_ciselnik, muze to byt ciselnik_id, muze to byt jenom ciselnik. Podle foreign klice se pak zjisti cilova tabulka, ktera opet slovo ciselnik 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 sloupcem author_id, ta bude provazana pres klice na tabulku users. Je preci mnohem logictejsi volat $post->author->name, nez $post->user->name.
hrach
Člen | 1838
+
0
-

Takze rada pro @drake – zaved si nejakou logiku v db a nebudes mit problemy. To co pouzivas – id_cis je prasarna/blbost i bez nette databse :P ;)

Filip Procházka
Moderator | 4668
+
0
-

Souhlasím s @**hrach**em, začni používat konvence a nebudeš mít problémy.

Drake
Člen | 13
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

@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
+
0
-

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
+
0
-

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->userid_user
    • $post->user_editid_user_edit
    • $post->editid_user_edit
    • $post->editorid_user_editor
    • $post->r_edid_user_edit
  • prostě to celkem předpokladatelně vybere nejkratší match…
David Grudl
Nette Core | 8228
+
0
-

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
+
0
-

@David:
Jak bys resil userId? (respektive zavolani use)

Editoval hrach (9. 1. 2012 8:59)

David Grudl
Nette Core | 8228
+
0
-

(^|_)…([A-Z_]|$)

hrach
Člen | 1838
+
0
-

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
+
0
-

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í.

hrach
Člen | 1838
+
0
-

Ano, jsou to pádné argumenty :)

err
Člen | 77
+
0
-

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->userid_user
    • $post->user_editid_user_edit
    • $post->editid_user_edit
    • $post->editorid_user_editor
    • $post->r_edid_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
+
0
-

@err:

err
Člen | 77
+
0
-

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)

hrach
Člen | 1838
+
0
-

Ujisti se, ze mas posledni verzi a zkus si pohrat s testy, tam to vsechno funguje, to co tu pises vypada ok. Ale tezko rict. A uz zamknete toto vlakno a reste kazdy problem zvlast!

err
Člen | 77
+
0
-

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.