CAKEPHP Group by problem in paginate - cakephp

CAKEPHP Group by problem in paginate..
This is my table structure
Friends Table
`id` int(11) NOT NULL AUTO_INCREMENT,
`user1_id` int(11) NOT NULL DEFAULT '0',--> UserFrom
`user2_id` int(11) NOT NULL DEFAULT '0',---> UserTo
I need to get all the unique records of one user and his user1_id = 100
There are lot of duplicate values in user2_id. I need to get the unique values
While i trying this code it returns only first 12 values(according to limit).
If i commented the group by line then all records are displaying (including duplicate values)
$this->paginate = array(
'conditions' => $conditions,
'contain'=>array(
'UserFrom'=>array(
'fields'=>array(
'UserFrom.id',
'UserFrom.first_name',
'UserFrom.last_name',
),
),
'UserTo'=>array(
'fields'=>array(
'UserTo.id',
'UserTo.first_name',
'UserTo.last_name',
)
)
),'limit' => 12,
'order' => array('Friend.id' => 'desc'),
'recursive'=>0,
'fields'=>array('DISTINCT Friend.user2_id','Friend.*'),
'group'=>array('UserTo.id'),
);
This is my sql query on that page
SELECT COUNT(*) AS count FROM friends AS Friend LEFT JOIN users AS UserTo ON (Friend.user2_id = UserTo.id) LEFT JOIN users AS UserFrom ON (Friend.user1_id = UserFrom.id) WHERE UserFrom.nick_name = 'shyam' AND Friend.status = 'friend' GROUP BY UserTo.id
SELECT DISTINCT Friend.user2_id, Friend.*, UserTo.id, UserTo.first_name, UserTo.last_name, UserTo.nick_name, UserTo.name, UserTo.icon_medium, UserTo.icon_big, UserFrom.id, UserFrom.first_name, UserFrom.last_name, UserFrom.nick_name, UserFrom.name FROM friends AS Friend LEFT JOIN users AS UserTo ON (Friend.user2_id = UserTo.id) LEFT JOIN users AS UserFrom ON (Friend.user1_id = UserFrom.id) WHERE UserFrom.nick_name = 'shyam' AND Friend.status = 'friend' GROUP BY UserTo.id ORDER BY Friend.id desc LIMIT 12
1.First count query returning count properly
2.Second query if i put limit it not working properly. i.e there are 144 records in my table i need to display 12 per page.. only first page coming if i use group by and limit

You should have either DISTINCT or GROUP BY, not both - they're duplicating each other and probably causing problems with your query. Remove one of them and retry.
Note: If you want to manually request pages other than 1st with $this->paginate(), add 'page' parameter with page number to your $this->paginate array, like this:
$this->paginate = array_merge($this->paginate, array('page' => $pageNumber);
However, normally you wouldn't want to do this, as paginator does this automatically (passing page variable via URL parameter to your action) as long as you use pagination controls in your view (see http://book.cakephp.org/view/1233/Pagination-in-Views).

Related

UpdateAll is not working with bigint() fields

I'm quite new to CakePHP and have an issue. I'm trying to update one field in a row with this:
$options = array('conditions' => array('User.id' => $_POST['userid']));
$user = $this->User->find('first', $options);
$currentpoint = $user['User']['point'];
$correctanswerpoint = Configure::read('CORRECT_SYSTEMANSWER_POINT');
$newpoint = $currentpoint + $correctanswerpoint;
$this->User->updateAll(array('User.point'=>$newpoint), array('User.id'=>$_POST['userid']));
There are no errors, but the field point (bigint(20)) is not being updated. I've changed the field to update to another one in the same row which is of smallint type and the update goes through fine.
My debugging log seems to show that the SQL query is ok too:
[query] => UPDATE `askyoode_askyoo`.`users` AS `User` LEFT JOIN `askyoode_askyoo`.`countries` AS `Country` ON (`User`.`country_id` = `Country`.`id`) LEFT JOIN `askyoode_askyoo`.`accesstypes` AS `Accesstype` ON (`User`.`accesstype_id` = `Accesstype`.`id`) SET `User`.`point` = 57 WHERE `User`.`id` = 124
[params] => Array
(
)
[affected] => 1
[numRows] => 1
[took] => 12
)
And manually running the same SQL statement in MySQL works. Been tearing out my hair for the whole day trying to resolve this. Any help is greatly appreciated. Thanks!!

findall ignoring order parameter?

I'm trying to use the following:
$this->Chapter->recursive=1;
$chaps = $this->Chapter->find('all', array(
'order'=> array('sequence_number' => 'ASC')
));
$this->set('chapters', $chaps );
to retrieve all my chapters by increasing order, but CakePHP seems to be ignoring the 'order' parameter. I believe that I have the syntax correct (based on view-source:http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#creating-custom-find-types, which says the following should work:
public function index() {
$articles = $this->Article->find('available', array(
'order' => array('created' => 'desc')
));
}
). The SQL for the table looks like:
CREATE TABLE chapters (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
sequence_number INT UNSIGNED,
title VARCHAR(50)
);
and I'm not getting any syntax or run-time errors. However, the SQL generated by Cake to actually get the chapter records is:
SELECT `Chapter`.`id`, `Chapter`.`sequence_number`, `Chapter`.`title`
FROM `Tutorial`.`chapters` AS `Chapter` WHERE 1 = 1
Clearly I'm doing something wrong, but I don't know what it is.
As a work-around I'm happy to put an order property on the Model. Since I typically want to retrieve chapters by sequence number I'm fine with adding this to the model:
public $order = 'Chapter.sequence_number ASC';
Once I do that Cake generates
SELECT `Chapter`.`id`, `Chapter`.`sequence_number`, `Chapter`.`title`
FROM `Tutorial`.`chapters` AS `Chapter` WHERE 1 = 1
ORDER BY `Chapter`.`sequence_number` ASC
What if you try something like this, instead:
$chaps = $this->Chapter->find('all', array(
'order' => array('Chapter.sequence_number ASC')
));
The main difference being this part: Chapter.sequence_number ASC

way for using DATE_SUB(CURDATE(),INTERVAL 2 DAY) condition in cakephp

i am using cakephp and creating this condition:
$conditions = array("Userword.levelid"=>0, "Userword.userid"=>$this->Auth->user('UserId'),"date(Userword.date)"=>"DATE_SUB(CURDATE(),INTERVAL 0 DAY)");
$Userword = $this->paginate(null, $conditions);
cake using this select :
`Userword`.`userwordid`, `Userword`.`userid`, `Userword`.`wordid`, `Userword`.`date`, `Userword`.`levelid`, `Word`.`wordid`, `Word`.`word`, `Word`.`meaning`, `User`.`UserId`, `User`.`Email`, `User`.`password`, `User`.`username`, `User`.`cell`, `User`.`web` FROM `userwords` AS `Userword` LEFT JOIN `words` AS `Word` ON (`Userword`.`wordid` = `Word`.`wordid`) LEFT JOIN `users` AS `User` ON (`Userword`.`userid` = `User`.`UserId`) WHERE date(`Userword`.`date`) = 'DATE_SUB(CURDATE(),INTERVAL 2 DAY)'
but by this select mysql can't return record because cakephp in this select use this character (') in last passage.
when i remove this character and test this code in phpmyadmin this code return rows. two images in below show this :
Just don't separate it out as key => value pair:
$conditions = array(
...
'DATE(Userword.date) = DATE_SUB(CURDATE(), INTERVAL 0 DAY)'
);

Paginate results filtered by condition on associated model (HABTM) using Containable

I need to paginate list of Products belonging to specific Category (HABTM association).
In my Product model I have
var $actsAs = array('Containable');
var $hasAndBelongsToMany = array(
'Category' => array(
'joinTable' => 'products_categories'
)
);
And in ProductsController
$this->paginate = array(
'limit' => 20,
'order' => array('Product.name' => 'ASC'),
'contain' => array(
'Category' => array(
'conditions' => array(
'Category.id' => 3
)
)
)
);
$this->set('products', $this->paginate());
However, resulting SQL looks like this:
SELECT COUNT(*) AS `count`
FROM `products` AS `Product`
WHERE 1 = 1;
SELECT `Product`.`*`
FROM `products` AS `Product`
WHERE 1 = 1
ORDER BY `Product`.`name` ASC
LIMIT 20;
SELECT `Category`.`*`, `ProductsCategory`.`category_id`, `ProductsCategory`.`product_id`
FROM `categories` AS `Category`
JOIN `products_categories` AS `ProductsCategory` ON (`ProductsCategory`.`product_id` IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) AND `ProductsCategory`.`category_id` = `Category`.`id`)
WHERE `Category`.`id` = 3
(I.e. it selects 20 Products and then queries their Categories)
while I'd need
SELECT COUNT(*) AS `count`
FROM `products` AS `Product`
JOIN `products_categories` AS `ProductsCategory` ON `ProductsCategory`.`product_id` = `Product`.`id`
JOIN `categories` AS `Category` ON `Category`.`id` = `ProductsCategory`.`category_id`
WHERE `Category`.`id` = 3;
SELECT `Product`.*, `Category`.*
FROM `products` AS `Product`
JOIN `products_categories` AS `ProductsCategory` ON `ProductsCategory`.`product_id` = `Product`.`id`
JOIN `categories` AS `Category` ON `Category`.`id` = `ProductsCategory`.`category_id`
WHERE `Category`.`id` = 3
ORDER BY `Product`.`name` ASC
LIMIT 20;
(I.e. select top 20 Products which belong to Category with id = 3)
Note:
Possible solution without Containable would be (as Dave suggested) using joins.
This post offers a very handy helper to build $this->paginate['joins'] to paginate over HABTM association.
Note: Still looking for more elegant solution using Containable than fake hasOne binding.
Finally I found a way to do what I want, so posting it as an answer:
To force JOIN (and be able to filter via condition on associated model) in Containable - you've got to use fake hasOne association.
In my case, code in ProductsController should be:
$this->Product->bindModel(array('hasOne' => array('ProductsCategory')), false);
$this->paginate = array(
'limit' => 20,
'order' => array('Product.name' => 'ASC'),
'conditions' => array(
'ProductsCategory.category_id' => $category
),
'contain' => 'ProductsCategory'
);
$this->set('products', $this->paginate());
Note false as a second argument to bindModel - which makes binding persistent. This is needed because paginate() issues find('count') before find('all'), which would reset temporary binding. So you might want to manually unbindModel afterwards.
Also, if your condition includes multiple IDs in HABTM associated model, you might want to add 'group' => 'Product.id' into your $this->paginate[] (as Aziz has shown in his answer) to eliminate duplicate entries (will work on MySQL only).
UPDATE:
However, this approach has one serious drawback compared to joins approach (suggested by Dave): condition can apply only to intermediate model's foreign key (category_id in my case); if you want to use condition on any other field in associated model - you'd probably have to add another bindModel('hasOne'), binding intermediate model to HABTM associated model.
When you put the condition in the nested Contain, you're asking it to retrieve only the Categories with that ID. So - it's doing what you're asking, but that's not what you want.
Though it seems like it should be possible, the only luck I've had doing what you're trying to do (after MANY hours and a few stackoverflow questions) is via Joins instead of Contain.
http://book.cakephp.org/view/1047/Joining-tables
It's not the exact same problem, but you can go through some of my code where I query against HABTM conditions (I answered my question at the bottom) here: Select All Events with Event->Schedule->Date between start and end dates in CakePHP

CakePHP 3 find() with beforeFind() callback SQL issue

I'm having a problem with data integrity when using find() in my controller in conjunction with beforeFind() in a behavior callback. The WHERE Submissions.site_id is not being added in the WHERE clause like it should be. I get different result sets depending on where the WHERE clause is set.
in my SubmissionsController:
public function index()
{
$query = $this->Submissions->find('all')
->where(['user_id' => $this->Auth->user('id')])
->contain(['Users', 'Categories']);
$this->set('submissions', $this->paginate($query));
}
In my beforeFind() Model callback (attached as a 'TenantBehavior' to
$query->where([$this->_table->alias().'.'.'site_id' => 3]);
The problem is that with the above, the SQL generated puts the "WHERE" clause as an AND on the JOIN condition like so, and NOT on the actual WHERE:
...
FROM
submissions Submissions
INNER JOIN users Users ON (
Users.id = (Submissions.user_id)
AND Users.site_id = 3
)
INNER JOIN categories Categories ON (
Categories.id = (Submissions.category_id)
AND Categories.site_id = 3
)
WHERE
user_id = 315
If I remove the beforeFind() ->where and instead place it on the controller ->where I get the expected SQL and result set like so:
...
FROM
submissions Submissions
INNER JOIN users Users ON (
Users.id = (Submissions.user_id)
AND Users.site_id = 3
)
INNER JOIN categories Categories ON (
Categories.id = (Submissions.category_id)
AND Categories.site_id = 3
)
WHERE
(
user_id = 315
AND Submissions.site_id = 3
)
Thoughts? Suggestions?
EDIT
As #ndm's suggestion, I began to update and provide much more context. In doing so I discovered (like an idiot) that I was missing the $this->addBehavior('Tenant'); on my 'SubmissionsTable' model. Adding this of course solved the issue.
$query = $this->Submissions->find('all', [
'conditions' => [
'user_id' => $this->Auth->user('id')
],
'contain' => [
'Users',
'Categories',
]
]);
Passing conditions as inline array will solve your issue and append beforeFind() where conditions properly.

Resources