Get only distinct data - cakephp

Good day everyone, So I have this data on my database
ID PLATFORM CURRENCY_AFTER
10 0 JPY
10 0 JPY
10 0 JPY
and this is my query
$data = $this->UserStatusChangeLog->find('all', array(
'fields' => array(
'SUM(IF(UserStatusChangeLog.platform = 0, 1, 0)) AS pc',
'UserStatusChangeLog.currency_after'
),
'conditions' => $conditions,
'joins' => $joins,
'group' => array('UserStatusChangeLog.currency_after'),
)
);
The return from this query should be pc = 1 since it belongs only to 1 user. But my query return 3. I don't know how or where should I put a distinct on my query. HELP.

Related

Joins not used for complex HABTM search CakePhp 2

I have Contents, which can have Tags belonging to different TagGroups. I have a quite complex search condition which is as follows:
A Content matches if it is tagged with at least one tag from the search as long as it belongs to the same tag group.
Example:
TagGroup 1 are colours, TagGroup2 are shapes.
So if a Content is tagged with "blue", "turquoise" and "rectangular" it will be found, when I search for "blue" and "rectangular"
However this example is only to show that the logic behind this is quite complex.
Content -> ContentsTag <- Tag -> TagGroup
I want to develop a search with paging of the results I had it working, but between refactoring and framework updates it is broken.
At some point I loose the information for the joins and so my SQL is crashing because it is missing tables.
array(
'limit' => (int) 10,
'order' => array(
'Content.objnbr' => 'asc'
),
'joins' => array(
(int) 0 => array(
'table' => 'sang_contents_tags',
'alias' => 'CT1', //join for the first TagGroup
'type' => 'INNER',
'conditions' => array(
(int) 0 => 'CT1.content_id = Content.Id'
)
),
(int) 1 => array(
'table' => 'sang_contents_tags',
'alias' => 'CT2', //join for the second TagGroup
'type' => 'INNER',
'conditions' => array(
(int) 0 => 'CT2.content_id = Content.Id'
)
)
),
'conditions' => array(
'AND' => array(
(int) 0 => array(
'OR' => array(
(int) 0 => array(
'CT1.tag_id' => '189' // chosen Tag 1 from the first TagGroup
)
)
),
(int) 1 => array(
'OR' => array(
(int) 0 => array(
'CT2.tag_id' => '7' // chosen Tag 2 from the second TagGroup
)
)
)
)
),
'contain' => array(
(int) 0 => 'Description',
'ContentsTag' => array(
'Tag' => array(
(int) 0 => 'Taggroup'
)
)
)
)
results in the following SQL:
SELECT `Content`.`id`, `Content`.`objnbr`, `Content`.`name`, `Content`.`imagecounter`, `Content`.`videolength`, `Content`.`money_maker`, `Content`.`comment`
FROM `my_db`.`contents` AS `Content`
WHERE ((`CT1`.`tag_id` = '189') AND (`CT2`.`tag_id` = '7'))
ORDER BY `Content`.`id` DESC
LIMIT 20
So clearly the Tags CT1 and CT2 are not joined and my sql is crashing.
Could it be that the contain is blocking the joins? If I unset the contain I still get the same result / error.
Any ideas?
Edit: To clarify, what I want to achieve:
The result should be a SQL statement like this:
SELECT `Content`.`id`, `Content`.`objnbr`, `Content`.`name`, `Content`.`imagecounter`, `Content`.`videolength`, `Content`.`money_maker`, `Content`.`comment`
FROM
`my_db`.`contents` AS `Content`
INNER JOIN
contents_tags AS CT1 ON CT1.content_id = Content.Id
INNER JOIN
contents_tags AS CT2 ON CT2.content_id = Content.Id
WHERE
((`CT1`.`tag_id` = '189')
AND (`CT2`.`tag_id` = '7'))
ORDER BY `Content`.`id` DESC
LIMIT 10
It looks like the trouble is caused by the pagination. If I do a "simple" find I get Contents based on the Tags:
$result = $this->Content->find('all', $this->paginate['Content']);
generated query by find:
SELECT
`Content`.`id`,
`Content`.`objnbr`,
`Content`.`name`,
`Content`.`imagecounter`,
`Content`.`videolength`,
`Content`.`money_maker`,
`Content`.`comment`
FROM
`my_db`.`contents` AS `Content`
INNER JOIN
`my_db`.`contents_tags` AS `CT0` ON (`CT0`.`content_id` = `Content`.`Id`)
INNER JOIN
`my_db`.`contents_tags` AS `CT2` ON (`CT2`.`content_id` = `Content`.`Id`)
WHERE
((`CT0`.`tag_id` = '56')
AND (`CT2`.`tag_id` = '7'))
ORDER BY `Content`.`objnbr` ASC
I did a research in the bowels of the pagination class and my conclusion is, that it simply is not able to work with the current Paginator, because I cannot pass on my special joins I need for this complex query.
A custom find type also will not help, because my query is too dynamic for that.
Should anybody prove me wrong I will be a happy coder.

I want Cakephp code in my query

I am Cakephp Beginner.
I want this query with cakephp..
Original Query..
SELECT `Advertisement`.*, `Gallery`.`image` FROM `moenatmi_moen`.`advertisements`
AS `Advertisement` LEFT JOIN `moenatmi_moen`.`galleries` AS `Gallery` ON
(`Gallery`.`advertise_id` = `Advertisement`.`id` AND `Gallery`.`main_ad_image`
= 1) WHERE `Advertisement`.`status` = 1 AND `Advertisement`.`category_id` = 14 AND
((`Advertisement`.`expiry_date` >= '2014-11-18') OR (`Advertisement`.`expiry_date`
IS NULL)) GROUP BY `Advertisement`.`id` ORDER BY `Advertisement`.`id` desc
LIMIT 10
I Want Like this query..
SELECT `Advertisement`.*, `Gallery`.`image` FROM `moenatmi_moen`.`advertisements`
AS `Advertisement` LEFT JOIN `moenatmi_moen`.`galleries` AS `Gallery` ON
(`Gallery`.`advertise_id` = `Advertisement`.`id` AND `Gallery`.`main_ad_image` = 1)
WHERE `Advertisement`.`status` = 1 AND `Advertisement`.`category_id` IN (select id from
categories where id=14 or parent_id=14) AND ((`Advertisement`.`expiry_date` >=
'2014-11-18') OR (`Advertisement`.`expiry_date` IS NULL)) GROUP BY
`Advertisement`.`id` ORDER BY `Advertisement`.`id` desc LIMIT 10
Original Cakephp Code..
$this->paginate['conditions'] = array(
'Advertisement.status'=>1,
'Advertisement.category_id'=>$cat_id,
'OR'=>array(
'Advertisement.expiry_date >='=>date('Y-m-d'),
'Advertisement.expiry_date'=>null
)
);
Help me..
Thanks You..
this should be a painless switch. This first query calls the category ids that you're wanting within your main query.
$cat_id is the same variable you're using in the original query.
$ids = $this->Category->find('all', array(
'conditions' => array(
'id' => $cat_id,
'parent_id' => $cat_id
)
)
);
The important thing to note here, this query will output your typical nested array..
array(
array(),
array(),
array()
);
The classicExtract function will sift through the mess for you and give you a single array with a list of 'id' which is exactly what you want for your query. EX:
array(14, 15, 16, 18, 19);
Finally, your main query.
$this->paginate['conditions'] = array(
'Advertisement.status'=>1,
'Advertisement.category_id'=> Set::classicExtract($ids, '{n}.Category.id'),
'OR'=>array(
'Advertisement.expiry_date >=' => date('Y-m-d'),
'Advertisement.expiry_date'=>null
)
);
Conclusion
All together your new conditions will look just like this:
$ids = $this->Category->find('all', array(
'conditions' => array(
'id' => $cat_id,
'parent_id' => $cat_id
)
)
);
$this->paginate['conditions'] = array(
'Advertisement.status'=>1,
'Advertisement.category_id'=> Set::classicExtract($ids, '{n}.Category.id'),
'OR'=>array(
'Advertisement.expiry_date >=' => date('Y-m-d'),
'Advertisement.expiry_date'=>null
)
);

CakePHP nested associations, empty records errors

I have few tables which are connected (hasMany) like a tree: 1 -> 2 -> 3.
There are few records in table 1, table 2 and 3.
I'm using CakePHP to fetch all data from table 1 with connected table 2, which is connected with table 3.
However few records in table 1 don't have any connected records in table 2. The same is in table 2, some records don't have connected records in table 3.
For the second situation scripts work fine. I get something like this:
1 -> 2 -> empty. But in the first situation, when data looks similar to: 1-> empty -> empty I get errors that table 3 doesn't exist.
Is there any solution to skip this errors and get pretty formatted association table as return to my query?
$options = array(
'conditions' => array(
'Table1.id' => $table1_ids
),
'contain' => array(
'Table2' => array(
'conditions' => array(
'id' => $table2_ids
),
'Table3' => array(
'conditions' => array(
'date_end >' => date('Y-m-d H:i:s')
),
'fields' => array('id'),
),
'fields' => array()
),
),
'fields' => array('id', 'name')
);
$this->Table1->recursive = -1;
$table1 = $this->Table1->find('all', $options);
It's not really cakephp to blame here but your lack of understanding what contain does.
What you're looking for are left joins, which is actually hinted in the documentation of contain.

Case Statement in CakePHP generates another array of the result

I have a simple select query using Case Statement in CakePHP.
$this->M->find('all', array(
'fields'=>array(
'M.id',
'(CASE WHEN M.check> 0 THEN
TRUNCATE((M.col1 + M.col2),2)
ELSE
TRUNCATE(M.col2, 2)
END) AS TOTAL'
)));
I'm trying to have this result.
(int) 0 => array(
'M' => array(
'id' => '200',
'TOTAL' => '1073.00'
)
)
But Cake Outputs
(int) 0 => array(
'M' => array(
'id' => '200',
),
(int) 0 => array(
'TOTAL' => '1073.00'
)
)
Does cake renders the result like this for the Case Statement or I miss out something?
You should be using
http://book.cakephp.org/2.0/en/models/virtual-fields.html
as documented.
$this->virtualFields['total'] = ...;
Then it would work as expected.

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