How to find by conditions in two joining tables in CakePHP - cakephp

In my CakePHP app I have three tables:
Businesses, Towns and Categories.
A business can belong to multiple towns and multiple categories so I have created joining tables and hasMany and belongsTo relationships. Everything works fine when finding businesses by either Town or Category by using the Town or Category model to search, but I am completely stuck when I want to search for businesses in a certain town AND a certain category, eg. Plumbers in London.
The associations just don't seem to work when searching with the Business model and I get column not found errors when trying to use the associated tables. I would think that this would be along the lines of what needs to be done, but I can't get it to work:
$this->set('listings', $this->Business->find('all', array(
'conditions' => array(
'Business.approved' => 1,
'BusinessesCategory.category_id' => $id,
'BusinessesTown.town_id' => $town_id,
'Business.sasite' => 1
)

You need to join the tables to do that.
I will put above a example how has to work with category and you can do the town yourself.
$this->Business->find("all", array(
"joins" => array(
array(
"table" => "businness_categories",
"alias" => "BusinessesCategory",
"type" => "LEFT",
"conditions" => array(
"Businesses.id = BusinessesCategory.business_id"
)
),
array(
"table" => "categories",
"alias" => "Category",
"type" => "LEFT",
"conditions" => array(
"BusinessesCategory.category_id = Category.id"
)
)
),
'conditions' => array(
'Business.approved' => 1,
'Category.id' => $id,
)
));
You also could use a behavior to do that for you:
https://github.com/Scoup/SuperJoin

Hi I had a very similar setup and the same problem. This is how I would solve your problem:
As you dont give away to much of your code I make some assumptions:
- You implemented your search method in the BusinessController
- Your search argument for the town is stored in vaiable $where and the one for Category is stored in $what
Code if you only have conditions for one table
$this->Businesses->Town->recursive = -1;
....
$options['joins'] = array(
array('table' => 'towns',
'alias' => 'Town',
'type' => 'inner',
'conditions' => array(
'Business.town_id = Town.id',
)
)
);
$options['conditions'] = array(
'Town.townName' => $where
);
$result = $this->Business->find('all', $options);
Code if you have conditions for two table
$this->Businesses->Town->recursive = -1;
$this->Businesses->Category->recursive = -1;
....
$options['joins'] = array(
array('table' => 'towns',
'alias' => 'Town',
'type' => 'inner',
'conditions' => array(
'Business.town_id = Town.id',
)
),
array('table' => 'categories',
'alias' => 'Category',
'type' => 'inner',
'conditions' => array(
'Business.category_id = category.id',
)
)
);
$options['conditions'] = array(
'Town.townName' => $where,
'Category.categoryName' => $what
);
$result = $this->Business->find('all', $options);

You can use
$this->Business->find('all', array(
'conditions' => array(
'AND' => array(
'BusinessesTown.town_id' => $town_id,
'BusinessesCategory.category_id' => $id
)
),
'recursive' => 2
));

Related

cake 2.5.1 bind 2 models together

I have a model Ranking which holds a contact_id and belongsTo Model Contact.
Model Contact has a costumer_id and belongsTo Model Costumer.
And hasMany Rankings.
There is also a Model Product which hasMany Ranking.
On a statistics page I select
$this->Product->recursive = 1;
$this->set('products', $this->Paginator->paginate())
;
and I get the array of
array(
'Product' => array(
'id' => '69',
),
'Ranking' => array(
(int) 0 => array(
'id' => '29',
'contact_id' => '9',
'product_id' => '69',
'ranking' => '9',
),
I would like to bind now the Contact and Costumer to the ranking based on the contact_id.
Is this manually possible via bindModel?
If yes, how can I do that?
I tried to set $this->Product->recursive = 1; to 2 and 3, but that select so many other things which I would need to clear with unbindModel... So I hope there is a smarter way of bind those model to get to the data...?
What you basically want to use is containable behavior. With this behavior you are able to filter and limit model find operations. You have the possibility to add this behavior on model level or at the controller to avoid side effects if the application has already grown to a complicated level.
Example from Cake-Book:
// Activate Containable Behavior on the fly in a controller
$this->User->Behaviors->load('Containable');
$this->User->contain();
$this->User->find('all', array(
'contain' => array(
'Profile',
'Account' => array(
'AccountSummary'
),
'Post' => array(
'PostAttachment' => array(
'fields' => array('id', 'name'),
'PostAttachmentHistory' => array(
'HistoryNotes' => array(
'fields' => array('id', 'note')
)
)
),
'Tag' => array(
'conditions' => array('Tag.name LIKE' => '%happy%')
)
)
)
));
Hope this gives you a push into the right direction.
using find it will get me the right data with this:
$this->set('products', $this->Product->find('all', array(
'contain' => array(
'Ranking' => array(
'Contact' => array(
'foreignKey' => 'contact_id',
'Customer' => array(
'foreignKey' => 'customer_id',
)
)
)
)
)));
When using the Paginator it looks like
$this->Paginator->settings['contain'] = array(
'Ranking' => array(
'Contact' => array(
'foreignKey' => 'contact_id',
'Customer' => array(
'foreignKey' => 'customer_id',
)
)
)
);
$this->Product->Behaviors->load('Containable');
$this->set('products', $this->Paginator->paginate());
Thanks so much!!

Joining two tables in CakePHP using bindModel method Cakephp

I am working on a cakephp 2.x .. I have two tables in my database both have the userid I am using bindModel.. my query is working fine ... I just have one problem .. I want to add the condition in where clause that
**where userid = $userid**
function getMessages($userid){
$this->bindModel(array(
'belongsTo' => array(
'Contact' => array(
'className' => 'Contact',
'foreignKey' => false,
'conditions' => array(
'Message.user_id = Contact.user_id',
'AND' =>
array(
array('OR' => array(
array('Message.mobileNo = Contact.mobileNo'),
array('Message.mobileNo = Contact.workNo'),
)),
)
),
'type' => 'LEFT',
)
)
), false);
return $this->find('all', array('conditions' => array(),
'fields' => array('Message.mobileNo'
),
'group' => 'Message.mobileNo',
'limit' => 6));
}
I am getting user id in parameter ... so I want to add the condition that get the following result where
message.userid and contact.userid = $userid ...
Just split the conditions in two lines like:
'conditions' => array(
'Contact.user_id' => $userid,
'Message.user_id = Contact.user_id',
[...]
)
But the approach that makes more sense is to leave the bind as is - after all the bind is generally between message users and contact users with the same id - and add the condition for the specific case in your find('all') with 'Message.user_id' => $userid.

HABTM and retrieving all Model A that does not has certain Model B

I have 3 tables: users, specialities, specialities_users.
User HABTM Speciality
Speciality HABTM User
SpecialitiesUser belongsTo Speciality,User
I have also model SpecialitiesUser.
When I want to get specialities for given user I can do it through
SpecialitiesUser->find('all' array('conditions' => array('user_id' => $given_user_id));
Now I want to get all specialities that user NOT belongs to. How I can do this in Cake?
The $other_specialities variable in the following code should be what you want:
$the_users_specialities = $this->SpecialitiesUser->find('list', array(
'conditions' => array(
'SpecialitiesUser.user_id' => $given_user_id
),
'fields' => 'SpecialitiesUser.speciality_id'
));
$other_specialities = $this->Speciality->find('all', array(
'conditions' => array(
'NOT' => array(
'Speciality.id' => $the_users_specialities
)
)
));
UPDATE: This is how to do it using a single query:
$db = $this->Speciality->getDataSource();
$sub_query = $db->buildStatement(
array(
'fields' => array('`SpecialitiesUser`.`speciality_id`'),
'table' => $db->fullTableName($this->SpecialitiesUser),
'alias' => 'SpecialitiesUser',
'conditions' => array('`SpecialitiesUser`.`user_id`' => $given_user_id),
),
$this->Speciality
);
$other_specialities = $this->Speciality->find('all', array(
'conditions' => array(
$db->expression('`Speciality`.`id` NOT IN (' . $sub_query . ')')
)
));

Trying to get "City" data from Event->Venue->City using 'contain' in CakePHP

I'm trying to return a list of events, and include the city where it's taking place. The city is only associated through the Event's Venue though.
Below is the code I'm using. It returns all the correct data, but it doesn't return ANY city data (other than the city_id field in Venue - which I'm not sure why it's returning).
Associations:
Event belongsTo Venue
Venue hasMany Event
Venue belongsTo City
City hasMany Venue
Code:
$this->Event->Behaviors->attach('Containable');
$events = $this->Event->find('all', array(
'limit' => 5,
'order' => 'Event.created DESC',
'fields' => array(
'name',
'description',
'phone',
'price_general',
'price_child',
'price_adult',
'price_child',
'tickets_url'
),
'contain' => array(
'Venue' => array(
'fields' => array(
'name',
'address',
'city_id',
),
'City' => array(
'fields' => array(
'City.name',
'state'
),
'conditions' => array(
'City.id' => 'Venue.city_id'
)
)
),
'Schedule' => array(
'fields'=>array(),
'Date' => array(
'conditions'=>array(
'Date.start >=' => $start_date,
'Date.start <=' => $end_date,
)
)
)
),
));
Bonus answer: (that I have currently asked in another StackOverflow question) - The Date conditions are supposed to filter which events show up, but instead, they're only filtering which Date data to show.
WORKING ANSWER: (thanks bancer)
$this->Event->recursive = -1;
$options['joins'] = array(
array('table' => 'schedules',
'alias' => 'Schedule',
'type' => 'LEFT',
'conditions' => array(
'Event.id = Schedule.event_id',
)
),
array('table' => 'dates',
'alias' => 'Date',
'type' => 'LEFT',
'conditions' => array(
'Date.schedule_id = Schedule.id',
)
)
);
$options['fields'] = array(
'Event.name',
'Schedule.start_date',
'Date.start',
);
$options['limit'] = 5;
$events = $this->Event->find('all', $options);
I would recommend to avoid using Containable. It generates too many queries in some cases. A better way for complex queries is to join tables "manually".
Another option I would consider at the first place is to search through 'Venue' model without using Containable like this: $this->Event->Venues->find('all', ...). As Venues directly associated with Cities and Events there should be possible to get what you want without extra complexities.
Update: take a look at the question How to change the sequence of 'joins' in CakePHP?
instead of containable, did you try including the city data in fields itself by fields=> array('','','City.name)

Cakephp $this->paginate with custom JOIN and filtering options

I've been working with cakephp paginations options for 2 days. I need to make a INNER Joins to list a few fields, but I have to deal with search to filter results.
This is portion of code in which I deal with search options by $this->passedArgs
function crediti() {
if(isset($this->passedArgs['Search.cognome'])) {
debug($this->passedArgs);
$this->paginate['conditions'][]['Member.cognome LIKE'] = str_replace('*','%',$this->passedArgs['Search.cognome']);
}
if(isset($this->passedArgs['Search.nome'])) {
$this->paginate['conditions'][]['Member.nome LIKE'] = str_replace('*','%',$this->passedArgs['Search.nome']);
}
and after
$this->paginate = array(
'joins' => array(array('table'=> 'reservations',
'type' => 'INNER',
'alias' => 'Reservation',
'conditions' => array('Reservation.member_id = Member.id','Member.totcrediti > 0' ))),
'limit' => 10);
$this->Member->recursive = -1;
$this->paginate['conditions'][]['Reservation.pagamento_verificato'] = 'SI';
$this->paginate['fields'] = array('DISTINCT Member.id','Member.nome','Member.cognome','Member.totcrediti');
$members = $this->paginate('Member');
$this->set(compact('members'));
INNER JOIN works good, but $this->paginations ignore every $this->paginate['conditions'][] by $this->passedArgs and I cannot have idea how I can work it out.
No query in debug, just the original INNER JOIN.
Can someone helps me ?
Thank you very much
Update:
No luck about it.
I've been dealing with this part of code for many hours.
If I use
if(isset($this->passedArgs['Search.cognome'])) {
$this->paginate['conditions'][]['Member.cognome LIKE'] = str_replace('*','%',$this->passedArgs['Search.cognome']);
}
$this->paginate['conditions'][]['Member.sospeso'] = 'SI';
$this->Member->recursive = 0;
$this->paginate['fields'] = array(
'Member.id','Member.nome','Member.cognome','Member.codice_fiscale','Member.sesso','Member.region_id',
'Member.district_id','Member.city_id','Member.date','Member.sospeso','Region.name','District.name','City.name');
$sospesi = $this->paginate('Member');
everything goes well, and from debug I receive the first condition and the conditions from $this->paginate['conditions'][]['Member.cognome LIKE'], as you can see
array $this->passedArgs
Array
(
[Search.cognome] => aiello
)
Array $this->paginate['conditions'][]
(
[0] => Array
(
[Member.cognome LIKE] => aiello
)
[1] => Array
(
[Member.sospeso] => NO
)
But, if I write the joins with paginate , $this->paginate['conditions'][] will ignore all the stuff, and give me from debug, just $this->paginate['conditions'][]['Reservation.pagamento_verificato'] = 'SI';
Another bit of information.
If I put all the stuff dealing with $this->paginate['conditions'][]['Reservation.pagamento_verificato'] = 'SI';
before the $this->paginate JOIN, nothing will be in $this->paginate['conditions'][].
This is an old question, so I'll just review how to do a JOIN in a paginate for others who got here from Google like I did. Here's the sample code from the Widget's Controller, joining a Widget.user_id FK to a User.id column, only showing the current user (in conditions):
// Limit widgets shown to only those owned by the user.
$this->paginate = array(
'conditions' => array('User.id' => $this->Auth->user('id')),
'joins' => array(
array(
'alias' => 'User',
'table' => 'users',
'type' => 'INNER',
'conditions' => '`User`.`id` = `Widget`.`user_id`'
)
),
'limit' => 20,
'order' => array(
'created' => 'desc'
)
);
$this->set( 'widgets', $this->paginate( $this->Widget ) );
This makes a query similar to:
SELECT widgets.* FROM widgets
INNER JOIN users ON widgets.user_id = users.id
WHERE users.id = {current user id}
And still paginates.
I'm not sure if you need those [] - try just doing this:
$this->paginate['conditions']['Reservation.pagamento_verificato'] = 'SI';
I use the conditions when I call paginate method.
$this->paginate($conditions)
This works ok for me, I hope it works for you!
If you have setted previous params, you may use:
$this->paginate(null,$conditions)
This might be help full to someone....
This is how I did complicated joins with pagination in cakephp.
$parsedConditions['`Assessment`.`showme`'] = 1;
$parsedConditions['`Assessment`.`recruiter_id`'] = $user_id;
$this->paginate = array(
'conditions' => array($parsedConditions ),
'joins' => array(
array(
'alias' => 'UserTest',
'table' => 'user_tests',
'type' => 'LEFT',
'conditions' => '`UserTest`.`user_id` = `Assessment`.`testuser_id`'
),
array(
'alias' => 'RecruiterUser',
'table' => 'users',
'type' => 'LEFT',
'conditions' => '`Assessment`.`recruiter_id` = `RecruiterUser`.`id`'
)
,
array(
'alias' => 'OwnerUser',
'table' => 'users',
'type' => 'LEFT',
'conditions' => '`Assessment`.`owner_id` = `OwnerUser`.`id`'
)
),
'fields' => array('Assessment.id', 'Assessment.recruiter_id', 'Assessment.owner_id', 'Assessment.master_id', 'Assessment.title', 'Assessment.status', 'Assessment.setuptype','Assessment.linkkey', 'Assessment.review', 'Assessment.testuser_email', 'Assessment.counttype_2', 'Assessment.bookedtime', 'Assessment.textqstatus', 'Assessment.overallperc', 'UserTest.user_id', 'UserTest.fname', 'UserTest.lname', 'RecruiterUser.company_name', 'OwnerUser.company_name'),
'limit' => $limit,
'order'=> array('Assessment.endtime' => 'desc')
);

Resources