I have the folowing associations
post->primary->secondary
$results = $this->Post->find('all', array(
'conditions' => array(
'Post.post_id =' => 2,
'Primary.secondary_id !=' => null
),
'contain' => array(
'Primary' => array(
'Secondary' => array(
'conditions' => array('Secondary.short_code =' => 'code')
)
)
)
));
Returns this.
Array
(
[0] => Array
(
[Post] => Array
(
[id] => 2
[created] => 2012-10-29 09:48:29
[modified] => 2012-10-29 09:48:29
)
[Primary] => Array
(
[id] => 3
[secondary_id] => 6
[Secondary] => Array
(
[id] => 6
[short_code] => code
[created] => 2012-10-31 11:19:56
[modified] => 2012-10-31 11:20:03
)
)
)
However when I change
'conditions' => array('Secondary.short_code =' => 'code')
to
'conditions' => array('Secondary.short_code !=' => 'code')
it still returns the primary record, when I dont want it to.
Array
(
[0] => Array
(
[Post] => Array
(
[id] => 2
[created] => 2012-10-29 09:48:29
[modified] => 2012-10-29 09:48:29
)
[Primary] => Array
(
[id] => 3
[secondary_id] => 6
[Secondary] => Array
(
)
)
)
It's hard to know exactly what you're hoping to achieve, but I THINK it sounds like you're trying to limit the 'Primary' results based on conditions against the 'Secondary' model.
If that's the case, you're going to need to use joins() instead of contain().
The reason: When you use CakePHP's Containable Behavior, it actually does separate queries, then combines the results before returning the data to you. Doing it this way is great in many ways, but it does not allow you to limit parent results based on conditions against it's children.
To do that, just use joins(). (CakePHP syntax which creates MySQL JOIN)
Related
When I fetch records using hasAndBelongsToMany it sends me the following output:
[Location] => Array
(
[id] => 1
[uuid] => 5a8e4d7f-e1d0-11e4-9567-c03fd5623744
[name] => chandigarh
[address_line1] => dummy
[address_line2] => dummy
[latitude] => 30.737780
[longitude] => 76.784439
[timezone] => UTC
[is_deleted] => 0
[created] => 2015-04-13 16:59:31
[modified] => 2015-04-13
[entry_ts] => 2015-04-13 16:59:31
)
[Department] => Array
(
[0] => Array
(
[dept_name] => Heater
[LocationDepartment] => Array
(
[id] => 1
[location_id] => 1
[department_id] => 1
[is_deleted] => 0
[created] => 2015-04-13 17:00:50
[modified] => 2015-04-13 17:00:50
[entry_ts] => 2015-04-13 17:00:50
)
)
)
But I don't need the LocationDepartment array. Can any one help me?
If your using a quick find you can do this before you do it
$this->Location->recursive = 1;
$this->Location->findById($id);
If your using a full find you can add in recursive as part of the passed parameters
$this->Location->find('first', array('conditions' => array('id' => $id), 'recursive' => 1));
Posting your sample code will help solve something this simple
You can also include the 'containable' behavior on the Model which will let you specify exactly what models/data to retrieve back.
class Location extends AppModel {
var $actsAs = array('Containable');
function GetRecord($id) {
return $this->find('first', array(
'conditions' => array(
'id' => $id,
),
'contain' => array('Department'),
);
}
}
I have a 2 CakePHP models, Articles and Categories, attached using a hasAndBelongsToMany relationship, like so:
$category = new Category();
$category->bindModel(array('hasAndBelongsToMany' => array(
'Article' => array(
'className' => 'Article',
'joinTable' => 'articles_categories',
'foreignKey' => 'category_id',
'associationForeignKey' => 'article_id',
'fields' => 'id,name'
))));
$this->set('categories', $category->find('all',
array(
'fields' => 'id,name'
)));
...but then, when I print out $categories, I get the following:
Array
(
[0] => Array
(
[Category] => Array
(
[id] => 31
[name] => category name
[article_count] => 1
)
[Article] => Array
(
[0] => Array
(
[id] => 1445
[name] => article name
[teaser] =>
[author_id] => 3
[ArticlesCategory] => Array
(
[id] => 6634
[article_id] => 1445
[category_id] => 31
[author_id] => 3
[created] => 2014-03-10 12:27:26
[modified] => 2014-03-10 12:27:26
)
)
)
)
I really don't need the [ArticlesCategory] member of [Article]. This just leads me back to information I already have. I tried limiting recursion but that didn't help.
How would I get rid of this?
You have two options:
[1] Reduce recursive value to 0 (CakePHP default: 1)
$category->recursive = 0;
$category->find('all',
array(
'fields' => 'id,name'
))
[2] Start using ContainableBehaviour (my own preference), which gives you more control over model data retrieved
Add following to AppModel for App-wide coverage:
public $actsAs = array('Containable');
The find method becomes:
$category->find('all',
array(
'fields' => 'id, name',
'contain' => 'Article'
))
That should do it!
To explain the issue I'm having, I'll use an example. Let's say that I'm building a system where students can sign up for one or more afterschool courses, but a school official has to approve the sign-up in order for it to be valid. So I have these models:
Course (belongs to "Teacher", hasAndBelongsToMany "Student")
Student (hasAndBelongsToMany "Course")
Teacher (hasMany "Course")
Now let's say I want to see a list of all of the unapproved sign-ups that are for ninth-graders. That's easy:
$this->request->data = $this->Student->CoursesStudent->find('all', array(
'conditions' => array(
'CoursesStudent.is_approved' => null,
'Student.grade' => 9
)
));
The thing that I'm struggling with is pulling the teacher's data as well (more than just their ID). I've tried this:
$this->request->data = $this->Student->CoursesStudent->find('all', array(
'conditions' => array(
'CoursesStudent.is_approved' => null,
'Student.grade' => 9
),
'contain' => array(
'CoursesStudent',
'Course' => array(
'Teacher' => array(
'fields' => array(
'Teacher.id',
'Teacher.name'
)
)
),
'Student'
)
));
But it doesn't have any effect whatsoever on the returned array. To make it easy for you to throw this into an existing CakePHP project to play with, here is the database schema and data and here is what you can paste into a random action of an existing CakePHP project. This is what I'm trying to get (notice that the teacher's data is there):
Array
(
[0] => Array
(
[CoursesStudent] => Array
(
[id] => 1
[course_id] => 1
[student_id] => 1
[is_approved] =>
[created] => 2012-12-11 00:00:00
[modified] => 2012-12-11 00:00:00
)
[Course] => Array
(
[id] => 1
[teacher_id] => 1
[title] => Introduction to Improvisation
[start_date] => 2012-12-17
[end_date] => 2012-12-21
[created] => 2012-12-11 00:00:00
[modified] => 2012-12-11 00:00:00
[Teacher] => Array
(
[id] => 1
[first_name] => John
[last_name] => Doe
[created] => 2012-12-11 00:00:00
[modified] => 2012-12-11 00:00:00
)
)
[Student] => Array
(
[id] => 1
[first_name] => Bill
[last_name] => Brown
[grade] => 9
[created] => 2012-12-11 00:00:00
[modified] => 2012-12-11 00:00:00
)
)
)
Thanks!
You must set $recursive to -1 before using contain() will work.
Also, make sure your model is set to use the Containable Behavior. In this case, since 'Course' is also containing something, it needs to use the Containable behavior too.
(You could think about setting the below $actsAs in your AppModel to make Containable available to every model.)
//in your CoursesStudent model (and in your Course model)
public $actsAs = array('Containable');
And here's your find, but with setting recursive first:
$this->Student->CoursesStudent->recursive = -1;
$this->request->data = $this->Student->CoursesStudent->find('all', array(
'conditions' => array(
'CoursesStudent.is_approved' => null,
'Student.grade' => 9
),
'contain' => array(
'CoursesStudent',
'Course' => array(
'Teacher' => array(
'fields' => array(
'Teacher.id',
'Teacher.name'
)
)
),
'Student'
)
));
Ive a HABTM relationship where the output is like below, what i would like to do is have cakephp return a "list" with just the "friend.id" and "friend.company_name" and exclude the "User".
Ive spent quite a while researching how to do this and cant get it to work.
My "search method" in users controller; i think i need to use the containable behaviour but im not sure what to do. ive managed to get the "friend" results only show 2 fields but i need to get rid of the "User" results. How do i do this?
The relationship is defined as in the user model file;
var $hasAndBelongsToMany = array(
'Friend' => array(
'joinTable' => 'retailerrelationships',
'className' => 'User',
'foreignKey' => 'retailer_id',
'associationForeignKey' => 'supplier_id'
)
);
user action in user controller:
$this->User->Behaviors->attach('Containable');
$users=$this->User->find('all',array('conditions'=>array('User.id'=>$this->Auth->user('id')),'contain'=>array('Friend.id','Friend.company_name')));
$this->set('users' ,$users);
debug Output
Array
(
[0] => Array
(
[User] => Array
(
[id] => 104
[username] => admin
)
[Friend] => Array
(
[0] => Array
(
[id] => 107
[company_name] => carskitchens
[Retailerrelationship] => Array
(
[id] => 12
[retailer_id] => 104
[supplier_id] => 107
[created] => 2012-03-28 10:14:23
[modified] => 2012-03-28 10:14:23
)
)
[1] => Array
(
[id] => 112
[company_name] => mr manufacturer
[Retailerrelationship] => Array
(
[id] => 13
[retailer_id] => 104
[supplier_id] => 112
[created] => 2012-03-28 11:26:52
[modified] => 2012-03-28 11:26:52
)
)
)
)
)
You should look at the Set class.
Try this
Set::combine($results, '{n}.Friend.id', '{n}.Friend.company_name');
I would like to be able to list the 10 most popular tags (subjects) of my site on the right menu as an element.
I however have been thinking 30 minutes about this now and unlike ending up with 1 statement to do this, I seem to be ending up with 3 statements that would drastically decrease the performance of the site.
I have a site with short stories and tags. This is a HABTM (Has-and-belongs-to-many) relationship between them using a table 'stories_tags'. A story can have multiple tags and a tag can be used by multiple stories.
The goal is listing $tagname ($storycountwiththattag), from highest number of stories per tag, to 10th highest. I have this so far, but it doesn't seem too close.
$tags = $this->Tag->find('all',array('fields'=>array('Tag.name')));
$tags_count = $this->Tag->Story->find('count',array('conditions'=>array('Story.tag'=>$tags)));
debug($tags_count);
I have tried a lot of possible queries. I could do it with restricted find('all')'s, or with:
Getting all ID's of all tags
Running 1 count-query per tag...
Use those results.
But I have picked Cakephp to make better applications so I wondered how you guys would do this! The site gets only a few hundred visitors daily, so performance is not extremely vital, but avoiding extremely dumb queries seems something I should try to do even if some performance penalty wouldn't matter.
use counterCache on your tag (although you can't use with HABTM though, you'll have to define another "Tag hasMany StoriesTag" relationship. You can also Cache the query result.
I make some test in the same scenario but differents ModelNames. I hope you can use it.
function top(){
$options = array(
'fields' => array('*','COUNT(CategoriesEnterprise.enterprise_id) AS num')
, 'group' => array( 'CategoriesEnterprise.category_id' )
, 'order' => 'num DESC'
, 'limit' => '3'
, 'joins' => array(array(
'table' => 'categories',
'alias' => 'Category',
'type' => 'LEFT',
'conditions' => array('CategoriesEnterprise.category_id = Category.id')
))
);
$categories = $this->Category->CategoriesEnterprise->find('all', $options);
debug($categories);
$this->autoRender = false;
}
The result
Array
(
[0] => Array
(
[CategoriesEnterprise] => Array
(
[category_id] => 3
[enterprise_id] => 7
)
[Category] => Array
(
[id] => 3
[name] => Turismo y Viajes
[modified] => 2011-05-16
[created] => 2011-04-14
)
[0] => Array
(
[num] => 4
)
)
[1] => Array
(
[CategoriesEnterprise] => Array
(
[category_id] => 24
[enterprise_id] => 5
)
[Category] => Array
(
[id] => 24
[name] => Compras
[modified] => 2011-05-05
[created] => 2011-05-05
)
[0] => Array
(
[num] => 3
)
)
[2] => Array
(
[CategoriesEnterprise] => Array
(
[category_id] => 32
[enterprise_id] => 8
)
[Category] => Array
(
[id] => 32
[name] => Salud y Belleza
[modified] => 2011-05-16
[created] => 2011-05-16
)
[0] => Array
(
[num] => 3
)
)
)