Keresés több modellben egyszerre

Egyszerre szeretnék több modellben keresni, hogy kivitelezhető ez?

A kontrollerben létrehoztam egy új action, search néven.


public function actionSearch() {

            

            $model = new Hirdetes('search');

            $modelextra = new HirdetesExtra('search');

            $modeluser = new Profile('search');

            $modelkat = new Kategoriak('search');

            $model->unsetAttributes();  // clear any default values

            $modelextra->unsetAttributes();  // clear any default values

            $modeluser->unsetAttributes();  // clear any default values

            $modelkat->unsetAttributes();  // clear any default values

            if(isset($_POST['Hirdetes'])) $model->attributes=$_POST['Hirdetes'];

            if(isset($_POST['HirdetesExtra'])) $modelextra->attributes=$_POST['HirdetesExtra'];

            if(isset($_POST['User'])) $modeluser->attributes=$_POST['User'];

            if(isset($_POST['Kategoriak'])) $modelkat->attributes=$_POST['Kategoriak'];


            $this->render('search',array(

                    'model'=>$model,

                    'modelextra'=>$modelextra,

                    'modeluser'=>$modeluser,

                    'modelkat'=>$modelkat

            ));

            

            

        }

Ehhez meg ugye egy teljesen új renderfájlt. Ez megy is, szépen mutatja a formokat, amiket beállítottam. Csak nem tudom, hogy kellene ezt kivitelezni, hogy mindenféle variációval keressen és csak azokat dobja vissza amikben minden szerepel. A fő modell a Hirdetes, ennek az id-ját szeretném visszakapni.

Gondolom a modelljeid aktív rekord osztály leszármazottjai.

A seach az egy séma akar lenni, ami az AR beépített search paraméterét használná.

Továbbá szeretnél keresni négy darab modellben. Hírek, Hírek Extra, Felhasználók és Kategóriák táblában, majd ezeket szeretnéd egy nézetben összevonva megjeleníteni.

AR-ben findAll()-al tudsz keresni. A findAll metódus egy-egy rekordot ad vissza. Ezeket egymás után fel tudod rakni az űrlapodra 4db foreachel.

Ha mondjuk vegyíteni szeretnéd a keresés eredményét, arra az AR minta nem alkalmas. Olyan esetben vagy megírod a saját keresésedet és a kontrollerben dolgozod össze, de még szebb megoldás, ha szakítasz az AR sémával és Query Biliderrel oldod meg natív sql-el. Nem is egyértelmű számomra, hogy ez a feladat AR-e opimális.

Érdemes elolvasnod a Yii Blog demót és főleg a Definitive guide adatbázis (database) részét. Ott elég sok választ fogsz kapni.

(http://www.yiiframework.com/doc/guide/)

Hogy néz ki pontosan a kereső úrlapod? Szükség van arra, hogy mind a négy model összes tulajdonságára tudjon szűrni a keresés?

Igen, a modellek onnan jönnek. Pontosan a beépített search-öt szeretném/szerettem volna használni, amelyik egyszerűbb. :)

A guide és az api az tiszta, elég változatosan használom őket. Itt egy kicsit komplexebb lenne a keresés. Adott a 4 modell, ezeket a Hirdetes modell köti össze, ebben meg van adva a felhasználó id és a kategoria id. A hirdetesextra modellben pedig a hirdetes id.

Lehet, hogy tényleg valami sajátot kellene írni, csak van vagy 100 feltétel, amire egyesével megírva elég hosszadalmas lenne.

A kereső mezőben a fenti modellek szinte mindegyik mezőjére lehet keresni, de a hirdetes modell fogja őket össze. Egy eset van, ami már kicsit másabb, amikor a felhasználó korára lehet keresni, ugye ott kikeresem azokat a felhasználókat akik annyi idősek, visszadom az id-t és csak azokat a hirdeteseket szűröm, amiknél a user id megegyezik.

Remélem értően írtam le. :)

Kicsit a sötétben tapogatózok még…nem teljesen tiszta, hogy lehetne ezt gyorsan és elegánsan megoldani.

Talán úgy járnál a legjobban, ha AR-el építed fel az űrlapot, de a Gii által generált AR modeleidbe még teszel egy-egy egyedi kereső metódust, amiben már query builder-el építed fel a lekérdezést és nem AR-t, hanem tömböt adsz vissza. Ha ragaszkodsz az AR-hez, akkor még legenrálhatod az egyedi azonosítókból a findall metódusnak megfelelő AR listát. Szerintem ezt a feladatot nem fogod megúszni a gyári search metódussal. Ámbár a problémát még nem teljesen látom át, így lehet, nem jól gondolkodom…

Az űrlapot a modellekkel készítettem el, az alap keresést amit az admin action is tartalmaz. Csak én még hozzá raktam a többi 3 modellt is.

Hát lehet az lesz amit te mondasz, a modell-ben saját kereső rész.

Hát a probléma egyelőre csak annyi, hogy az admin felületen egyesével szépen tudok keresni a beépített keresővel, de nekem egyszerre több modellben kellene és csak azokat visszaadni, ahol egy hirdetésen belül van minden.

De azon is agyaltam, hogy az adott modelleken belül keresek, az eredményeket(id) visszaadom és utána csinálom egy metszetet belőlük. Tehát ahol megegyeznek a rekordok foreing key értékeivel ott azokat listázom majd ki.

Én erre írnék egy SearchForm modellt, ami a CFromModel osztály leszármazottja. Két modellre és 4 tulajdonságra leegyszerűsítve valahogy így:




class SearchForm extends CFormModel

{

public $hirdetesTulajdonsag1;


public $hirdetesTulajdonsag2;


public $hirdetesExtraTulajdonsag1;


public $hirdetesExtraTulajdonsag2;


private $_criteria;


private $_hirdetesExtraCriteria;


public function getCriteria()

{

  if ( ! $this->_criteria instanceof CDbCriteria) {

	$this->_criteria = new CDbCriteria();

  } 

  return $this->_criteria;

}


public function getHirdetesExtraCriteria()

{

  if ( ! $this->_hirdetesExtraCriteria instanceof CDbCriteria) {

	$this->_hirdetesExtraCriteria = new CDbCriteria();

  }

  return $this->_hirdetesExtraCriteria;

}


public function getHasHirdetesExtraCriteria()

{

 return $this->_hirdetesExtraCriteria instanceof CDbCriteria;

}


public function search()

{

  if ( ! empty($this->hirdetesTulajdonsag1)) {

	$this->criteria->addColumnCondition(array('hirdetesTulajdonsag1' => $this->hirdetesTulajdonsag1));

  }

  if ( ! empty($this->hirdetesTulajdonsag2)) {

	//...

  }

  if ( ! empty($this->hirdetesExtraTulajdonsag1) {

	$this->hirdetesExtraCriteria->addColumnCondition(array('hirdetesExtra.tulajdonsag1' => $this->hirdetesExtraTulajdonsag1));

  }

  //...

  if ($this->hasHirdetesExtraCriteria) {

	$this->criteria->mergeWith(array(

  	'with' => array(

    	'hirdetesExtra' => array(

      	'select' => false,

      	'joinType' => 'INNER JOIN',

    	),

  	),

	));

	$this->criteria->mergeWith($this->hirdetesExtraCriteria);

  }

  return new CActiveDataProvider('Hirdetes', array('criteria' => $this->criteria));

}

}



Lehet, hogy nyakatekert megoldásnak tűnik, mert ha sok különböző tulajdonságra lehet keresni, akkor sok kódot kell hozzá írni. Az előnye az, hogy így minden logika ami a kereséssel kapcsolatos egy helyen lesz, nem kell a kontrollerben és a viewben 4 különböző modellel és a köztük levő kapcsolatokkal foglalkozni, elég a SearchForm osztályt példányosítani.

Ha jól értem akkor itt a 4 modell összes elemét fel kellene sorolni és aszerint megnézni, hogy létezik-e a keresőben vagy sem, ha igen akkor hozzáadja.

Viszont lehet, hogy ebből kiindulva egyszerűbb lenne (talán), ha felsorolom a modelleket, ahogy kezdtem. Majd megnézem a postolt adatokat és csak az általad írt kód végét használnám fel.

Meglesem, aztán kiderül, hogy mi lesz belőle. :)

Valami ilyesmire gondoltam, talán járható lenne. :)


private $_criteria;

        private $_hirdetesExtraCriteria;

        private $_hirdetesKatCriteria;

        private $_hirdetesUserCriteria;

        

        

        

        public function actionSearch() {

            

            $model = new Hirdetes();

            $modelextra = new HirdetesExtra();

            $modeluser = new Profile();

            $modelkat = new Kategoriak();

            

            $model->unsetAttributes();  // clear any default values

            $modelextra->unsetAttributes();  // clear any default values

            $modeluser->unsetAttributes();  // clear any default values

            $modelkat->unsetAttributes();  // clear any default values

            

            if(isset($_POST['HirdetesExtra'])) {

                $this->_hirdetesExtraCriteria = new CDbCriteria();

                foreach ($_POST['HirdetesExtra'] as $hirdetesextra) {

                     $this->_hirdetesExtraCriteria->addColumnCondition(array($hirdetesextra => $this->$hirdetesextra));

                }

            }

            if(isset($_POST['User'])) {

                $this->_hirdetesUserCriteria = new CDbCriteria();

                foreach ($_POST['User'] as $hirdetesuser) {

                     $this->_hirdetesUserCriteria->addColumnCondition(array($hirdetesuser => $this->$hirdetesuser));

                }

            }

            if(isset($_POST['Kategoriak'])) {

                $this->_hirdetesKatCriteria = new CDbCriteria();

                foreach ($_POST['Kategoriak'] as $hirdeteskat) {

                     $this->_hirdetesKatCriteria->addColumnCondition(array($hirdeteskat => $this->$hirdeteskat));

                }

            }

            if(isset($_POST['Hirdetes'])) {

                $this->_criteria = new CDbCriteria();

                foreach ($_POST['Hirdetes'] as $hirdetes) {

                     $this->_criteria->addColumnCondition(array($hirdetes => $this->$hirdetes));

                }

                if ($this->_hirdetesExtraCriteria) {

                    $this->_criteria->mergeWith(array(

                    'with' => array(

                    'hirdetesExtra' => array(

                    'select' => false,

                    'joinType' => 'INNER JOIN',

                    ),

                    ),

                    ));

                    $this->_criteria->mergeWith($this->_hirdetesExtraCriteria);

              }

              if ($this->_hirdetesUserCriteria) {

                    $this->_criteria->mergeWith(array(

                    'with' => array(

                    'user' => array(

                    'select' => false,

                    'joinType' => 'INNER JOIN',

                    ),

                    ),

                    ));

                    $this->_criteria->mergeWith($this->_hirdetesUserCriteria);

              }

              if ($this->_hirdetesKatCriteria) {

                    $this->_criteria->mergeWith(array(

                    'with' => array(

                    'user' => array(

                    'select' => false,

                    'joinType' => 'INNER JOIN',

                    ),

                    ),

                    ));

                    $this->_criteria->mergeWith($this->_hirdetesKatCriteria);

              }

            }

            

            if($this->_criteria) {

                $criteria = new CActiveDataProvider('Hirdetes', array('criteria' => $this->_criteria));

                $model = Hirdetes::model()->findAll($criteria);

                

                $this->render('view',array(

                    'model'=>$model,

                    //'modelextra'=>$modelextra,

                    //'modeluser'=>$modeluser,

                    //'modelkat'=>$modelkat

                ));

                

            }


            $this->render('search',array(

                    'model'=>$model,

                    'modelextra'=>$modelextra,

                    'modeluser'=>$modeluser,

                    'modelkat'=>$modelkat

            ));

            

            

        }

A fenti már majdnem jó, kisebb igazításokkal, ha csak az alap modellben keresek. Viszont ha már a többiben is akarok akkor kiakad a biztosíték a join-ok miatt. Valamit meg kellene neki adni a modellekben, de nem vágom, hogyan kellene. Ez a rész kicsit érthetetlen nekem a wiki-ből.

A "hirdetesExtra" reláció nincs definiálva a(z) "Hirdetes" aktív rekord osztályban.

2755

kep.jpg

Valaki ebben tudna segíteni?

Az, hogy fizikai adatbázis kapcsolat nincsen talán nem akkora probléma, mivel lehet scope-okat készíteni, ha megfelelő idegen kulcsok vannak, márpedig itt igen, akkor a yii-ben logikailag felépítehted a kapcsolat rendszert. (azt, hogy hogyan én sem tudom, meg azt sem, hogy miért nem jó)

http://www.yiiframew…th-named-scopes

Kicsit kezdem úgy érezni ezt a problémát, hogy tulajdonképpen nem teljesen megfelelő az adatbázis szervezése.

Miért nem készítesz egy olyan táblát, amiben a keresési kritériumoknak megfelelően redundánsan feltöltöd értékekkel, és akkor csak abban a táblában kell keresned.

Egyrészt nyersz azzal, hogy az AR ott már hatékony lesz. Félő, hogy olyan terhelést fogsz így adni a szervernek, hogy ha még működni is fog a rendszered, egy nagyobb adatbázisnál és felhasználó tábornál már nagyon erős szerver kell hozzá.

Érdekel és várom a megoldást.

Az a baj, hogy elég sok féle adat van és kategória, kategóiránként más más értékekkel mezőkkel. Így ez bizonyult a leghatékonyabbnak. Jobb megoldást nem találok rá (egyelőre). Csak ez a járható út. :(

Közben meglett a megoldás, de az + feltételekre nem akar keresni, az alap az megy, de a többi még nem.

Model része:


public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

                Yii::app()->getModule('User');

                Yii::app()->getModule('Profile');

		return array(

                    'hid' => array(self::HAS_ONE, 'HirdetesExtra', 'hirdetes_id'),

                    'kid' => array(self::HAS_ONE, 'Kategoriak', 'id'),

                    'kategoria' => array(self::HAS_ONE, 'Kategoriak', 'id'),

                    'pid' => array(self::HAS_ONE, 'User', 'id'),

                    'uid' => array(self::HAS_ONE, 'Profile', 'user_id')

		);

	}


private $_criteria;

        private $_hirdetesExtraCriteria;

        private $_hirdetesKatCriteria;

        private $_hirdetesUserCriteria;

        

        public function actionSearch() {

            

            $model = new Hirdetes();

            $modelextra = new HirdetesExtra();

            $modeluser = new Profile();

            $modelkat = new Kategoriak();

            

            $model->unsetAttributes();  // clear any default values

            $modelextra->unsetAttributes();  // clear any default values

            $modeluser->unsetAttributes();  // clear any default values

            $modelkat->unsetAttributes();  // clear any default values

           

            if(isset($_POST['HirdetesExtra'])) {

                $this->_hirdetesExtraCriteria = new CDbCriteria();

                foreach ($_POST['HirdetesExtra'] as $hirdetesextra=>$value) {

                     if($value == 1) $value1 = 'egyfele'; $value2='masikfele';

                     if(!empty($value)) $this->_hirdetesExtraCriteria->addColumnCondition(array($hirdetesextra => $value1, $hirdetesextra=>$value2), 'AND', 'OR');

                }

            }

            if(isset($_POST['Profile'])) {

                $this->_hirdetesUserCriteria = new CDbCriteria();

                foreach ($_POST['Profile'] as $hirdetesuser=>$value) {

                     if(!empty($value)) $this->_hirdetesUserCriteria->addColumnCondition(array($hirdetesuser => $value));

                }

            }

            if(isset($_POST['Kategoriak'])) {

                $this->_hirdetesKatCriteria = new CDbCriteria();

                foreach ($_POST['Kategoriak'] as $hirdeteskat=>$value) {

                     if(!empty($value)) $this->_hirdetesKatCriteria->addColumnCondition(array($hirdeteskat => $value));

                }

            }

             

            if(isset($_POST['Hirdetes'])) {

                $this->_criteria = new CDbCriteria();

                foreach ($_POST['Hirdetes'] as $hirdetes=>$value) {

                    if(!empty($value)) $this->_criteria->addColumnCondition(array($hirdetes =>$value));

                }

                /*

                if ($this->_hirdetesExtraCriteria) {

                    $this->_criteria->mergeWith(array(

                    'with' => array(

                    'hid' => array(

                    'select' => false,

                    'joinType' => 'INNER JOIN',

                    ),

                    ),

                    ));

                    $this->_criteria->mergeWith($this->_hirdetesExtraCriteria);

              }

              if ($this->_hirdetesUserCriteria) {

                    $this->_criteria->mergeWith(array(

                    'with' => array(

                    'uid' => array(

                    'select' => false,

                    'joinType' => 'INNER JOIN',

                    ),

                    ),

                    ));

                    $this->_criteria->mergeWith($this->_hirdetesUserCriteria);

              }

              if ($this->_hirdetesKatCriteria) {

                    $this->_criteria->mergeWith(array(

                    'with' => array(

                    'kid' => array(

                    'select' => false,

                    'joinType' => 'INNER JOIN',

                    ),

                    ),

                    ));

                    $this->_criteria->mergeWith($this->_hirdetesKatCriteria);

              }*/

            }

            

            if($this->_criteria) {

                $criteria = new CActiveDataProvider('Hirdetes', array('criteria' => $this->_criteria));

                //var_dump($this->_criteria); exit;

                //var_dump($criteria); exit;

                

                $this->render('talalat',array(

                    'model'=>$criteria

                ));

                

            } else {

                $this->render('search',array(

                        'model'=>$model,

                        'modelextra'=>$modelextra,

                        'modeluser'=>$modeluser,

                        'modelkat'=>$modelkat

                ));

            }

            

        }

Itt úgy tűnik, van pár probléma:

  • A relációkban mindig az idegen kulcsot tartalmazó oszlop nevét kell megadni, függetlenül attól, hogy ez az oszlop melyik táblában található.

  • Ha "A" és "B" között HAS_MANY kapcsolat van, akkor "B" és "A" között BELONGS_TO.

A fentiek szerint valahogy igy kellene kinéznie (amennyiben helyesen tippeltem meg a tábláid közötti kapcsolatokat):




return array(

  'kategoria' => array(self::BELONGS_TO, 'Kategoriak', 'kategoria_id'),

  'user' => array(self::BELONGS_TO, 'User', 'user_id'),

);



A "HirdetesExtra" reláció jónak tűnik. A "Hirdetes" és a "Profile" között pedig nem egyértelmű a kapcsolat, ezeket az "User" táblán keresztül szeretnéd összekapcsolni?

A denormalizáció a sebesség szempontjából tényleg hasznos lehet, de sokszor több problémát okoz, mint amennyit megold - csak más típusúakat.

Ha MySQL adatbázisról van szó (gyanítom, hogy igen) akkor ott indexelt/materializált vieweket nem tudsz használni, a sima, röptében generált viewek pedig nem jelentenek számottevő előrelépést a teljesítményben. Ha pedig PHP kódból kell frissíteni a redundáns értékeket, az igencsak megbonyolítja a fejlesztő életét :)

A JOINoktól szerintem nem kell félni. A relációs adatbázisokat arra tervezték és optimalizálták, hogy hatékonyan tudjanak táblákat összekapcsolni - ezért hívják "relációs" adatbázisnak. Az AR által generált JOINok pedig - feltéve, hogy idegen kulcs alapján történő egyszerű tábla összekapcsolásokról van szó, nem pedig eredményhalmazok bonyolult feltételek szerinti összekapcsolásáról - ugyanolyan hatékonyak mint a kézzel írott SQL, mivel az AR ugyanazt az SQL-t generálja, mint amit te írnál.

Összességében szerintem célszerűbb normalizált adatmodellel és adatbázissal dolgozni amig csak lehet, és csak akkor denormalizálni, ha konkrét teljesítmény-problémákat tapasztalunk.

Idegen kulcs alatt mit értesz? Fentebb linkeltem a táblák "relációját".

Az utolsó paraméter az idegen tábla mezője vagy a saját tábla mezője?

A hirdetes és a profile között csak a profile id a lényeges, ez az id megegyeik a user id-vel. Ebben a táblában csak az életkort akarom lekérdezni, de lehet jobb lenne ha inkább a hirdetes modellbe beraknám, így egy join-nal kevesebb lenne. Úgy is, 1 user csak 1 hirdetést adhat fel.

A joint már így is csökkentettem a kategóriával, mert az úgy is csak egy számot ad vissza, ezt meg beraktam az alap critériumba. Szóval ha még az életkort is beraknám a hirdetésbe, akkor lényegében csak 1 join lenne, ami talán nem jelentene akkor terhelést. Bár azért így is 100 mező van 2 táblában, szóval…

Illetve mi van abba az esetben, hogy lehet megoldani ha.

Adott egy checkbox. Vagy 0 vagy 1 az értéke. Ha 0 akkor nem is foglalkozok vele, mert nincs kipipálva. Viszont ha igen, akkor egy or kapcsolat kellene arra a részre. Ezt hogy lehet megadni?

Próbáltam így, de csak az utolsó értéket vette figyelembe.


foreach ($_POST['HirdetesExtra'] as $hirdetesextra=>$value) {

                     if($value == 1) $value = 1; $value2 = 2;

                     if(!empty($value)) $this->_hirdetesExtraCriteria->addColumnCondition(array($hirdetesextra => $value, $hirdetesextra=>$value2));

                }

Azt az oszlopot/mezőt amelyik létrehozza a kapcsolatot a két tábla között. A hirdetések táblában pl. a kategória id-t és az user id-t tartalmazó oszlop.

Attól függ, hogy melyik tábla tartalmazza az idegen kulcsot.

Ezt érdemes lehet átnézni: Relational Query with through

Az addColumnCondition() -nak van opcionális második és harmadik paramétere is…

EDIT: upsz, ezt elnéztem, az addColumnCondition() jelen esetben nem fog működni, az addInCondition() lesz a megfejtés.

Végül így oldottam meg:


$this->_hirdetesExtraCriteria->addCondition( $hirdetesextra.'= :val1 or '.$hirdetesextra.' = :val2' );

$this->_hirdetesExtraCriteria->params[':val1'] = $value;

$this->_hirdetesExtraCriteria->params[':val2'] = $value2;

Úgy néz ki már okés minden, már csak pár dolgot kell előtte megváltoztatni, mert 1-2 keresési paraméter másmilyen.

Pl ha a korra keresek akkor így keresek rá 30-45év között, de az adatbázisban így van eltárolva 1978-02-15.

De ez már részletkérdés, php-val előtte megcsinálom jó paraméterre.

Köszi az eddigieket. :)

Nincs mit. Nem csináltam még (Yii-vel legalábbis) ilyen sokmodelles összetett keresést, és engem is érdekelt, hogy hogyan lehetne megoldani.

Jó volt így AR-al megoldani, féltem azért, hogy több soros saját magam által írt sql lekérdezéseket kell majd írni, de nem. :)

Ja ,köszi a felvetést és a megoldásokat is, engem is nagyon érdekelt a téma. A http://www.mysql.com/products/workbench/ programmal érdemes kidolgozni valamilyen vázlatot a relációs modellből, akkor kevesebbet kell írni itt a fórumon és jobban is átlátható a kérdés.