cakephp how to avoid the repeating columns in query result - cakephp

I have a query like
$p = $this->Products->findById( $id )
->select(['name', 'description', 'category_id', 's.name', 'pp.price'])
->join([
'table' => 'sizes',
'alias' => 's',
'type' => 'INNER',
'conditions' => 's.category_id = Products.category_id',
])
->join([
'table' => 'products_prices',
'alias' => 'pp',
'type' => 'LEFT',
'conditions' => 'pp.size_id = s.id AND pp.product_id = Products.id',
]);
The problem is if the product has 10 different sizes, 10 rows will be produced with repeating name, description, category_id elements
Is there a way to rewrite it so the size's name and prices are delivered as array as a sub-array?

Maybe just use Containable instead of Join? You can run a find() on your Products and contain Sizes and ProductPrices. This would get you the array/sub-array you're looking for.
The only down-side is that it would still pull products even if they don't have a matching size.
It's tough to tell what you're really going for, since you're joining on category_id, which isn't really referenced. Maybe if you explain in more detail what the goal of this query is, we could provide a better answer on how to achieve.

Related

Cakephp Avoid wrong condition betweeen models

I am using cakephp 2.5, and need to join Vehicle table with Address table where vei_id is the foreign key
I have one find operation that is generating a wrong condition for the two models: Vehicle and Address.
Address has the vei_id column wich is the foreign key to join the vehicle table.
The query is generating vehicle_id as the column to join the two tables, the probem is that this column does not even exists.
I have mapped the two models using the vei_id column.
How can i avoid this situation ? seems cakephp try to guess the join column even if i have already write the condition using the column i want.
//Vehicle Model
public $hasOne = array(
'Address' => array(
'className' => 'Address',
'conditions' => array('Vehicle.vei_id = Address.vei_id'),
'foreignkey' => false
)
//Address Model
public $belongsTo = array(
'Vehicle' => array(
'className' => 'Vehicle',
'conditions'=> array('Vehicle.vei_id=Address.vei_id'),
'foreignKey' => 'vei_id'
),
);
//At vehiclecontroller
$data = $this->Vehicle->find('first', array(
'conditions' => array('Vehicle.vei_id' => $vehicleId),
'contain' => array(
'Address' => array('conditions'=> array('Address.vei_id'=>'Vehicle.vei_id',
'Vehicle.vei_id' => $vehicleId
),
)),
));
it generates this line :
LEFT JOIN Address ON (
Address.vehicle_id = Vehicle.vei_id
AND Address.vei_id = 'Vehicle.vei_id'
AND Vehicle.vei_id = 123
)
Where this column does not exists :
Address.vehicle_id = Vehicle.vei_id
Your query looks little bit confusing:
Just look at following conditions within contain:
'contain' => array(
'Address' => array('conditions'=>
array(
'Address.vei_id'=>'Vehicle.vei_id', // why is this ?
'Vehicle.vei_id' => $vehicleId
),
));
Why are you using following conditions within contain ?
Address.vei_id'=>'Vehicle.vei_id
Did you do that to join two tables ?
When you use contain these things are done by cakephp's convention.
Address table is already joined with vehicle table.
See here:Cakephp contain.
Also why not to follow cakephp convention?
If you have vehicles table,
the foreign key would be vehicle_id according to cakephp convention.
And if you have users table foreign key would be user_id.
These things also reduces your work and make things easier.
See Here: (cakephp model and database conventions).

cakephp - issue for joining multiple tables

I am joining multiple tables like the following.
$recommend_logs = $this->RecommendingProductLog->find('all', array(
'recursive' => 2,
'fields' => array('Product.ProductName', 'Product.Gender', 'Product.Price', 'RecommendingProductLog.preference', 'Brand.BrandName'),
'conditions' => array('RecommendingProductLog.user_id' => $user_id),
'contain' => array('Product', 'Product.Brand')
));
I am getting this query from log.
SQL Query: SELECT `Product`.`ProductName`, `Product`.`Gender`, `Product`.`Price`, `RecommendingProductLog`.`preference`, `Brand`.`BrandName`, `Product`.`id` FROM `database`.`recommending_Product_log` AS `RecommendingProductLog` LEFT JOIN `database`.`Products` AS `Product` ON (`RecommendingProductLog`.`Product_id` = `Product`.`id`) WHERE `RecommendingProductLog`.`user_id` = 32
Even though 'Product' table is a child table of 'Brand' table, somehow I don't see 'Brand' table in the query. That's why I am getting an error 'Unknown column 'Brand.BrandName' in 'field list'.
I specified 'Brand' in the Perfume model as 'belongsTo' and 'Perfume' in RecommendingPerfumeLog model and 'Perfume' as hasMany in Brand model.
can somebody point where the problem is?
thanks.
Have you correctly put the foreign keys on the respective tables of the relation? In this case Product have to have a a key with the name brand_id and it must be a foreign key to the brand id field.
Also check this and try to put your joins.
I don't remenber this 'contain' field from the docs. Are you using the latest version?

SUM field from contained records

On cake 2.1, I need to SUM a field from the contained records.
Currently I get the sum of All records on the child table, but need to group by the id of my main table (employee).
Attend belongs to Employee
$agents = $this->Employee->find('all', array(
'fields' => array('Employee.FullNameNoId'),
'conditions' => $conditions,
'contain' => array(
'Attend' => array(
'conditions' => array(
'Attend.as_date BETWEEN ? AND ?' => array(
date('Y-m-d', strtotime($this->passedArgs['date1'])),
date('Y-m-d', strtotime($this->passedArgs['date2']))
)
),
'fields' => array('sum(Attend.as_labormin) AS total'),
//'group' => array('Attend.employee_id') // This gets sql errors below
)
)
));
Tried several combinations with SQL errors:
'group' => array('Attend.employee.id') // Model "Attend" is not associated with model "Attend"
//Column not found: 1054 Unknown column
'group' => array('employee_id') // Model "Attend" is not associated with model "employee_id"
// Column not found: 1054 Unknown column 'Attend.group' in 'field list'
'group' => array('Employee.id') //Column not found: 1054 Unknown column Attend.group' in 'field list'
Relation betweent tables is fine, I can get related records, problem is to get a sum by employee id.
Checked Cakephp SUM of related field, but it seems cumbersome to use SELECT SUM, and they left out the grouping needed.
Can you help?
Try disabling 'autoFields'. It is known to cause SQL errors with aggregate functions and 'group by' statements. Find out more here

CakePHP Find Query SUM() two fields from two different tables

I am using CakePHP Find query to get SUM() from two diff fields in two diff db tables:
$total = $this->Model->find('all',array('fields' =>
array(
'SUM(Model1.amount * Model2.purchase * Model3.rental * Model4.manu) as total'),
'group' => array( 'Model.trackby_id' ) )
);
I have found sum() method useful for same tables two diff fields, but it does not seems working when two diff fields from diff tables.
Anybody have a solution for this?
You need to specify your SQL fragment in the fields array in the parameters of the find method.
$total = $this->Model->find('all', array(
'fields' => array(
'SUM(Model.price + OtherModel.price) AS total'
),
'group' => 'Model.id'
));

Exclude empty results for HABTM relationship in CakePHP

Most of the CakePHP documentation seems to tell you how to filter based on a concrete relationship result. What I cannot seem to find is how to filter out results which have a relationship that returns no data.
For example, take the typical blog example which has Posts and Tags. Tags has and belongs to many Posts (HABTM). For this discussion, assume the following table structure:
posts ( id, title )
tags ( id, name )
posts_tags ( post_id, tag_id )
How do you find only the Tags which have one or more Posts associated with them (i.e. exclude Tags which would return no Posts)?
The ideal result set would look something like (quotes added for formatting):
Array (
[0] => Array (
[Tag] => Array (
[id] => 1
[name] => 'Tag1' )
[Post] => Array (
[0] => Array (
[id] => 1
[title] => 'Post1' )
[1] => Array (
[id] => 4
[title] => 'Post4' ) )
)
[1] => Array (
[Tag] => Array (
[id] => 4
[name] => 'Tag5' )
[Post] => Array (
[0] => Array (
[id] => 4
[title] => 'Post4' )
[1] => Array (
[id] => 5
[title] => 'Post5' )
[2] => Array (
[id] => 6
[title] => 'Post6' ) )
) )
The only way I've ever found to do this in a reliable way is to use ad hoc joins. Using these, you can specify an inner join type and get exactly what you want.
The following was tested with Cake 1.3.
To start you probably want to, or already do, have the HABTM relationship defined on the models for all the other circumstances where this normally applies:
class Post extends AppModel {
var $hasAndBelongsToMany = 'Tag';
}
class Tag extends AppModel {
var $hasAndBelongsToMany = 'Post';
}
According to Cake's own documentation:[1]
In CakePHP some associations (belongsTo and hasOne) perform automatic joins to retrieve data, so you can issue queries to retrieve models
based on data in the related one.
But this is not the case with hasMany and hasAndBelongsToMany associations. Here is
where forcing joins comes to the rescue. You only have to define the necessary joins
to combine tables and get the desired results for your query.
Excluding empty HABTM results is one of these times. This same section of the Cake Book explains how to accomplish this, but I didn't find it overly obvious from reading the text that the result achieves this. In the example in the Cake Book, they use the\ join path Book -> BooksTag -> Tags, instead of our Tag -> PostsTag -> Posts. For our example, we'd set it up as follows from in the TagController:
$options['joins'] = array(
array(
'table' => 'posts_tags',
'alias' => 'PostsTag',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'PostsTag.tag_id = Tag.id'
),
array(
'table' => 'posts',
'alias' => 'Post',
'type' => 'INNER',
'foreignKey' => false,
'conditions' => 'Post.id = PostsTag.post_id'
)
);
$tagsWithPosts = $this->Tag->find('all', $options);
Make sure to set the foreignKey to false. This tells Cake that it should not attempt to figure out the join condition and instead use only the condition we supplied.
It's common that this will bring back duplicate rows due to the nature of the joins. To reduce the SQL returned use a DISTINCT on the fields as necessary. If you want all fields as is normally returned by find('all'), this adds the complication that you need to hard code each column. (Sure your table structure shouldn't change that often, but it could happen, or if you may just have a lot of columns). To grab all columns programmatically, add the following before the find method call:
$options['fields'] = array('DISTINCT Tag.'
. implode(', Tag.', array_keys($this->Tag->_schema)));
// **See note
It is important to note that the HABTM relationship runs AFTER the main select. Essentially, Cake gets the list of eligible Tags and then runs another round of SELECT statement(s) to get the associated Posts; you can see this from the SQL dump. The 'joins' we manually setup apply to the first select giving us the desired set of Tags. Then the built-in HABTM will run again to give us ALL associated Posts to those tags. We won't have any tags which have no Posts, our goal, but we may get posts associated with the tag that are not part of any of our initial 'conditions', if they were added.
For example, adding the following condition:
$options['conditions'] = 'Post.id = 1';
Will yield the following result:
Array (
[0] => Array (
[Tag] => Array (
[id] => 1
[name] => 'Tag1' )
[Post] => Array (
[0] => Array (
[id] => 1
[title] => 'Post1' )
[1] => Array (
[id] => 4
[title] => 'Post4' ) )
)
)
Based on the sample data in the question, only Tag1 was associated with our 'conditions' statement. So this was the only result returned by the 'joins'. However, since the HABTM ran after this, it grabbed all Posts (Post1 and Post4) that were associated with Tag1.
This method of using explicit joins to get the desired initial data set is also discussed in Quick Tip - Doing Ad-hoc Joins in Model::find(). This article also shows how to generalize the technique and add it to the AppModel extending find().
If we really only wanted to see Post1 as well, we would need to add a 'contain'[2] option clause:
$this->Tag->Behaviors->attach('Containable');
$options['contain'] = 'Post.id = 1';
Giving the result:
Array (
[0] => Array (
[Tag] => Array (
[id] => 1
[name] => 'Tag1' )
[Post] => Array (
[0] => Array (
[id] => 1
[title] => 'Post1' ) )
)
)
Instead of using Containable you could use bindModel to redefine the HABTM relationship with this instance of find(). In the bindModel you would add the desired Post condition:
$this->Tag->bindModel(array(
'hasAndBelongsToMany' => array(
'Post' => array('conditions' => 'Post.id = 1'))
)
);
I feel that for beginners trying to wrap their head around the automagic abilities of cake, making the explicit joins is easier to see and understand (I know it was for me). Another valid and arguably more 'Cake' way to do this would be to use unbindModel and bindModel exclusively. Teknoid over at http://nuts-and-bolts-of-cakephp.com has a good write up on how to do this: http://nuts-and-bolts-of-cakephp.com/2008/07/17/forcing-an-sql-join-in-cakephp/. Additionally, teknoid made this into a Behavior which you can grab from github: http://nuts-and-bolts-of-cakephp.com/2009/09/26/habtamable-behavior/
** This will pull the columns in the order defined in the database. So if the primary key is not defined first it may not apply DISTINCT as expected. You may need to modify this to use array_diff_key to filter out the primary key from $this->Model->primaryKey.

Resources