This is one of those times when I know I'm doing something wrong, but I'm apparently deaf, dumb and blind with respect to seeing it. I'm hoping that another pair of eyes can open my own.
I have a ZipCode model and an Incentive model. There is a glorified join table (glorified because it has its own key) sitting in the middle. The join table has id, incentive_id and zip fields (legacy database). My ZipCode model HABTM Incentive as shown:
public $hasAndBelongsToMany = array(
'Incentive' => array(
'with' => 'ZipCodeIncentive',
'foreignKey' => 'zip',
'associationForeignKey' => 'incentive_id',
),
);
My Incentive model HABTM ZipCode as follows:
public $hasAndBelongsToMany = array(
'ZipCode' => array(
'with' => 'ZipCodeIncentive',
'foreignKey' => 'incentive_id',
'associationForeignKey' => 'zip',
),
I have a method, ZipCode::incentives( $zip ) that wants to pull all of the incentives relevant to the specified zip code:
$incentives = $this->Incentive->find(
'all',
array(
'contain' => array( 'ZipCode' ),
'fields' => array( 'Incentive.id', 'Incentive.name', 'Incentive.it_name', 'Incentive.amount', 'Incentive.state', 'Incentive.entire_state', 'Incentive.excluded', 'Incentive.is_active' ),
'conditions' => array(
'Incentive.excluded' => 0,
'Incentive.is_active' => 1,
'OR' => array(
'Incentive.state' => 'US', # nationwide incentives
array(
# Incentives that apply to the entire state the zip code belongs to
'Incentive.entire_state' => 1,
'Incentive.state' => $state,
),
'ZipCode.zip' => $zip # specific to the building's zip code
)
),
'order' => array( 'Incentive.amount DESC' ),
)
);
What I get for my trouble is the following error:
SQL Error: 1054: Unknown column 'ZipCode.zip' in 'where clause'
The ZipCode model's table isn't getting joined in the SQL, but I haven't grasped why yet. It's worth mentioning that the Incentive model is tied to a MySql view, not a table, via $useTable. I haven't seen anything to suggest that it should be a problem in this scenario, but it's non-standard.
If you see what I'm missing, please call 911 or at least drop an answer.
Rob
Move the condition
'ZipCode.zip' => $zip
To your contain declaration like this
array(
'contain' => array( 'ZipCode'=>
array('conditions'=>
array('ZipCode.zip' => $zip ))),
Then continue with the rest of your statement as usual
Related
I'm facing a problem with cakephp associations in Models.
I have to Select records which have atleast one hasMany reation row
Model
class Category extends AppModel
{
public $hasMany = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => 'CategoryId',
)
);
}
Query
$categories = $this->Category->find('all');
I only needed the categories which have atleast one product entry
Categories Like : Shirts, Footwear, Glasses etc
Products like :
Small, medium, large (Shirts)
With Frame, UV protected (Glass)
So, i jus want to get Shirts and Glasses Categories only because for the above example there is no products for Footwear
Use counterCache or joins
Please refer to CakePHP - Find and count how many associated records exist
The most simple way with the best performance would be using a properly indexed counter cache field as shown in the linked answer.
Sice the linked answer is not an exact duplicate with respect to the join, here's some additional info, instead of using HAVING COUNT with the join you'd use a IS NOT NULL condition. Here's an (untested) example:
$this->Category->find('all', array(
'joins' => array(
array(
'table' => 'products',
'alias' => 'Product',
'type' => 'LEFT',
'conditions' => array('Category.id = Product.CategoryId')
)
),
'conditions' => array(
'Product.CategoryId IS NOT NULL'
)
'group' => 'Category.id'
));
Depending on the used DBMS and version you might get better performance using an inner join:
$this->Category->find('all', array(
'joins' => array(
array(
'table' => 'products',
'alias' => 'Product',
'type' => 'INNER',
'conditions' => array('Category.id = Product.CategoryId')
)
),
'group' => 'Category.id'
));
I have three models in question: Customer, Company and User. Customer and User both belong to Company and Company has many Customers as following:
Customer:
var $belongsTo = array(
'Company' => array(
'className' => 'Company',
'foreignKey' => 'company_id',
'dependent' => false,
),
);
Company:
var $hasMany = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'company_id',
'dependent' => false
),
'Customer'=>array(
'className' => 'Customer',
'foreignKey' => 'company_id',
'dependent' => false
)
);
User:
var $belongsTo = array(
'Company' => array(
'className' => 'Company',
'foreignKey' => 'company_id',
'dependent' => false,
),
);
I have a problem when creating/editing Customer objects. Here is how to create form looks like:
echo $this->Form->input('Customer.customer_nr');
echo $this->Form->input('Customer.name');
echo $this->Form->input('Customer.phone');
echo $this->Form->input('Customer.email');
echo $this->Form->input('Customer.address');
echo $this->Form->input('Customer.post_nr');
echo $this->Form->input('Customer.city');
echo $this->Form->input('Customer.company_id', array('value' => $current_user['company_id'], 'type'=>'hidden'));
What I do in the end of the form is I take company_id from a currently logged in user and insert it as a Customer.company_id. It used to work without any problems before the new relations have been introduced. But now as I try to create/edit Customer, I receive the following SQL error:
Error: SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'name' in where clause is ambiguous
Any help is much much appreciated.
Here is the controller add function:
function add() {
if (!empty($this->data) ) {
$this->Customer->create();
if ($this->Customer->save($this->data)) {
$this->Session->setFlash(__('Customer was saved'), 'positive_notification');
$this->redirect(array('controller'=>'events', 'action' => 'dashboard'));
} else {
$this->Session->setFlash(__('Customer has been saved. Please, try again'), 'negative_notification');
}
}
}
The error is definately not being cause by redirect as it was fully tested.
the problem is somewhere else.
It's in fact related to a find() call.
Try to locate the exact code that trigger the error and post it in your question.
Probably you set some conditions like
'conditions' => array(
'name' => 'john'
)
but you better do something like
'conditions' => array(
'User.name' => 'john'
)
after you created the relationship between User and Company (it's just an example, maybe the two tabler involved are others) Cake started to join the two tables. So when you search for a particular name mysql doesn't know if you want user name or company name because you have name column in both tables.
If you look at the generated query (the one that gives you that error) you'll see the two tables joined. If you don't want that join you have to specify recursive => -1
'conditions' => array(
'name' => 'john'
),
'recursive' => -1
Hi i have model logo with the following associations
var $belongsTo = array(
'Attachment' => array(
'className'=>'Attachment',
'foreignKey'=>'attachment_id'
),
'Employee' => array(
'className'=>'Employee',
'foreignKey'=>'employee_id'
)
);
var $hasMany = array(
'Voting' => array(
'className'=>'Voting',
'foreignKey'=>'program_id'
)
);
i wrote a query in the following way it throws error
$PrgCond['contain'] = array(
'Attachment',
'Voting' => array(
'fields' => array(
'logo_program_id',
'COUNT(employee_id) as noOfEmps'
),
'group' => array('LogoVoting.logo_program_id')
),
'Employee' => array(
'fields' => array('id', 'employee_number', 'first_name', 'last_name')
)
);
$logoPrgDatas = $this->Program->find('all',$PrgCond);
Thrown Error is
SQL Error: 1054: Unknown column 'LogoVoting.logo_program_id' in 'field list'
This is an old question but still comes up in Google results..
I had a similar issue and found out that you cannot use group in the Model find options when you have associations or either use containable behavior(which uses associations) in cakephp 1.* or 2.*.
However, this issue is fixed in cakephp 3.* by using query builder! :)
It's a good idea to reference the model in your fields. For example, when you are using a column in a condition or a fields array, you should call it Model.column_name.
Having said that, the problem you are having is most likely related to the group:
'group' => array('LogoVoting.logo_program_id')
There is no LogoVoting in your model. It should be:
'group' => array('Voting.logo_program_id')
So I would update the query to be:
$PrgCond['contain'] = array(
'Attachment',
'Voting' => array(
'fields' => array(
'Voting.logo_program_id',
'COUNT(Voting.employee_id) as noOfEmps',
),
'group' => array('Voting.logo_program_id'),
),
'Employee' => array(
'fields' => array(
'Employee.id',
'Employee.employee_number',
'Employee.first_name',
'Employee.last_name',
),
),
);
I have some deep associations using containable and need to filter back the results. For the sake of this question, let's say we are selling cars and want to narrow the results down by features.
Car hasmany make hasmany model HABTM features
$options = array(
'order' => array('Car.price'),
'contain' => array(
'make',
'model' => array(
'order' => 'Model.name ASC'
),
'features'
)
);
$cars = $this->Car->find('all', $options);
How would I go about excluding all cars that don't have power windows (Features.name != power_windows).
Containable is only suitable for you to specify what models you wanted to include when fetching data, but not limiting the parent model from fetching data at all. One obvious symptom is that sometimes your parent data may have some null contained data.
So to achieve it, I think we should use joins here so you can specify condition:
$options = array(
'order' => array('Car.price'),
'contain' => array(
'make',
'model' => array(
'order' => 'Model.name ASC'
),
'features'
),
'joins' => array(
array(
'table' => 'features',
'alias' => 'Feature',
'type' => 'LEFT',
'conditions' => array(
'Car.id = Feature.car_id'
)
)
),
'conditions' => array(
'Features.name !=' => 'power_windows',
)
);
But one drawback of this is that you might have duplicated Car due to joining. That's a separate issue ;)
I want to limit the fields returned by a deeper association using containable.
My associations:
Game hasMany Review
The paginate and containable code:
$this->paginate = array(
'conditions' => $conditions,
'fields' => array(
'Game.id', 'Game.name',
'Publisher.id', 'Publisher.name'
),
'contain' => array(
'Game' => array(
'Review' => array(
'fields' => array('Review.id', 'ROUND(AVG(Review.score),1)')
)
),
)
);
$games = $this->paginate('Game');
Currently, all of the fields in the Review table are returned. 'ROUND(AVG(Review.score),1)' is never returned. How can I specify what fields I want returned from the Review association?
SQL dumps for two search results using #theJetzah's answer. The first is a search with one game as a result and the second is a search returning three games.
SELECT `Review`.`id`, `Review`.`review_text`, `Review`.`score`, `Review`.`user_id`, `Review`.`game_id`, `Review`.`created`, `Review`.`platform_id`, (ROUND(AVG(`Review`.`score`),1)) AS `Review__average_score` FROM `videogamedb`.`reviews` AS `Review` WHERE `Review`.`game_id` = (55)
SELECT `Review`.`id`, `Review`.`review_text`, `Review`.`score`, `Review`.`user_id`, `Review`.`game_id`, `Review`.`created`, `Review`.`platform_id`, (ROUND(AVG(`Review`.`score`),1)) AS `Review__average_score` FROM `videogamedb`.`reviews` AS `Review` WHERE `Review`.`game_id` IN (55, 56, 57)
Not a full answer, but an attempt to get it working :)
Approach1 (UPDATE: Containable doesn't support 'group by')
First of all, try to add the 'Game' model to the $uses array of your Controller, if it is not included yet, and re-organise the pagination array (as previously suggested by Sam), so that you'll be pagination the Game model itself.
Then, It may help to create a virtual field for the calculated score, but the results of 'Review' need to be grouped, otherwise you'll not be able to calculate the average score.
I'm not able to test this, but it may worth trying
something like this;
public $uses = array(
'Game',
// other models
);
public function myfunction()
{
$this->Game->Review->virtualFields['average_score'] = 'ROUND(AVG(Review.score),1)';
$this->paginate = array(
'Game' => array(
'fields' => array(
'Game.id',
'Game.name',
'Publisher.id',
'Publisher.name'
),
'contain' => array(
'Review' => array(
'fields' => array(
'Review.game_id,
'Review.average_score',
),
'group' => array(
'Review.game_id,
),
)
)
)
);
// Conditions can be passed to paginate,
// that way you can specify 'paginate' at
// one place and don't have to modify it
// to include the conditions
$games = $this->paginate('Game', $conditions);
}
Alternative approach: Using joins and a database-view
Apparently, the Containable behavior doesn't like group-by clauses; See this ticket for more information: Containable behavior does not implement 'group' option
CakePHP allows you to manually specify a join: Joining Tables
To simplify things and to prevent having to add a 'group by' for all fields, create a simple database-view in your database;
CREATE VIEW review_scores AS
SELECT
game_id,
ROUND(AVG(score),1) AS average_score,
COUNT(id) AS total_reviews
FROM
reviews
GROUP BY
game_id;
If you're unfamiliar with this; a database 'view' is basically a 'stored query', which can be accessed as if it was a regular table. See Create View
Then, use a 'manual' join, using the newly created database-view as the source-table. In your case, this will look something like this;
$this->paginate = array(
'Game' => array(
'fields' => array(
'Game.id',
'Game.name',
'Publisher.id',
'Publisher.name',
'ReviewScore.average_score',
'ReviewScore.total_reviews',
),
'joins' => array(
array(
'table' => 'review_scores',
'alias' => 'ReviewScore',
'type' => 'LEFT',
'conditions' => array(
'ReviewScores.game_id = Game.id',
)
)
)
)
);
Hope this helps
I think your array is a configured a little wrong, try:
$this->paginate = array(
'Game' => array(
'conditions' => $conditions,
'fields' => array(
'Game.id', 'Game.name',
'Publisher.id', 'Publisher.name'
),
'contain' => array(
'Review' => array(
'fields' => array('Review.id', 'ROUND(AVG(Review.score),1)')
)
)
)
);
$games = $this->paginate('Game');
As an aside, from personal experience, specifying the fields in a query doesn't always speed it up (certainly for small number of fields), assuming this is the motive for doing so. It does reduce memory occupancy but this is only relative to original size of the record and the number of records returned.