cakephp paginate with group and group count - cakephp

I have a db table with items with a matching group_id. I'd like to display those items in a paginator like this:
codeset_id - itemsInGroupCount
group1 - 3
group2 - 6
How can I do this? I have the distinct groups but how do I get the count?
My Controller:
public function admin_index() {
$this->Codesetitem->recursive = 0;
$this->paginate = array(
'Codesetitem' => array(
'group' => 'codeset_id',
'order' => array('codeset_id' => 'asc')
)
);
$this->set('codesetitems', $this->paginate());
}

The following seems to work on my side:
public $components = array('Paginator');
public function index() {
$this->Paginator->settings = array(
'fields' => array('Item.group_id', 'COUNT(*) AS "Item.group_count"'),
'limit' => 3,
'group' => 'group_id'
);
$this->set('items', $this->Paginator->paginate('Item'));
}
I simply added COUNT(*) AS "Foo.bar" to the fields key.

Related

CakePHP: How to make the paginator component use distinct counting?

I am making simple pagination which using this code:
$paginate = array(
'limit' => 30,
'fields' => array('DISTINCT Doctor.id','Doctor.*'),
'order' => array('Doctor.id' => 'desc'),
'joins' => array(
array('table' => 'doctors_medical_degrees',
'alias' => 'DoctorsMedicalDegree',
'type' => 'INNER',
'conditions' => array(
'Doctor.id = DoctorsMedicalDegree.doctor_id',
)
),
),
'recursive' => -1,
);
$this->Paginator->settings = $paginate;
$data = $this->Paginator->paginate('Doctor');
Now the problem is I am using Inner join so for Distinct result I am using Distinct Doctor.id, but the cakephp when doing query for pagination the count query not including Distinct Doctor.id
'query' => 'SELECT COUNT(*) AS `count` FROM `pharma`.`doctors` AS `Doctor` INNER JOIN `pharma`.`doctors_medical_degrees` AS `DoctorsMedicalDegree` ON (`Doctor`.`id` = `DoctorsMedicalDegree`.`doctor_id`)'
as you can see No
COUNT(DISTINCT Doctor.id)
so pagination return more number of result which it can actually return for
The problem is that the paginator doesn't pass the fields to the find('count') call, so by default it will always count on *.
But even if it would pass the fields, passing an array would make the find('count') call expect that the field to count is passed as a COUNT() expression, ie something like
'fields' => array('COUNT(DISTINCT Doctor.id) as `count`')
However that won't work with the paginator anyways, so what you need is a customized find('count') call.
Custom query pagination to the rescue
See Cookbook > Pagination > Custom Query Pagination for more information.
Custom query pagination is probably your best bet, that way it's totally up to you how counting is being done.
For example you could make use of the extra values passed by the paginator component, that way you could pass the field to count on to the find('count')` call, something like this (untested example code):
class Doctor extends AppModel {
// ...
public function paginateCount($conditions = null, $recursive = 0, $extra = array()) {
$parameters = compact('conditions');
if($recursive != $this->recursive) {
$parameters['recursive'] = $recursive;
}
if(!empty($extra['countField'])) {
$parameters['fields'] = $extra['countField'];
unset($extra['countField']);
}
return $this->find('count', array_merge($parameters, $extra));
}
}
$this->Paginator->settings = array(
'limit' => 30,
'fields' => array('DISTINCT Doctor.id','Doctor.*'),
// ...
'countField' => 'DISTINCT Doctor.id'
);
$data = $this->Paginator->paginate('Doctor');
This should then create a COUNT query that looks like
SELECT COUNT(DISTINCT Doctor.id) AS `count` ...
I found the solution on CakePHP - Pagination total count differs from actual count when using DISTINCT
Add the public function paginateCount in your model and use the distinct option in your paginator like:
$this->paginate = array('limit' => $limit, 'order' => array('Item.created' => 'ASC', 'Item.code' => 'ASC'),
'fields' => 'DISTINCT Item.*',
'conditions' => $conditions,
'countField' => array('Item.id'),
'joins' => $joins,
'distinct' => 'Item.id',
'contain' => array(
'Image' => array('limit' => 1),
'ItemsTag'
)
);

CakePHP HABTM queries

What's the CakePHP way to do such queries?
SELECT news.* FROM news, users
INNER JOIN news_users nu ON nu.user_id = users.id
WHERE users.id = $user_id
I have a link table news_users, so trying with something like $news = $this->News->findByUserId($this->User->id); does not work, because it looks for news.user_id
P.S. The above query works, I just desire to make the script shorter.
You can declare findByUserId function in your News model:
//put this code in your News model
public function findByUserId($user_id = null)
{
return $this->find('all', array(
'conditions' => array(
'NewsUser.user_id' => $user_id
),
'joins' => array(
array(
'table' => 'news_users',
'alias' => 'NewsUser',
'type' => 'INNER',
'conditions' => array(
'NewsUser.news_id = News.id'
)
)
)
));
}
Then you can use your findByUserId function enywhere in your NewsController
//this code in NewsController
$news = $this->News->findByUserId($this->User->id);

how to pass raw sql query into paginator component in cakephp

I have row sql query and i want to paginate this query with the help of cakephp paginator component.How could i ?
$q = "SELECT
`UserList`.`id`,`UserList`.`user_id`,`UserList`.`list_user_id`,
`User`.`username` ,`User`.`id` ,
`UserDetail`.`sex`,`UserDetail`.`image`,`UserDetail`.`display_name`,`UserDetail`.`country`,`UserDetail`.`height`,`UserDetail`.`weight`,`UserDetail`.`hair_color`,`UserDetail`.`eye_color`
from `user_lists` as `UserList`
inner join `users` as `User` on `UserList`.`list_user_id` = `User`.`id`
inner join `user_details` as `UserDetail` on `UserList`.`list_user_id` = `UserDetail`.`user_id`
where ((`UserList`.`user_id`=".$user_id.") and (`UserList`.`in_list`='short')) order by `UserList`.`created` desc limit 1";
$this->paginate = array(
'conditions' => /*Pass $q here */,
'limit' => 15,
)
);
thanks in advance.
You don't
That just doesn't work, from the query in the question though, all you need is to use pagination as normal:
class UserListsController extends AppController {
public $paginate = array(
'contain' => array(
'User',
'UserDetail'
)
);
public function index($userId = null) {
$conditions = array(
'user_id' => $userId,
'in_list' => 'short'
);
$data = $this->paginate($conditions);
$this->set('data', $data);
}
}

Cakephp IN(x,x) with 'AND'

I am struggeling with a custom find in cakephp 2.1.
In my model I have this function:
public function findByGenres($data = array()) {
$this->Item2genre->Behaviors->attach('Containable', array('autoFields' => false));
$this->Item2genre->Behaviors->attach('Search.Searchable');
$query = $this->Item2genre->getQuery('all', array(
'conditions' => array('Genre.name' => $data['genre']),
'fields' => array('item_id'),
'contain' => array('Genre')
));
return $query;
}
This returns the following query:
SELECT `Item`.`id` FROM `items` AS `Item`
WHERE `Item`.`id`
IN(SELECT `Item2genre`.`item_id` FROM `item2genre` AS Item2genre
LEFT JOIN `genres` AS Genre ON(`genre_id` = `Genre`.`id`)
WHERE `Genre`.`name`
IN ('Comedy', 'Thriller')
)
The result of the query returns Items with either 'Comedy' or 'Thriller' genre associated to them.
How can I modify the query to only return Items with 'Comedy' AND 'Thriller' genre associated to them?
Any suggestions?
edit:
content of data is:
'genre' => array(
(int) 0 => 'Comedy',
(int) 1 => 'Thriller'
)
You would want your 'conditions' key to be this:
'conditions' => array(
array('Genre.name' => 'Comedy'),
array('Genre.name' => 'Thriller')
)
So specifically to your problem your $data['genre'] is array('Comedy', 'Thriller'). So you could create a variable that has contents similar to what you need by doing:
$conditions = array();
foreach ($data['genre'] as $genre) {
$conditions[] = array('Genre.name' => $genre);
}

Model group and order

I have Order model, which belogns to User model by field user_id.
Now I want to get 1 OLDEST order per each user. For example:
id = 0, user_id = 1, created = 2012-10-10 20:36:42
id = 1, user_id = 1, created = 2012-10-10 19:36:42
id = 2, user_id = 1, created = 2012-10-10 21:36:42
id = 3, user_id = 2, created = 2012-10-10 22:36:42
id = 4, user_id = 2, created = 2012-10-10 21:36:42
id = 5, user_id = 2, created = 2012-10-10 23:36:42
So result should be:
id = 1, user_id = 1, created = 2012-10-10 19:36:42
id = 4, user_id = 2, created = 2012-10-10 21:36:42
I am using following find options for now:
array(
'group' => 'user_id',
'order' => array('MIN(created) DESC'),
)
and also tried with 'order' => array('created DESC')
but it doesn't work - it seems that GROUPING orders is done always before ORDERING.
How I can force the find method to order records by created field before grouping?
You can use the HAVING clause to filter the group. To construct the query with CakePHP, you'll need to include the clause in the ORDER clause.
$this->Order->find('all', array(
'group' => 'user_id HAVING created = MIN(created)',
'order' => array('created DESC')
));
I would use CakePHP's Containable Behavior:
//User model
public $actsAs = array('Containable');
public function oldestOrderPerUser() {
$this->recursive = -1;
$this->find('all', array(
'contain' => array(
'Order' => array(
'order' => array('created asc'),
'limit' => 1
)
)
));
}
This finds all users (feel free to add 'conditions' to limit it to specific user(s), and gets the first/oldest order for each user.
Your returned data will look something like this:
0 => array(
'User' => array(
'id' => 1,
'name' => 'John Doe',
'email' => 'johndoe#awesome.com',
'Order' => array(
0 => array(
'id' => '4',
'user_id' => 1,
'created' => '2012-10-10 19:36:42'
)
)
)
1 => array(
'User' => array(
...
You can limit what fields are returned in each User and Order, set conditions on either/both, limit either/both...etc etc.
I recommend putting $this->recursive=-1; in the AppModel to set it as the default - if you do that, you don't have to specify it before each query or in each model...etc. And using anything other than -1 is bad practice anyway, so... works well.
I also set public $actsAs = array('Containable'); in my AppModel.
I can't test it now, but try this way :
$this->Order->find('all', array(
'fields' => array('MIN(Order.Created) as min_created'),
'group' => 'user_id',
'order' => array('min_created DESC')
));

Resources