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

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.

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

Joins using paginate in cakephp

I have a project that i have inherited and was poorly designed and i am just trying to bring some sanity into it. Here is the issue i am having. I have a table that stores projects, contacts and another table that stores writers attached to that project(although there are still more tables). I have managed to write the below script.
$conditions = $this->paginate = array('joins' => array(
'table' => 'wp3_order_writers',
'alias' => 'OrderWriter',
'type' => 'LEFT',
'conditions' => array(
'OrderWriter.order_id=Order.id'
)
),
'conditions' => array(
array('Order.status_id' => array(5,6,7,8),
'OR' => array('Order.assigned_to_writer' => $this->_user['Contact']['id'],
'OrderWriter.writer_id' => $this->_user['Contact']['id'],
)
),
),
'fields' => array(
'Order.id', 'Order.urgent', 'Order.writer_deadline', 'Order.subject_title', 'Client.name', 'Client.email', 'Service.*', 'Order.writer_fee', 'Order.count_of_words', 'Status.*', 'Order.pay_status', 'Subject.*'
)
);
$this->paginate['limit'] = 100000;
$orders = $this->paginate($this->Order);
When i check the sql script generated it contains error. Below is what is generated
SELECT `Order`.`id`, `Order`.`urgent`, `Order`.`writer_deadline`, `Order`.`subject_title`, `Client`.`name`, `Client`.`email`, `Service`.*,`Order`.`writer_fee`, `Order`.`count_of_words`, `Status`.*, `Order`.`pay_status`, `Subject`.* FROM `wp3_orders` AS `Order` wp3_order_writers OrderWriter LEFT Array LEFT JOIN `wp3_contacts` AS `Client` ON (`Order`.`client_id` = `Client`.`id`) LEFT JOIN `wp3_keys_values` AS `Service` ON (`Order`.`service_id` = `Service`.`id` AND `Service`.`key` = 'services') LEFT JOIN `wp3_keys_values` AS `PayMethod` ON (`Order`.`pay_method_id` = `PayMethod`.`id` AND `PayMethod`.`key` = 'pay_methods') LEFT JOIN `wp3_subjects` AS `Subject` ON (`Order`.`subject_id` = `Subject`.`id`) LEFT JOIN `wp3_keys_values` AS `Status` ON (`Order`.`status_id` = `Status`.`id` AND `Status`.`key` = 'statuses') LEFT JOIN `wp3_clients_sessions` AS `ClientsSession` ON (`ClientsSession`.`current_order_id` = `Order`.`id`) WHERE ((`Order`.`status_id` IN (5, 6, 7, 8)) AND (((`Order`.`assigned_to_writer` = 1087) OR (`OrderWriter`.`writer_id` = '1087')))) AND `Order`.`completed` = 1 LIMIT 100000
With this error "Warning (512): SQL Error: 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'wp3_order_writers OrderWriter LEFT Array LEFT JOIN wp3_contacts AS Client ON' at line 1 [CORE\cake\libs\model\datasources\dbo_source.php, line 684]"
Your help will be greatly appreciated
I would make it with Containable (assuming that you have models like Order, OrderWriter, Writer and defined relations inside)
Then your query will look like:
$this->paginate['Order'] = array('contain' => array('Writer'));
$this->paginate['conditions'] = array(...);
$this->paginate['fields'] = array(...);
$orders = $this->paginate($this->Order);
I am not sure if the Order section should be like above or:
$this->paginate['Order'] = array('contain' => array('OrderWriter'=>array('Writer')));
Also I would advise you don't use 100000 limit, since it's probably a way to skip the pagination, but then just use find() instead of paginate.
Although avoiding pagination will slow down the request, since it will make a query for each row of Orders, so having default limit (20) or some reasonable number should speed-up the site/app.

Cakephp custom query with left join table rows as nested arrays

I'm trying to get nested arrays for my Cakephp custom query below:
$this->query("
SELECT *
FROM group_buys GroupBuy
LEFT JOIN products Product
ON Product.id = GroupBuy.product_id
LEFT JOIN group_buy_users GroupBuysUser
ON GroupBuysUser.group_buy_id = GroupBuy.id
LEFT JOIN group_buy_images GroupBuyImage
ON GroupBuyImage.group_buy_id = GroupBuy.id
LEFT JOIN product_details ProductDetail
ON ProductDetail.product_id = Product.id
LEFT JOIN specifications Specification
ON Specification.id = ProductDetail.specification_id
LEFT JOIN specification_categories SpecificationCategory
ON SpecificationCategory.id = Specification.specification_category_id
WHERE GroupBuy.id = {$id}
");
Problem with this is that it comes up with redundant data obviously with GroupBuy table row values repeating which I don't want.
Is there a way we can have nested arrays if LEFT JOINED table has more rows than the former table with Cake's custom query?
I know this can be done with find recursive = 2 but would like to achieve this with custom query.
Have you tried using containable?
$this->GroupBuy->Behaviors->attach('Containable');
$this->GroupBuy->find('all', array(
'conditions' => array('GroupBuy.id' => $id),
'contain' => array(
'Product' => array(
'ProductDetail' => array(
'Specification' => array(
'SpecificationCategory'
)
)
),
'GroupBuysUser',
'GroupBuyImage'
),
));

CakePHP database queries DISTINCT / GROUP BY error

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)
));

Getting the column 'country_id' in 'where' clause is ambiguous - error in search query in cakephp 1.3

I am working with CakePHP 1.3 version for search functionality using Search Plugin.
I have three models:
Demo,
Country
State
Demo has two foreign keys, country_id and state_id. State has the foreign key country_id.
What I am doing is, I have search form which have country & state drop down which fetch all data from countries & states table. When i search any of country from dropdown & submit the form it will show me below error. If i search using only state dropdown i get the correct result.
When I execute the search query, I get the error
'Column 'country_id' in where clause is ambiguous'
My query is:
SELECT `Demo`.`id`, `Demo`.`demo2`, `Demo`.`desc`, `Demo`.`subject`, `Demo`.`gender`, `Demo`.`country_id`, `Demo`.`state_id`, `Demo`.`image_url`, `Country`.`id`, `Country`.`name`, `State`.`id`, `State`.`country_id`, `State`.`description` FROM `demos` AS `Demo` LEFT JOIN `countries` AS `Country` ON (`Demo`.`country_id` = `Country`.`id`) LEFT JOIN `states` AS `State` ON (`Demo`.`state_id` = `State`.`id`) WHERE `country_id` = 2
Model relationships in Demo table:
var $belongsTo = array(
'Country' => array(
'className' => 'Country',
'foreignKey' => 'country_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'State' => array(
'className' => 'State',
'foreignKey' => 'state_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
);
The controller query to fetch all Country in dropdown is:
$country=$this->Country->find('list'); //just display the list of country in dropdown
The query search the data from all fields except Country (country_id), because it will not know which country_id it is looking for from table Demo or table State. I need the country_id from the demo table to get the correct result.
As I understand you want to make a find over Demo for a specific country_id.
Well you should define which "country_id" you're using because more than one of those tables
has such a column.
Just use Demo.country_id in the conditions array:
array('conditions' => array('Demo.country_id' => 2));
And you should see some SQL generated by Cake like this:
SELECT `Demo`.`id`, `Demo`.`demo2`, `Demo`.`desc`, `Demo`.`subject`, `Demo`.`gender`, `Demo`.`country_id`, `Demo`.`state_id`, `Demo`.`image_url`, `Country`.`id`, `Country`.`name`, `State`.`id`, `State`.`country_id`, `State`.`description` FROM `demos` AS `Demo` LEFT JOIN `countries` AS `Country` ON (`Demo`.`country_id` = `Country`.`id`) LEFT JOIN `states` AS `State` ON (`Demo`.`state_id` = `State`.`id`) WHERE `Demo`.`country_id` = 2
Try this:
SELECT
Demo.id,
Demo.demo2,
Demo.desc,
Demo.subject,
Demo.gender,
Demo.country_id,
Demo.state_id,
Demo.image_url,
Country.id,
Country.name,
State.id,
State.country_id,
State.description
FROM demos AS Demo
LEFT JOIN countries AS Country ON (Demo.country_id = Country.id)
LEFT JOIN states AS State ON (Demo.state_id = State.id) WHERE Demo.country_id = 2

Categories