Dane do portletu z dwóch modeli

Witam.

Posiadam na stronie portlet który wyświetla kategorie pobrań dla plików, np:

Regulaminy

Pliki

itd.

i po kliknięciu odpowiedniej kategorii na stronie mam spis plików tylko z tej kategorii. Chciałem dołożyć do tego jeszcze aby obok nazwy kategorii informacje o ilości plików w danej kategorii, np:

Regulaminy (2)

Pliki (1)

Inne (0)

Kod portletu:




<?php

Yii::import('zii.widgets.CPortlet');

 

class DownloadCategory extends CPortlet

{

    public $title='Kategoria';

    public $decorationCssClass='portletdownload-decoration';

    public $titleCssClass='portletdownload-title';

    public $contentCssClass='portletdownload-content';

     

    public function getDownloadCategory()

    {

        return Typpobrania::model()->findDownloadCategory();

    }

 

    protected function renderContent()

    {

        $this->render('downloadCategory');

    }

}

?>



Kod funkcji z modelu:




public function findDownloadCategory()

{

    return $this->findAll();

}



Kod widoku:




<ul>

	<?php foreach($this->getDownloadCategory() as $downloads): ?>

	<li> <?php echo CHtml::link(CHtml::encode($downloads->nazwa), $downloads->getUrl()); ?> </li>

	<?php endforeach; ?>

</ul>



Ponieważ chcę aby były wyświetlone wszystkie nazwy kategorii dlatego dane dla portletu są z tabeli Typpobrania, gdzie są dwa pola: id i nazwa, ale ma to ta wadę że nie mogę zrobić zliczenia ile plików wg danej kategorii występuje ponieważ są one umieszczone w tabeli połączonej Pobrania.

Obie tabele są połączone relacjami:

Typpobrania:




'pobrania' => array(self::HAS_MANY, 'Pobrania', 'typpobrania_id'),



Pobrania:




'typpobrania' => array(self::BELONGS_TO, 'Typpobrania', 'typpobrania_id'),



Oczywiście mogę to zrobić wg modelu Pobrania, ale wtedy nie mam kategorii w których nie ma żadnych plików.

W SQL-u byłoby to tak:




SELECT nazwa, (SELECT count(typpobrania_id) FROM tbl_pobrania WHERE typpobrania_id = p.id) AS ile FROM tbl_typpobrania p



tylko jak to wcisnąć do tego modelu ?

Nie najefektywniejsza metoda, ale najłatwiejsza bo już wszystko masz :)

W pętli foreach daj:

echo count($downloads->pobrania);

Ta da :)

Obstawiam, że przyda Ci się to:

http://www.yiiframework.com/doc/guide/1.1/en/database.arr#statistical-query

Jeśli nie to wrzuć tu schemat tabel z bazy danych.

Wydaje mi się, że sprawę załatwiłoby FindAllBySql() w funkcji findDownloadCategory(), które w wyniku podałoby właściwy zestaw pól, tylko moje próby zpisania tego sql’a:




SELECT nazwa, (SELECT count( typpobrania_id ) FROM tbl_pobrania WHERE typpobrania_id = p.id) AS ile

FROM tbl_typpobrania p



coś nie wychodzą.

Nie próbowałeś mojego rozwiązania? :)




<ul>

        <?php foreach($this->getDownloadCategory() as $downloads): ?>

        <li> <?php echo CHtml::link(CHtml::encode($downloads->nazwa).'('.count($downloads->pobrania).')', $downloads->getUrl()); ?> </li>

        <?php endforeach; ?>

</ul>



Twoja propozycja jest OK i ją zastosowałem, tylko nie wiem na jakiej zasadzie działa ta cześć kodu:




count($downloads->pobrania)



więc próbowałem z FindAllBySql() bo wiem co z czego, a tu funkcja:




public function findDownloadCategory()

{

    return $this->findAll();

}



jest w modelu w którym są tyko 2 kolumny id i nazwa, więc skąd ten "count" wie że ma zliczać wg pola "typpobrania_id" z modelu "Pobrania"?

Chyba, że relacje to załatwiają?

A czy funkcja findDownloadCategory()nie powinna mieć odniesienia do tej relacji i wygladać tak:




public function findDownloadCategory()

    {

        return $this->with('pobrania')->findAll();

    }



Czesc

Lazy loading. Czyli w momencie wywolania count($downloads->pobrania) odpala sie getter, ktory dochodzi do wniosku, ze wlasnosc pobrania to relacja o takiej nazwie. Wykonuje zatem sql, ktory znajduje wszystkie rekordy pasujace do relacji i zwraca te rekordy w postaci tablicy. Nastepnie count zwraca ilosc elementow w tej tablicy. Nie jest to wydajne, poniewaz dla kazdego rekordu tego samego typu co $downloads musisz wykonac osobny sql pobierajacy wszystkie ‘pobrania’… Chyba, ze uzyjesz wczesniej modyfikatora with.

Czasami bywa tak, ze lepiej uzywac findBySql niz liczyc na to, ze wszystko zalatwi za Ciebie aktywny rekord. Jezeli bardzo zalezy Ci na tym, zeby wszystkie pobrane elementy byly obiektami uzyj populateRecords

d.

Czesc,

@robikon modele po czym dziedzicza? po ActiveRecord czy po CModel?

Wlasciwie to nie ma wielkiego znaczenia bo zawsze mozesz w AR skorzystac z natywnego sqla.

Mozesz sprobowac tak:




public function findDownloadCategory()

    {

       $sql= 'SELECT nazwa, (SELECT count( typpobrania_id ) FROM tbl_pobrania WHERE typpobrania_id = p.id) AS ile

FROM tbl_typpobrania p'; 

       $connection = Yii::app()->db;

       $command=$connection->createCommand($sql);

       $result = $command->queryAll();

       return $result;

    }



Albo mozesz wykorzystac Query Buildera zapisujac tak (wynik powinien byc taki sam):




public function findDownloadCategory()

    {

       $result = Yii::app()->db->createCommand()->select('p.nazwa, (SELECT count( typpobrania_id ) FROM tbl_pobrania WHERE typpobrania_id = p.id) AS ile')

                                                 ->from('tbl_typpobrania p')->queryAll();


       return $result;

    }



Jesli chodzi o samo zapytanie to lepszy (ale to jeszcze zalezy kiedy :) ) bylby zapis w stylu:




SELECT tp.nazwa, COUNT(pob.typpobrania_id) AS ile

FROM tbl_typpobrania tp

JOIN tbl_pobrania pob ON (pob.typpobrania_id = tp.id)

GROUP BY tp.nazwa



czyli procedura mialaby postac np:




public function findDownloadCategory()

    {

       $result = Yii::app()->db->createCommand()->select('tp.nazwa, COUNT(pob.typpobrania_id) AS ile')

                                                ->from('tbl_typpobrania tp')

                                                ->join('tbl pobrania pob', 'pob.typpobrania_id = tp.id')

                                                ->group('tp.nazwa')

                                                ->queryAll();


       return $result;

    }



Tak jak napisal @drylko, jesli zalezaloby Ci bardzo, zeby zwracany wynik byl typowym AR to kazde zwrocenie wyniku powinienes zapisac jako:


 return $this->populateRecords($result); 

Szczerze dziękuję, za wszystkie podpowiedzi i przykłady użycia składni SQL w zapytaniach. Fajnie wytłumaczone.