I have two tables posts and users. The post.user_id=users.id.
The fields are
user: id, username, password
post: id, user_id, post_name
I would like to select post.id, post.post_name, user.username.
How to select this in CakePHP?
I have two Models named User and Post. It calls the Model from PostController.
If you have correctly defined the relationship between those two models, this should just happen when you use the Post model's find method:
// In PostsController
$posts = $this->Post->find('all');
// $posts should be an array like this
Array
(
[0] => Array
(
[Post] => Array
(
// Model data
)
[User] => Array
(
// Model data
)
)
)
Hope this helps.
You should properly define the relations between the two models in order to simplify your life, but if you want to use a join, here is the syntax:
find('all', array(
'conditions' => array('name' => 'Thomas Anderson'),
'joins' => array(
array(
'alias' => 'Thought',
'table' => 'thoughts',
'type' => 'LEFT',
'conditions' => '`Thought`.`person_id` = `Person`.`id`'
)
)
));
Related
I'm newbie on CakePHP, and now I'm stuck on many to many situation
ok, i have 3 Table :
questions
with fields (id, question)
question_product
with fields (id, question_id, product_id, question_number, is_enabled)
products
with fields (id, name, code, is_enabled)
so when i want to select questions with specific field, i don't know how to fix it
for now, my code is like this :
Question.php (Model)
class Question extends AppModel {
public $hasAndBelongsToMany = array (
'Product' => array (
'joinTable' => 'question_product',
'foreignKey' => 'question_id',
'associationForeignKey' => 'product_id',
'unique' => 'keepExisting',
'order' => 'question_number',
'fields' => array (
'QuestionProduct.question_number',
'Product.id',
'Product.name'
),
'conditions' => array (
'QuestionProduct.is_enabled' => 1,
)
)
);
}
QuestionsController.php (Controller)
public function loadQuestions($productId) {
$this->view = 'load_questions';
$questions = $this->Question->find('all', array (
'fields' => array (
'Question.id',
'Question.question',
'Question.is_optional',
'Question.reason_optional',
'Question.text_size'
),
'conditions' => array (
'QuestionProduct.product_id' => $productId
)
));
$this->set($questions);
}
method loadQuestions have one parameter to select with specified product
if i using sql query, it will be like this
select all from Question with condition Product.product_id=4, sorted by QuestionProduct.question_number ascending
select questions.*
from questions
join question_product on questions.id=question_product.question_id
join products on products.id=question_product.product_id
where products.id=4
order by question_product.question_number;
any answer will be appreciated :)
Thanks !
Any time you use a many-many (HABTM) relation with any other field that requires conditions, it is no longer many-many as far as Cake is concerned. You want the HasManyThrough relationship
Instead of using hasAndBelongsToMany relation, use two belongsTO relation from question_product to questions and another time from question_product to products.
question_product belognsTo questions
question_product belongsTo products
NOTE:you should change the table name from question_product to question_products as cakePHP convention
in your model QuestionProduct model :
<?php
// declares a package for a class
App::uses('AppModel', 'Model');
class QuestionProduct extends AppModel {
/**
* #see Model::$actsAs
*/
public $actsAs = array(
'Containable',
);
/**
* #see Model::$belongsTo
*/
public $belongsTo = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => 'product_id',
),
'Question' => array(
'className' => 'Question',
'foreignKey' => 'question_id',
),
);
then in your Controller :
public function loadQuestions($productId) {
$this->view = 'load_questions';
$questions = $this->QuestionProduct->find('all', array (
'fields' => array (
'Question.id',
'Question.question',
'Question.is_optional',
'Question.reason_optional',
'Question.text_size'
),
'conditions' => array (
'QuestionProduct.product_id' => $productId
),
'contain' => array('Product','Question')
));
$this->set($questions);
}
It should make exactly the query you want, and I don't think it has any other way to produce that query.
I have two tables topics and posts. Relation: topics hasmany posts. In both table, there is a status field (Y, N) to moderate the content. In my page, I want to list all not moderated topics for which at least one post status is N or topic status itself is N. Is it possible to do with find function in cakephp2.0. Im using Containable behavior.
I need to apply pagination too.
This is one solution:
Search on the Post model for (Posts with N status) OR (Posts which belong to Topics with N status) and store the topic_id
Now search on the Topic model for topics with ID on the list
Something like this:
# TopicsController.php
$ids = $this->Topic->Post->find('list', array(
'fields' => array('Post.topic_id')
'conditions' => array(
'OR' => array(
'Post.status' => 'N',
'Topic.status' => 'N',
)
)
));
$this->paginate = array(
'conditions' => array('Topic.id' => (array)$ids),
'order' => array('Topic.created' => 'DESC')
);
$topics = $this->paginate('Topic');
Since you're searching on the Posts model, CakePHP will join the parent Topic data and you can filter by both statuses.
If i understand correctly you can use:
$conditions => array ('OR' => array ('Topic.status' => 'N', 'Post.status' => 'N'));
Well I haven't tested it but following should work
$this->recursive = -1; //necessary to use joins
$options['joins'] = array(
'table' => 'posts',
'alias' => 'Post',
'type' => 'left',
'conditions' => array('Topic.id = Post.topic_id', 'Post.status = N') //updated code
);
$options['group'] = array('Topic.id HAVING count('Topic.id') >= 1 OR Topic.status = N');
$this->Topic->find('all', $options);
I have 3 models,
Mentor, MentorAttrib, Attrib where MentorAttrib is a join table. it lists Mentor.id -> Attrib.id
This is my find
$cond = array("Mentor.is_listed"=>1);
$contain_cond = array();
$contain = array(
'MentorAttrib' => array(
'fields' => array('MentorAttrib.id' ,'MentorAttrib.attrib_id'),
'Attrib'
)
);
if(! empty($this->request->data))
{
debug($this->request->data);
//skills
if(! empty($this->request->data['bookingSkills']))
{
$cond = array('MentorAttrib.attrib_id' => $this->request->data['bookingSkills']);
}
}
$this->request->data = $this->Mentor->find('all', array(
'conditions' => $cond,
'fields' => array('Mentor.id','Mentor.first_name','Mentor.last_name','Mentor.img'),
'contain' => $contain
));
I want to filter the result by the skills.
[bookingSkills] => Array
(
[0] => 2
[1] => 4
[2] => 10
)
The error im getting is
Column not found: 1054 Unknown column 'MentorAttrib.attrib_id' in 'where clause'
This is the data set
http://pastebin.com/85uBFEfF
You need a join
By default, CakePHP will only create joins for hasOne and belongsTo associations - any other type of association generates another query. As such you cannot filter results based on a condition from a hasMany or hasAndBelongsToMany association by just injecting conditions and using contain.
Forcing a join
On option to achieve the query you need is to use the joins key. As shown in the docs You can also add a join, easily, like so:
$options['joins'] = array(
array('table' => 'mentor_attribs',
'alias' => 'MentorAttrib',
'type' => 'LEFT',
'conditions' => array(
'Mentor.id = MentorAttrib.mentor_id',
)
)
);
$data = $this->Mentor->find('all', $options);
This allows the flexibility to generate any kind of query.
HI,
why not work "order" function?
$this->paginate = array(
'limit' => 5,
'order' => array(
'User.name' => 'desc'
),
'fields' => array('Post.id', 'Post.title', 'User.name AS aut_name'),
'joins' => array(
array(
'table' =>'users',
'alias' =>'User',
'type' =>'LEFT',
'conditions' => array(
'Post.user_id = User.user_id'
)
)
)
);
$posts = $this->paginate();
$this->set(compact('posts'));
DB structure:
posts:
id, title,body, created, updated, user_id
users:
user_id, name
Looking quickly your code... Does field user_id exist in User table?
'conditions' => array(
'Post.user_id = User.id'
)
Because you're specifying User.name As aut_name in your fields, you won't be able to order by User.name unless you also have User.name in your fields list. Alternatively use:
'order' => array(
'aut_name' => 'desc'
),
NOTE: This is for the initial query only, to sort by aut_name from the View you'll need to use a Virtual Field in the User model.
Also, as #Min said, are your conditions correct?
Adding a solution on the same problem:
This work for cakePHP 2.3.4
CakePHP Paginate conditions on Join Table
I have been busy with the cakePHP framework for a couple of months now and I really love it. At the moment I'm working on a very new project and it does the job like it should (I think ...) but I feel uncomfortable with some code I wrote. In fact I should optimize my paginate conditions query so I get immediately the right results (right now I manipulate the result set by a bunch of Set::extract method calls.
I'll sketch the relevant aspects of the application. I have a model 'Site' who has a hasMany relationship with the model 'SiteMeta'. This last table looks as follow: id, site_id, key, value, created.
In this last model I record several values of the site at various periods. The name of the key I want to store (e.g. alexarank, google pagerank, ...), and off course also the value. At a given interval I let my app update this database so I can track evolution of this values.
Now my problem is this.
On the overview page of the various websites (controller => Sites, action => index) I'd like to show the CURRENT pagerank of the website. Thus I need one exact SiteMeta record where the 'created' field is the highest and the value in 'key' should be matching the word 'pagerank'. I've tried several things I read on the net but got none of them working (containable, bindmodel, etc.). Probably I'm doing something wrong.
Right now I get results like this when I do a $this->paginate
Array
(
[0] => Array
(
[Site] => Array
(
[id] => 1
[parent_id] => 0
[title] => test
[url] => http://www.test.com
[slug] => www_test_com
[keywords] => cpc,seo
[language_id] => 1
)
[SiteMeta] => Array
(
[0] => Array
(
[id] => 1
[site_id] => 1
[key] => pagerank
[value] => 5
[created] => 2010-08-03 00:00:00
)
[1] => Array
(
[id] => 2
[site_id] => 1
[key] => pagerank
[value] => 2
[created] => 2010-08-17 00:00:00
)
[2] => Array
(
[id] => 5
[site_id] => 1
[key] => alexa
[value] => 1900000
[created] => 2010-08-10 17:39:06
)
)
)
To get the pagerank I just loop through all the sites and manipulate this array I get. Next I filter the results with Set::extract. But this doens't feel quite right :)
$sitesToCheck = $this->paginate($this->_searchConditions($this->params));
foreach($sitesToCheck as $site) {
$pagerank = $this->_getPageRank($site['Site']);
$alexa = $this->_getAlexa($site['Site']);
$site['Site']['pagerank'] = $pagerank;
$sites[] = $site;
}
if (isset($this->params['named']['gpr']) && $this->params['named']['gpr']) {
$rank = explode('-', $this->params['named']['gpr']);
$min = $rank[0];$max = $rank[1];
$sites = Set::extract('/Site[pagerank<=' . $max . '][pagerank>=' . $min .']', $sites);
}
$this->set(compact('sites', 'direction'));
Could you guys please help me to think about a solution for this? Thanks in advance.
Thanks for the contributions. I tried these options (also something with bindmodel but not working also) but still can't get this to work like it should be. If I define this
$this->paginate = array(
'joins'=> array(
array(
'table'=>'site_metas',
'alias'=>'SiteMeta',
'type' =>'inner',
'conditions' =>array('Site.id = SiteMeta.site_id')
)
),
);
I get duplicate results
I have a site with 3 different SiteMeta records and a site with 2 different record.
The paginate method returns me 5 records in total. There's probably an easy solution for this, but I can't figure it out :)
Also I tried to write a sql query myself, but seems I can't use the pagination magic in that case. Query I'd like to imitate with pagination options and conditions is the following. The query returns exactly as I would like to get.
$sites = $this->Site->query('SELECT * FROM sites Site, site_metas SiteMeta WHERE SiteMeta.id = (select SiteMeta.id from site_metas SiteMeta WHERE Site.id = SiteMeta.site_id AND SiteMeta.key = \'pagerank\' order by created desc limit 0,1 )');
As you are trying to retrieve data in a hasMany relationship, cakephp doesn't join the tables by default. If you go for joins you can do something like:
$this->paginate = array(
'joins'=>array(
array(
'table'=>'accounts',
'alias'=>'Account',
'type' =>'inner',
'conditions' =>array('User.id = Account.user_id')
)
),
'conditions'=> array('OR' =>
array(
'Account.name'=>$this->params['named']['nickname'],
'User.id' => 5)
)
);
$users = $this->paginate();
$this->set('users',$users);
debug($users);
$this->render('/users/index');
You have to fit this according to your needs of course. More on joins, like already mentioned in another answer.
Edit 1: This is because you are missing the second 'conditions'. See my code snippet. The first 'conditions' just states where the join happens, whereas the second 'conditions' makes the actual selection.
Edit 2: Here some info on how to write conditions in order to select needed data. You may want to use the max function of your rdbms on column created in your refined condition.
Edit 3: Containable and joins should not be used together. Quoted from the manual: Using joins with Containable behavior could lead to some SQL errors (duplicate tables), so you need to use the joins method as an alternative for Containable if your main goal is to perform searches based on related data. Containable is best suited to restricting the amount of related data brought by a find statement. You have not tried my edit 2 yet, I think.
Edit 4: One possible solution could be to add a field last_updated to the table Sites. This field can then be used in the second conditions statement to compare with the SiteMeta.created value.
Try something like this:
$this->paginate = array(
'fields'=>array(
'Site.*',
'SiteMeta.*',
'MAX(SiteMeta.created) as last_date'
),
'group' => 'SiteMeta.key'
'conditions' => array(
'SiteMeta.key' => 'pagerank'
)
);
$data = $this->paginate('Site');
Or this:
$conditions = array(
'recursive' => 1,
'fields'=>array(
'Site.*',
'SiteMeta.*',
'MAX(SiteMeta.created) as last_date'
),
'group' => 'SiteMeta.key'
'conditions' => array(
'SiteMeta.key' => 'pagerank'
)
);
$data = $this->Site->find('all', $conditions);
If that does not work check this and this. I am 100% sure that it is possible to get the result you want with a single query.
Try something like this (with containable set up on your models):
$this->Site->recursive = -1;
$this->paginate = array(
'conditions' => array(
'Site.title' => 'title') //or whatever conditions you want... if any
'contain' => array(
'SiteMeta' => array(
'conditions' => array(
'SiteMeta.key' => 'pagerank'),
'limit' => 1,
'order' => 'SiteMeta.created DESC')));
I use containable so much that I actually have this in my app_model file so it applies to all models:
var $actsAs = array('Containable');
Many thinks to all who managed to help me through this :)
I got it fixed after all hehe.
Eventually this has been the trick for me
$this->paginate = array(
'joins'=> array(
array(
'table'=>'site_metas',
'alias'=>'SiteMeta',
'type' =>'inner',
'conditions' => array('Site.id = SiteMeta.site_id'))
),
'group' => 'Site.id',
'contain' => array(
'SiteMeta' => array(
'conditions' => array(
'SiteMeta.key' => 'pagerank'),
'limit' => 1,
'order' => SiteMeta.created DESC',
)));
$sites = $this->paginate();