CakePHP database queries DISTINCT / GROUP BY error - php

In my CakePHP model I'm trying to get some data from my table.
I tried using DISTINCT but it seems like using DISTINCT doesn't change the query results.
I can see many rows that has the same nick
with 'DISTINCT Mytable.nick'
$this->Mytable->find('all',
array(
'fields'=> array(
'DISTINCT Mytable.nick',
'Mytable.age', 'Mytable.location',
),
'conditions' => array('Mytable.id >=' => 1, 'Mytable.id <=' => 100),
'order' => array('Mytable.id DESC')
));
with 'group Mytable.nick'
$this->Mytable->find('all',
array(
'fields'=> array(
'Mytable.nick',
'Mytable.age', 'Mytable.location',
),
'conditions' => array('Mytable.id >=' => 1, 'Mytable.id <=' => 100),
'group' => 'Mytable.nick',
'order' => array('Mytable.id DESC')
));
with 'Mytable.nick'
$this->Mytable->find('all',
array(
'fields'=> array(
'Mytable.nick',
'Mytable.age', 'Mytable.location',
),
'conditions' => array('Mytable.id >=' => 1, 'Mytable.id <=' => 100),
'order' => array('Mytable.id DESC')
));
Edit: It seems like even CakePHP 2.1 can't use DISTINCT option. When I tried "GROUP BY" it solved my issue. But as you can see from my query I need to order results with Mytable.id descended. When I use GROUP BY, when Mysql finds relevant row, it doesn't take others. For example.
id=1, nick=mike, age=38, location=uk
id=2, nick=albert, age=60, location=usa
id=3, nick=ash, age=42, location=uk
id=4, nick=albert, age=60, location=new_zelland
When I use group Mytable.nick, I don't see 4th row in my results, I see 2nd row. Because when mysql saw "albert" second time, it doesn't put it into my results. But I need latest "albert" result. Is it not possible?
Edit2: It seems like order by/group by conflict is a common problem. I found some tips in this question. But it gives solution for native Mysql queries. I need a solution for CakePHP type queries.

Not clear on why you want to group by nick and order by id. Do you intend to use an aggregate function like COUNT() to see how many occurrences of the same nick there are? In short you overall goal still is not clear to me. Might be worth being aware of the HAVING MySQL keyword.
Updated: Ok, that makes more sense. So you need to use a sub select on the condition or perhaps express that as a join. I'll try and show an example using the sub select in the WHERE clause.
/* select last occurrence for each nick (if you need one for each location )*/
SELECT nick, age, location
FROM myTable t1
WHERE id =
(SELECT MAX(id)
FROM myTable t1
WHERE t1.nick = t2.nick);
Would think something like this would work:
$this->Mytable->find('all',
array(
'fields'=> array(
'Mytable.nick',
'Mytable.age', 'Mytable.location',
),
'conditions' => array('Mytable.id =' => '(SELECT MAX(id) FROM myTable t2 WHERE myTable.nick = t2.nick)', 'Mytable.id <=' => 100)
));

Related

Checking if selected rows are favourite by user in cakephp

I have a Venue table with 20+ columns.
I also have a favorite_venues table with columns
id | userId | venueId
Here is my code to get venues
$this->Venue->virtualFields = array(
'bookedCount' => "SELECT count(*) FROM bookings WHERE venueId = Venue.id"
);
$result = $this->Venue->find('all', array('conditions'=>$conditions,
'order' => array('Venue.bookedCount DESC'),
'limit' => 20,
'offset' => $offset * 20
));
i want to add a condition if i send userId, it should check every venue if its added to favourite list or not and set
$venue['isFavorite'] = yes/no
I dont want a for loop and check every venue i get. is there any way i can incorporate in same Mysql query in cakephp. I am not sure how to put yes/no as virtual field
You can LEFT join in your favorite_venues table, and then for example use a CASE statement in a virtual field that checks whether there is a linked row.
Here's an untested example to illustrate what I mean. I don't know how the user ID is involved, so you got to figure that on your own.
$this->Venue->virtualFields = array(
'bookedCount' => "SELECT count(*) FROM bookings WHERE venueId = Venue.id",
'isFavorite' => 'CASE WHEN FavoriteVenues.id IS NOT NULL THEN "yes" ELSE "no" END'
);
$result = $this->Venue->find('all', array(
'conditions' => $conditions,
'order' => array('Venue.bookedCount DESC'),
'limit' => 20,
'offset' => $offset * 20,
'joins' => array(
array(
'table' => 'favorite_venues',
'alias' => 'FavoriteVenues',
'type' => 'LEFT',
'conditions' => array(
'FavoriteVenues.venueId = Venue.id',
)
)
),
// don't forget to group to prevent duplicate results
'group' => 'Venue.id'
));
See also
Cookbook > Models > Virtual Fields > Virtual fields set in controller with JOINS
Cookbook > Models > Associations: Linking Models Together > Joining Tables
MySQL 5.7 Reference Manual / SQL Statement Syntax / ... / CASE Syntax

data escaping remove for specific filed in cakephp

I am using subquery for id field.
$db = $this->AccountRequest->getDataSource();
$subQuery = $db->buildStatement(
array(
'fields' => array('MAX(id)'),
'table' => $db->fullTableName($this->AccountRequest),
'alias' => 'MaxRecord',
'limit' => null,
'offset' => null,
'order' => null,
'group' => array("user_id")
),
$this->AccountRequest
);
$searching_parameters = array(
#"AccountRequest.id IN " => "(SELECT MAX( id ) FROM `account_requests` GROUP BY user_id)"
"AccountRequest.id IN " => "(".$subQuery.")"
);
$this->Paginator->settings = array(
#'fields' => array('AccountRequest.*'),
'conditions' => $searching_parameters,
'limit' => $limit,
'page' => $page_number,
#'group' => array("AccountRequest.user_id"),
'order' => array(
'AccountRequest.id' => 'DESC'
)
);
$data = $this->Paginator->paginate('AccountRequest');
This structure is producing a query is:
SELECT
`AccountRequest`.`id`,
`AccountRequest`.`user_id`,
`AccountRequest`.`email`,
`AccountRequest`.`emailchange`,
`AccountRequest`.`email_previously_changed`,
`AccountRequest`.`first_name`,
`AccountRequest`.`first_namechange`,
`AccountRequest`.`f_name_previously_changed`,
`AccountRequest`.`last_name`,
`AccountRequest`.`last_namechange`,
`AccountRequest`.`l_name_previously_changed`,
`AccountRequest`.`reason`,
`AccountRequest`.`status`,
`AccountRequest`.`created`,
`AccountRequest`.`modified`
FROM
`syonserv_meetauto`.`account_requests` AS `AccountRequest`
WHERE
`AccountRequest`.`id` IN '(SELECT MAX(id) FROM `syonserv_meetauto`.`account_requests` AS `MaxRecord` WHERE 1 = 1 GROUP BY user_id)'
ORDER BY
`AccountRequest`.`id` DESC
LIMIT 25
In the subquery, its add an extra single quote so it's producing an error.
So, How can I remove these single quotes from this subquery?
Thanks
What are you trying to achieve with the sub query?
The MAX(id) just means it will pull the id with the largest value AKA the most recent insert. The sub query is completely redundant when you can just ORDER BY id DESC.
using MAX() will return only one record, if this is what you want to achieve you can replicate by adding LIMIT 1
If the sub query is just an example and is meant to be from another table I would just run the query that gets the most recent id before running the main query. Getting the last inserted id in a separate query is very quick and I cant see much of a performance loss. I think it will result in cleaner code that`s easier to follow to.
edit 1: From the comments it sounds like all your trying to get is a particular users latest account_requests.
You dont need the sub query at all. My query below will get the most recent account record for the user id you choose.
$this->Paginator->settings = array(
'fields' => array('AccountRequest.*'),
'conditions' => array(
'AccountRequest.user_id' => $userID // you need to set the $userID
)
'page' => $page_number,
'order' => array(
'AccountRequest.id DESC' //shows most recent first
),
'limit' => 1 // set however many you want the maximum to be
);
The other thing you cold be meaning is to get multiple entries from multiple users and display them in order of user first and then the order of recent to old for that user. MYSQL lets you order by more than one field, in that case try:
$this->Paginator->settings = array(
'conditions' => array(
'AccountRequest.user_id' => $userID // you need to set the $userID
)
'page' => $page_number,
'order' => array(
'AccountRequest.user_id', //order by the users first
'AccountRequest.id DESC' //then order there requests by recent to old
)
);
If the example data you have added into the question is irrelevant and you are only concerned about how to do nested subqueries it has already been answered here
CakePHP nesting two select queries
However I still think based on the data in the question you can avoid using a nested query.

Cakephp get all months records with a single query i.e with single find

I am working on a ecommerce with platform cakephp and using google charts for reports.My requirement is to get all records as per all 12 months, so I have used following code for a single month
Query
SELECT COUNT(*) AS count FROM orderproductmasters AS Orderproductmaster
LEFT JOIN ordermasters AS Ordermaster ON
(Orderproductmaster.ordermaster_id = Ordermaster.id) LEFT JOIN productmasters AS Productmaster ON
(Orderproductmaster.productmaster_id = Productmaster.id)
WHERE Ordermaster.orderstatusmaster_id = 1 AND Month(Orderproductmaster.created) = 8
Code
$this->Orderproductmaster->find('count',
array('conditions'=>array('Ordermaster.orderstatusmaster_id'=>1,'
Month(Orderproductmaster.created)'=>8)));
Since, I need records as per Jan, feb,march and all 12 months...,so for 12 months I am using following code
for($i=1;$i<13;$i++)
{
$orderproductmasters[$i] = $this->Orderproductmaster->find('count',
array('conditions'=>array('Ordermaster.orderstatusmaster_id'=>1,
'Month(Orderproductmaster.created)'=>$i)));
}
So question might be silly, but is it possible to get all months record without using for loop i.e, within a single query.
Thanks in advance
I think , your need can be fulfilled by using cursors in stored procedure. And then using stored procedure to cake-php.
Example on db side is here
$options = array();
$options['fields'] = array('COUNT(Orderproductmaster.id)');
$options['conditions'] = array('Ordermaster.orderstatusmaster_id = 1',
'Month(Orderproductmaster.created) BETWEEN 1 AND 12');
$options['joins'] = array(
array(
'table' => 'ordermasters',
'alias' => 'Ordermaster',
'type' => 'left',
'conditions' => array(
'Orderproductmaster.ordermaster_id = Ordermaster.id'
)
),
array(
'table' => 'productmasters',
'alias' => 'Productmaster',
'type' => 'left',
'conditions' => array(
'Orderproductmaster.productmaster_id = Productmaster.id'
)
)
);
$options['group'] => array('Month(Orderproductmaster.created)');
$this->Orderproductmaster->find('all',$options);
What About something like:
$this->Orderproductmaster->find('count',
array(
'fields'=>'DISTINCT(Month(Orderproductmaster.created)),
'conditions'=>array('Ordermaster.orderstatusmaster_id'=>1,'
Month(Orderproductmaster.created)'=>8)));

Paginate do not sort DESC

I can't display Agreements with agreement_number less than 7 and order it by agreement_number DESC.
I have read Pagination CakePHP Cook Book and can't find where my code is wrong. It display only less than 7, but always ASC. I have found similar question here, [that works],(CakePHP paginate and order by) and do not know why. Agreement.agreement_number is int(4).
$this->Agreement->recursive = 0;
$agreements = $this->Paginator->paginate('Agreement', array(
'Agreement.agreement_number <' => '7'
), array(
'Agreement.agreement_number' => 'desc'
)
);
$this->set('agreements', $agreements);
}
Exact cake version is 2.5.2.
... Where did you read that that was the correct syntax?
The paginate function's third parameter is for sorting (and I mean, within the table... with those down and up arrows).
List of allowed fields for ordering. This allows you to prevent
ordering on non-indexed, or undesirable columns.
You have the exact link used for documentation of the API, but you don't seem to be following it (like, from here and here)
$this->Paginator->settings = array(
'Agreement' => array(
'order' => array('Agreement.agreement_number' => 'desc')
)
);
$agreements = $this->Paginator->paginate('Agreement', array(
'Agreement.agreement_number <' => '7'));

How to get data from different associated tables using containable in Cakephp?

I have 3 tables: orders, discounts and products in the way that product has many discounts (discount times). Discount has many orders. in other words, it looks like: Product > Discount > Order.
I want to get data from 3 tables as the raw mySQL query below:
SELECT discounts.product_id, products.product_name,
sum(products.product_price - discounts.product_discount) as total_Amount,
count(orders.order_id) as total_Number
FROM products
inner join discounts on products.product_id = discounts.product_id
inner join orders on discounts.discount_id = orders.discount_id
group by discounts.product_id,products.product_name
This is what I did:
$this->Order->virtualFields['benefit']='SUM(Product.product_price - Discount.product_discount)';
$this->Order->virtualFields['number']='COUNT(Order.order_id)';
$products = $this->Order->find('all',array('contain'=>array('Discount'=>array('Product'))),
array( 'limit'=>20,
'fields'=>array('benefit','number'),
'group'=>array('Discount.product_id','Product.product_name')));
Debugger::dump($products);
$this->set('products',$products);
But I got an error:
Database Error
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Product.product_price' in 'field list'
SQL Query: SELECT `Order`.`order_id`, `Order`.`user_id`
`Order`.`order_date`, `Order`.`payment`, `Order`.`discount_id`, `Order`.`total`,
(SUM(`Product`.`product_price` - `Discount`.`product_discount`)) AS `Order__benefit`,
(COUNT(`Order`.`order_id`)) AS `Order__number`, `Discount`.`discount_id`,
`Discount`.`product_id`, `Discount`.`product_discount`,
`Discount`.`start_time`, `Discount`.`end_time`
FROM `project`.`orders` AS `Order`
LEFT JOIN `project`.`discounts` AS `Discount`
ON (`Order`.`discount_id` = `Discount`.`discount_id`) WHERE 1 = 1
It seems that containable didnt work as it didnt not contain products table in the query.
EDIT: according to Dave's suggestion, I used JOIN:
$this->Order->recursive=-1;
$this->Order->virtualFields['benefit']='SUM(Product.product_price - Discount.product_discount)';
$this->Order->virtualFields['number']='COUNT(Order.order_id)';
$option['joins'] = array(
array('table'=>'discounts',
'alias'=>'Discount',
'type'=>'INNER',
'conditions'=>array(
'Order.discount_id = Discount.discount_id',
)
),
array('table'=>'products',
'alias'=>'Product',
'type'=>'INNER',
'conditions'=>array(
'Discount.product_id = Product.product_id'
)
)
);
$products = $this->Order->find('all',$option,
array( 'limit'=>20,
'fields'=>array('Discount.product_id','Product.product_name'),
'group'=>array('Discount.product_id','Product.product_name')));
Debugger::dump($products);
$this->set('products',$products);
However, what $products contains is only:
array(
(int) 0 => array(
'Order' => array(
'order_id' => '23567636',
'user_id' => '1',
'order_date' => '2013-11-16 16:03:00',
'payment' => 'mc',
'discount_id' => '2',
'total' => '599',
'benefit' => '7212',
'number' => '19'
)
)
)
but, what I want is:
How can I fix that? thanks in advance.
Containable is not the same as JOIN.
Containable does not join the queries into a single query, but for the most part creates completely separate queries, then combines the results for your viewing pleasure.
So - per your error, in the query that's being run on the orders table, there IS no Product.product_price field because those fields are available only in a completely separate query.
Try using JOINs instead.

Categories