Pagination with hasMany association cakePHP - sql

I have two tables:
Contestant and Votes
Contestant hasMany Votes
I've tried doing a count(Vote.id) as Votes so I can place it on the
recordset and just paginate them but I have no idea where to place it.
I did it on the fields array but it gave me the total count of votes
regardless of the contestant they belong to.
The votes are linked together in the Contestant recordset so what I
did was on my view I did a count($contestant[Vote]) but this can't be
paginated and my client wants to be able to sort the contestants by
votes.
Is there a way I can do something like this on my view?:
sort('Votes', 'count(Vote)'); ?>
Or do I have to create a query which does a count for all the votes
where Contestant.id = Votes.contestant_id ?
Controller Contestant:
function index() {
$page = 'Contestants';
$this->set('page', $page);
$this->paginate =
array(
'order' => 'id ASC',
'contain' => array(
'Vote' => array(
'fields' => array("Vote.contestant_id",'Vote.id')
)
)
$conditions ["Contestant.active"] = 1;
$this->set('contestants', $this->paginate('Contestant',
$conditions));
}

Check out deceze's response in this question: CakePHP mathematic-calculation field?
Essentially you want to do something like this I'm guessing:
'contain' => array(
'Vote' => array(
'fields' => array('SUM(Vote.id) AS Contestant__votes'),
'group' => array('Vote.contestant_id'),
)
)

Since cakephp doesn't support group by in containable behavior I tried a different approach. Create the paginate var for the vote model instead (All of this is done in the Contestants Controller):
var $paginate = array(
'Vote'=>array(
'limit'=>5,
'fields' => array(
'Contestant.*, count(Vote.contestant_id) as Contestant_votes, Vote.id'
),
'group' => array(
'Vote.contestant_id'
),
'order' => array(
'Contestant_votes Desc'
)
),
'Contestant'=>array(
'limit'=>5,
'order' => array(
'Contestant.id Desc'
)
)
);
And now in my controller I do the following:
function index() {
$page = 'Contestants';
$this->set('page', $page);
$conditions ["Contestant.active"] = 1;
$this->set('contestants', $this->paginate($this->Contestant->Vote,$conditions));
}
Now the contestants are ordered by their total vote tally, although I still can't figure how to place the Contestant_votes as a paginator variable since in the record set it's in a array of it's own and not in any of the model arrays used to paginate.
Thanks Matt Huggins your approach was the one that led me to this solution.

Addition: Do you also want to sort by Votes (total votes) ascending or descending? If yes, you can not do it easily by the default pagination method of cakephp.
For that you need a little tweak. Here is the details about this trick: CakePHP Advanced Pagination – sort by derived field
Hope you'd find it helpful.
Thanks
Adnan

For the specific relationship you define, your needs are well-served by counter-caching.
You will need to define a new field in your contestants table: vote_count. Then, in your Votes model, you'll need to update the $belongsTo definition slightly:
class Votes extends AppModel
{
var $belongsTo = array(
'Contestant' => array( 'counterCache' => true )
);
}
Now, anytime a Vote record is saved, the vote_count field of the parent Contestant will be updated. Now you can simply sort by Contestant.vote_count as you would any other Contestant field:
class ContestantsController extends AppController
{
// Controller stuff that comes before...
function index()
{
$this->paginate = array( 'Contestant' => array(
'conditions' => array( 'Contestant.active' => 1 ),
'order' => array( 'Contestant.vote_count' => 'DESC' ),
));
$contestants = $this->paginate('Contestants');
$this->set( compact('contestants'));
}
// Controller stuff that comes after...
}

Related

Yii: A HAS_MANY B, B HAS_MANY C, find count of C belonging to A

Basically, I have 3 tables.
house_table
===========
house_id
floor_table
===========
floor_id
house_id
room_table
===========
room_id
floor_id
is_occupied
A house has many floor, a floor has many rooms. A room belongs to one floor, a floor belongs to one house. The corresponding models created automatically by gii are HouseTable, FloorTable, RoomTable.
What I need is to findAll() houses that have rooms that are not occupied.
How do I do that? Something like this?
class HouseRecord extends CActiveRecord {
public function relations() {
return array(
'FREE_ROOM_COUNT' => array(self::STAT ...???...),
);
}
}
Sure, I could do it with SQL, but it needs to be done this way, because the result of findAll() is used as a data provider in a grid.
UPDATE
Following tinybyte's advice, here's what finally worked.
public function relations() {
return array(
'FREE_ROOM_COUNT' => array(
self::STAT ,
'FloorTable',
'house_id',
'select' => 'COUNT(rt.floor_id)',
'join' => 'INNER JOIN room_table rt ON t.floor_id = rt.floor_id',
'condition' => 'rt.is_occupied = 0',
),
);
}
And to be used as so:
$criteria = new CDbCriteria;
$criteria->with = array('FREE_ROOM_COUNT');
$criteria->together = true;
$provider = new CActiveDataProvider(HouseTable::model(), array(
'criteria'=>$criteria,
'pagination' => array(
'pageSize' => 1,
),
));
$this->widget('zii.widgets.CListView', array(
'dataProvider'=>$provider,
'itemView'=>'house',
));
Unfortunately it turns out one can not use these STAT relations as conditions! (Confirmed here: Using STAT relation in CActiveDataProvider criteria).
I think this should do the trick
'FREE_ROOM_COUNT' => array(
self::STAT ,
'Floor' ,
'house_id'
'select' => 'count(rt.floor_id)' , // or count(rt.room_id)
'join' => 'Inner join room_table rt ON Floor.floor_id = rt.floor_id' ,
),

Disabling order from belongsto array in just one function

I have a belongsTo array in my gal_provider model as below:
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => 'User.name'
),
)
And most of the functions inluded in the model gal_provider requires to sort the results according to the user names. But there is a function called recevtProviders() does not require this order. So I tried
$order = "";
$gal_providers = $this->find("all",array("conditions"=>$conditions,"recursive"=>$recursive,
"fields"=>$fields,"limit"=>$limit,"order"=>$order));
BUt the query generated still shows ORDER BY "User.name". How can I disable this order only from this function?
I believe the order parameter you're passing is only going to affect the gal_provider model. Try this just before your find call to remove the order from the associated model:
$this->belongsTo['User']['order'] = '';
Also, if you're not using it, Containable is a very useful behavior. Using containable, the order on the User model could be disabled like this:
$this->find("all",array("conditions"=>$conditions,"recursive"=>$recursive,
"fields"=>$fields,"limit"=>$limit,"order"=>$order,"contain" => array("User" => array("order" => ""))));

How to sort the fields from relation table in Yii?

I am trying to implement sorting with Yii list view. I have joined 2 tables named provider_favourite and service_request in list view. And the fields and contents from both tables are listing in the list view. But sorting is working only in provider_favourite table, not from service_request table.How can I sort the fields from service_request table? Iam using csort for sorting. I also tried CGrid view. But the same problem is happening in grid view also ..
Iam using the following code to join
$criteria = new CDbCriteria;
$criteria->select = 'favourite_notes,favourite, favourite_added_date,max_budget,preferred_location,service_name';
$criteria->join = 'LEFT JOIN service_request AS s ON service_request_id = favourite';
$criteria->condition = 'favourite_type = 1';
$sort=new CSort('ProviderFavourite');
// $sort->defaultOrder='s.max_budget ';
$sort->applyOrder($criteria);
$sort->attributes = array(
'max_budget' => 'service_request.max_budget',
'service_name' => 'service_request.service_name',
'favourite_added_date'
);
$type = 2;
$data = new CActiveDataProvider('ProviderFavourite', array('criteria' => $criteria, 'pagination' => array('pageSize' => 4),'sort'=>$sort
));
$this->renderPartial('favourites', array(
'ModelInstance' => ProviderFavourite::model()->findAll($criteria),
'dataProvider' => $data, 'type' => $type, 'sort'=>$sort,
));
and also Iam providing sortable attributes in list view
$this->widget('zii.widgets.CListView', array('dataProvider'=>$dataProvider,'itemView'=>'index_1',
'id'=>'request',
'template' => ' {items}{pager}',
'sortableAttributes'=>array('favourite_notes','max_budget','service_name')
));
If more details needed, I will provide. Thanks in advance
You have to specify attributes property of your $sort instance. By default only fields of $modelClass (ProviderFavourite in your case) are sortable.
I think it could look like this (not tested):
$sort->attributes = array(
'service_name' => array(
'asc' => 's.service_name ASC',
'desc' => 's.service_name DESC'
),
// ...another sortable virtual attributes from service_request table
"*"
);
You should not create a CSort object in $sort. The CActiveDataProvider will already provide you with the right sort object. The way you do it, you apply the sort criteria to $criteria before you configure the sort attributes. That can not work.
You should try a simple setup like this instead:
$data = new CActiveDataProvider('ProviderFavourite', array(
'criteria' => $criteria,
'pagination' => array('pageSize' => 4),
'sort'=> array(
'attributes' => array(
'service_name' => array(
'asc' => 's.service_name ASC',
'desc' => 's.service_name DESC'
),
// ...
),
));
If you need to access the related CSort object (which you usually don't if you use a CGridView or CListView, because they deal with that for you), then you get it via $data->sort.

yii CGridView show only logged-in user's records in many_many relation

I have two tables: user and asset and there is user_asset table that makes many_many relation
Now I want to show only the assets related with logged-in user in CGridView (user_id comes from Yii::app()->user->id)
How to write a criteria that makes it possible?
1) You need to define your relations in both of the model
user:
'assets' => array(self::MANY_MANY, 'Asset', 'user_asset(user_id, asset_id)'),
asset:
'users' => array(self::MANY_MANY, 'Asset', 'user_asset(asset_id, user_id)'),
2) You Create a dataProvider that will fetch only the recquired datas (define it in a model (best) or a controller bu not in a view):
$dataProvider=new CActiveDataProvider('Asset', array(
'criteria'=>array(
'with'=>array(
'users'=>array(
'condition' => 'id = ' . Yii::app()->user->id
),
),
),
...
));
3) You give to your CGridView the good provider:
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$dataProvider,
...
));
The code is given as example, you should adapt it to your need!
I got it work, adding this inside the Model class's search method:
$criteria->with=array(
'users'
);
$criteria->together = true;
$criteria->condition = "users_users.user_id='".Yii::app()->user->id."'";
... and inside CActiveDataProvider declaration:
'criteria'=>array(
'with' => 'users',
'together'=>true,
'condition'=>'users_users.user_id = '.Yii::app()->user->id,
),
Maybe not the best way but does what needed. users_users is sql table alias by Yii, I got a hint from error message :)

Kohana ORM: Get results based on value of a foreign table

taxonomies
-id
-name
taxonomy_type
-taxonomy_id
-type_id
I've configured two models:
class Model_Taxonomy{
protected $_has_many = array('types'=>array());
}
class Model_Taxonomy_Type{
protected $_belongs_to = array('taxonomy' => array());
}
*Please note that taxonomy_type is not a pivot table.*
A taxonomy can have multiple types associated.
Then, what I'm trying to do is get all taxonomies that belong to a given type id.
This is would be the SQL query I would execute:
SELECT * FROM taxonomies, taxonomy_type WHERE taxonomy_type.type_id='X' AND taxonomies.id=taxonomy_type.taxonomy_id
I've tried this:
$taxonomies = ORM::factory('taxonomy')
->where('type_id','=',$type_id)
->find_all();
Obviously this doesn't work, but I can't find info about how execute this kind of queries so I have no clue.
class Model_Taxonomy{
protected $_belongs_to = array(
'types' => array(
'model' => 'Taxonomy_Type',
'foreign_key' => 'taxonomy_id'
)
);
}
class Model_Taxonomy_Type{
protected $_has_many = array(
'taxonomies' => array(
'model' => 'Taxonomy',
'foreign_key' => 'taxonomy_id'
)
);
}
And use some like that:
$type = ORM::factory('taxonomy_type')
->where('type_id', '=', $type_id)
->find();
if( ! $type->taxonomies->loaded())
{
types->taxonomies->find_all();
}
type_id column is a PK of taxonomy_type table, am I right?
So, you have one (unique) taxonomy_type record, and only one related taxonomy object (because of belongs_to relationship). Instead of your:
get all taxonomies that belong to a
given type id
it will be a
get taxonomy for a given type id