Formatting CakePHP join statement - php

I'm using CakePHP 2.3.8 and I'm trying to more efficiently join two tables. buildings and building_rental_rates
buildings
id | name | description | property_owner | building_type
1 Big Big Building 1 1
building_rental_rates
id | building_type | rate_name | rate
1 1 daily 150.00
2 1 hourly 15.00
I want to look up a building and select the different rental rates. The tables are joined on building_type. Here's the find statement I have
$buildings = $this->Building ->find('all',array(
'Building.property_owner' => '1',
'fields' => array('Building.*','BuildingRentalRate.*'),
'joins' => array(
array(
'table' => 'building_rental_rates',
'alias' => 'BuildingRentalRate',
'type' => 'inner',
'conditions' => array(
'Building.building_type = BuildingRentalRate.building_type'
)
)
)
));
Here is the result
Array
(
[0] => Array
(
[Building] => Array
(
[id] => 1
[name] => Big
[description] => Big Building
[property_owner] => 1
[building_type] => 1
)
[BuildingRentalRate] => Array
(
[id] => 1
[building_type] => 1
[rate_name] => daily
[rate] => 150.00
)
)
[1] => Array
(
[Building] => Array
(
[id] => 1
[name] => Big
[description] => Big Building
[property_owner] => 1
[building_type] => 1
)
[BuildingRentalRate] => Array
(
[id] => 2
[building_type] => 1
[rate_name] => hourly
[rate] => 15.00
)
)
)
While the data is found properly, it's a pain to traverse through it. Can I use a join statement to result in this output? Notice how the BuildingRentalRate is an array containing all entries of that table that share the same building_type
Array
(
[0] => Array
(
[Building] => Array
(
[id] => 1
[name] => Big
[description] => Big Building
[property_owner] => 1
[building_type] => 1
)
[BuildingRentalRate] => Array
(
[0] => Array
(
[id] => 1
[building_type] => 1
[rate_name] => daily
[rate] => 150.00
)
[1] => Array
(
[id] => 2
[building_type] => 1
[rate_name] => hourly
[rate] => 15.00
)
)
)
)
I know Cake can output results like this when using model associations, but I wasn't able to get my associations correct apparently because it kept on joining Building.id on BuildingRentalRate.building_type (it should be Building.building_type = BuildingRentalRate.building_type)
Even with my association like this...
//Building.php
public $hasMany = array('BuildingRentalRate' => array('foreignKey' => 'building_type'));
//BuildingRentalRate.php
public $belongsTo = array('Building' => array('foreignKey' => 'building_type'));
It would join Building.id on BuildingRentalRate.building_type despite specificying building_type in both models as the foreign key.
Can I do some kind of nested SQL query in the conditions or is there an easier way of doing this?

you should be able to avoid using joins if in BuildingRentalRate.php you set your relationship this way
$public belongsTo = array(
'Building' => array(
'foreignKey' => false,
'conditions' => array
(
'Building.building_type' => 'BuildingRentalRate.building_type'
)
)
);

Related

Filter Pagination results on Contained column

I have two tables in my database Members and Memberitems. I created two models for it. one is, Member and another is Memberitem
Member model has hasMany relation to Memberitem, and Memberitem model has belongsTo relations with my Member model.
Memberitem entries has specific categorization based on color, like, Red, Pink, Green, etc.
Now I want to select all the members who has atleast one Pink color memberitem using pagination.
Currently I am using:
$this->paginate = array(
'limit' => 5,
'contain' => array(
'Memberitem' => array(
'conditions' => array('Memberitem.color' => 'Pink')
)
)
);
But its showing all the Members.
output is this:
Array
(
[0] => Array
(
[Member] => Array
(
[id] => 1
[first_name] => fh
[last_name] => g
)
[Memberitem] => Array
(
[0] => Array
(
[id] => 1
[name] => item2
[color]=> Pink
)
)
)
[1] => Array
(
[Member] => Array
(
[id] => 2
[first_name] => ad
[last_name] => vd
)
[Memberitem] => Array
(
)
)
[2] => Array
(
[Member] => Array
(
[id] => 3
[first_name] => ae
[last_name] => sdi
)
[Memberitem] => Array
(
[0] => Array
(
[id] => 3
[name] => item1
[color]=> Pink
)
)
)
)
Its showing this result. Member of empty memberitem is still there. I want only 1st and 3 record in result.
This is a classic :-) It takes a while to get it though.
The Cake manual has an excellent peace on this. You should read: http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html#containing-deeper-associations
So if you want only Members with certain MemberItems, you should query on the MemberItems and contain the Member Model. You can actually do this from Member Model:
$this->Member->Memberitem->find('all', array(
'conditions' => 'Memberitem.color = "Pink"',
'contain' => 'Member',
));
or maybe a bit more catered to you app:
$this->paginate['Memberitem'] = array(
'contain' => array('Member'),
'conditions' => array(
'Memberitem.color' => 'Pink',
),
);
$items = $this->paginate('Memberitem');
Hope that helps!

cakephp - one find() query to rule them all

I have the following model relationships defined:
class Publication extends AppModel {
var $name = 'Publication';
var $hasAndBelongsToMany = array(
'Author'=>array(
'className'=>'Author'
)
);
}
class Author extends AppModel {
var $name = 'Author';
var $hasAndBelongsToMany = array(
'Publication'=>array(
'className'=>'Publication'
)
);
var $belongsTo = array(
'College' => array (
'className' => 'College'
)
);
}
class College extends AppModel {
var $name = 'College';
var $hasMany = array(
'Department'=>array(
'className'=>'Department'
)
);
}
class Department extends AppModel {
var $name = 'Department';
var $belongsTo = array(
'College'=>array(
'className'=>'College'
)
);
}
The database tables are set up correctly (join tables for the HABTM, etc.). I am trying to find the one DB query to rule them all. I want to create a query that will find all of the publications with the associated authors, colleges, departments, etc. After getting data from a form, I have tried to run queries like this:
$conditions = array(
"Author.id" => $this->data['authors'],
"Publication.year" => $this->data['year']
);
$publications = $this->Publication->find('all', array('conditions' => $conditions));
This throws SQL errors saying that Author.id is not a valid field. Now, this is because a join with the 'authors' db table has not been completed by the time Author.id is being searched for. BUT, if I do this:
$pubs = $this->Publication->find('all', array('conditions' => $conditions));
then I get an array that has all of the Publications with all of the associated Authors (though, not the associated Colleges for some reason).
My question is this: what do I need to do to make the tables join before Author.id is searched for? I've attempted to use bindModel, containable, subqueries, but cannot get those to work for some reason (probably a ID10T error).
Thanks for any advice!
Edit:
The result of the following call:
$this->Publication->recursive = 2;
$pubs = $this->Publication->find('all');
are as follows:
Array (
[0] => Array (
[Publication] => Array ( [id] => 1 [title] => TestArticle [year] => 2011 [type_id] => 3 )
[Type] => Array ( [id] => 3 [type_name] => Journal Articles )
[Author] => Array (
[0] => Array ( [id] => 2 [firstname] => Jeremy [lastname] => Gustine [middle] => A [faculty] => 0 [college_id] => 4 [AuthorsPublication] => Array ( [id] => 3 [author_id] => 2 [publication_id] => 1 )
[College] => Array ( [id] => 4 [college_name] => Letters, Arts, and Sciences ) )
[1] => Array ( [id] => 3 [firstname] => George [lastname] => Obama [middle] => A [faculty] => 0 [college_id] => 6 [AuthorsPublication] => Array ( [id] => 2 [author_id] => 3 [publication_id] => 1 )
[College] => Array ( [id] => 6 [college_name] => School of Public Affairs ) )
[2] => Array ( [id] => 2 [firstname] => Jeremy [lastname] => Gustine [middle] => A [faculty] => 0 [college_id] => 4 [AuthorsPublication] => Array ( [id] => 1 [author_id] => 2 [publication_id] => 1 )
[College] => Array ( [id] => 4 [college_name] => Letters, Arts, and Sciences ) ) ) )
[1] => Array (
[Publication] => Array ( [id] => 2 [title] => TestBook [year] => 2010 [type_id] => 1 )
[Type] => Array ( [id] => 1 [type_name] => Books )
[Author] => Array (
[0] => Array ( [id] => 7 [firstname] => Sony [lastname] => Stuff [middle] => L [faculty] => 0 [college_id] => 5 [AuthorsPublication] => Array ( [id] => 4 [author_id] => 7 [publication_id] => 2 )
[College] => Array ( [id] => 5 [college_name] => Nursing and Health Science ) ) ) ) )
Hopefully that is somewhat readable...
Your second find is correct
i.e. $pubs = $this->Publication->find('all', array('conditions' => $conditions));
just above that try using recursive 2 (below code) to get related College too.
$this->Publication->recursive = 2;

shopping cart session array

When user click on the add button, the product id are stored into the session array.
See Code below:
Array
(
[storeID] => 123
[10] => Array
(
[quantity] => 1
[product_id] => 2
[extras_id] => Array
(
)
)
[20] => Array
(
[quantity] => 12
[product_id] => 2
[extras_id] => Array
(
8
)
)
)
As you can see 10 and 20 is option_id from the product_id = 2
User can select number of options from a specific product.
User can select extras (or without) from option
Is this array good design or how can it be improved?
Example:
Product (2): Burger
- Option (10): Large (User not selected any extra)
- Option (20): Small (User selected coke(8) as extra)
User selected ID 10 and 20 for burger.
I don't see a problem with it, except that you could get it more "organized", this way:
Array
(
[123] => array(
[2] => array(
[10] => array(
[quantity] => '',
[extras] => ''
),
[20] => array(
[quantity] => '',
[extras] => ''
)
)
)
)
But that's just my opinion and my way to think.
What if product 3 also has option 10?
I'd go for using product's as the key, and adding quantity, options and extras an subs of that array.
This setup does assume you can't add the same product more then once, even if the extra's do not match. Another poster suggest adding an combination of extras/options/productid, which is good. If that's the case, please upvote him :).
Array
(
'cart' => array(
'storeid' => 123,
'products' => array(
2 => array(
'quantity' => 2,
'options' => array(10, 20),
'extras' => array(2)
),
3 => array(
'quantity' => 12,
'options' => array(150, 20),
'extras' => array(1, 7)
)
)
)
)
What I prefer is to store products in this format:
Array
(
[md5 hash of (product id + serialized array of selected options)] => Array
(
'qty' => 10
'title' => 'Product XYZ'
'price' => 49.99
'options' => Array
(
...
)
)
)
That gives each cart item it's own "id", easily allowing you to modify data for each cart item, such as qty, if you need to.
Edited:
This is a trimmed down example of what my cart's items array looks like:
7483f8f0007eb9ef3ddb8d2bff606bd6 and 859d1bb225ba5d16de4d4c23076cfae0 are both md5 hashes created by md5($itemId.serialize($submittedOptions)).
Array
(
[7483f8f0007eb9ef3ddb8d2bff606bd6] => Array
(
[id] => 3
[qty] => 2
[price] => 20.00
[title] => Product XYZ
[data] => Array
(
[photo] => /uploads/media/products/product_xyz.jpg
[link] => /product/3-product-xyz/
[sku] => PRODUCT-XYZ
[weight] => 10
[attributes] => Array
(
...
)
)
)
[859d1bb225ba5d16de4d4c23076cfae0] => Array
(
[id] => 3
[qty] => 2
[price] => 30.00
[title] => Product XYZ
[data] => Array
(
[photo] => /uploads/media/products/product_xyz.jpg
[link] => /product/3-product-xyz/
[sku] => PRODUCT-XYZ
[weight] => 15
[attributes] => Array
(
...
)
)
)
)

Merge arrays from 2 SQL results

So i have a tags table setup in SQL
lists, lists_tags, tags
Each list can have multiple tags, and tags have two types genre and producer. Now if we are looking for all lists with tags 'action' these are the steps im following
SELECT GROUP_CONCAT(mini_lists_tags.list_id) AS list_ids
FROM (`mini_tags`)
LEFT JOIN `mini_lists_tags` ON `mini_lists_tags`.`tag_id` = `mini_tags`.`tag_id`
WHERE `mini_tags`.`tag_slug` = 'action'
That will return an array 1,2 for the ids of the list.
SELECT *
FROM (`mini_lists_anime`)
JOIN `mini_lists` ON `mini_lists`.`list_id` = `mini_lists_anime`.`list_id`
WHERE `mini_lists`.`list_id` IN ('1', '2')
AND `mini_lists`.`list_state` = 'active'
that gets all the lists in an array EXAMPLE:
Array
(
[0] => stdClass Object
(
[list_id] => 1
[list_episodes] => 13
[list_duration] => 24
[list_aired] => 1238623200
[list_age_rate] => PG-13 - Teens 13 or older
[user_id] => 1
[list_mal] => 5342
[list_category] => Anime
[list_type] => TV
[list_status] => Completed
[list_title] => Asura Cryin'
[list_alt_titles] => アスラクライン
[list_thumb] => 17071
[list_likes] => 0
[list_date] => 1300609723
[list_update] => 0
[list_state] => active
[list_info] =>
)
[1] => stdClass Object
(
[list_id] => 2
[list_episodes] => 26
[list_duration] => 23
[list_aired] => 1238623200
[list_age_rate] => PG-13 - Teens 13 or older
[user_id] => 1
[list_mal] => 329
[list_category] => Anime
[list_type] => TV
[list_status] => Completed
[list_title] => Planetes
[list_alt_titles] => プラネテス
[list_thumb] => 4822
[list_likes] => 0
[list_date] => 1300609723
[list_update] => 0
[list_state] => active
[list_info] =>
)
)
And then we get the tags
SELECT `mini_lists_tags`.`list_id`, `mini_tags`.`tag_type`, GROUP_CONCAT(mini_tags.tag_name) AS tag_names
FROM (`mini_lists_tags`)
INNER JOIN `mini_tags` ON `mini_tags`.`tag_id` = `mini_lists_tags`.`tag_id`
WHERE `mini_lists_tags`.`list_id` IN ('1', '2')
GROUP BY `mini_lists_tags`.`list_id`, `mini_tags`.`tag_type`
that gets all the tags in an array EXAMPLE:
Array
(
[0] => stdClass Object
(
[list_id] => 1
[tag_type] => Genre
[tag_names] => Supernatural,Action,Mecha
)
[1] => stdClass Object
(
[list_id] => 1
[tag_type] => Producers
[tag_names] => Seven Arcs
)
[2] => stdClass Object
(
[list_id] => 2
[tag_type] => Genre
[tag_names] => Romance,Action,Sci-fi,Comedy,Slice of Life,Drama,Space
)
[3] => stdClass Object
(
[list_id] => 2
[tag_type] => Producers
[tag_names] => Sunrise,Bandai Entertainment,Bandai Visual,Bang Zoom! Entertainment
)
)
Now the problem is I need to get them merged on the list_id so it returns something like this for each one.
stdClass Object
(
[list_id] => 1
[list_episodes] => 13
[list_duration] => 24
[list_aired] => 1238623200
[list_age_rate] => PG-13 - Teens 13 or older
[user_id] => 1
[list_mal] => 5342
[list_category] => Anime
[list_type] => TV
[list_status] => Completed
[list_title] => Asura Cryin'
[list_alt_titles] => アスラクライン
[list_thumb] => 17071
[list_likes] => 0
[list_date] => 1300609723
[list_update] => 0
[list_state] => active
[list_info] =>
[list_tags] => Array
(
[0] => stdClass Object
(
[tag_type] => Genre
[tag_names] => Mecha,Action,Supernatural
)
[1] => stdClass Object
(
[tag_type] => Producers
[tag_names] => Seven Arcs
)
)
)
Any advice is appreciated, i'm really lost. if there is better solution than this, i am all ears.
You can do another type of join that will return the parent item multiple times merged with each child object like so:
Array(
[0] => stdClass Object
(
[list_id] => 1
[list_episodes] => 13
[list_duration] => 24
...etc
[tag_type] => Genre
[tag_names] => Supernatural,Action,Mecha
...etc
)
[1] => stdClass Object
(
[list_id] => 1
[list_episodes] => 13
[list_duration] => 24
...etc
[tag_type] => Producers
[tag_names] => Seven Arcs
...etc
)
[2] => stdClass Object
(
[list_id] => 2
[list_episodes] => 26
[list_duration] => 23
...etc
[tag_type] => Genre
[tag_names] => Supernatural,Action,Mecha
...etc
)
[3] => stdClass Object
(
[list_id] => 2
[list_episodes] => 26
[list_duration] => 23
...etc
[tag_type] => Producers
[tag_names] => Seven Arcs
...etc
)
)
You will then need to loop through your results merging down the results into their child/parent relationships. This is because SQL always returns rows as results, not complex structures.
Although this is more complex to deal with, it's ususally less process intensive than making looped sql queries for each parent object (known as n+1 queries)

Joins in cakephp using find

I have used join query which joins three tables employee, places and employee_places.The join query gives me all the results as I have used find('all').But in the view page I just want to show employee name from employee table - place name from place table in a dropdown.
e.g:- emp1-place1,emp2-place2 and so on. Where should I give the fields name in find.
Look at the following code :
$options['joins'] = array(
array('table' => 'employees',
'alias' => 'emp',
'type' => 'inner',
'conditions' => array(
'emp.id = EmployeePlace.employee_id '
)
),
array('table' => 'places',
'alias' => 'pl',
'type' => 'inner',
'conditions' => array(
'pl.id = EmployeePlace.place_id'
)
)
);
$empTables = $this->Bill->EmployeePlace->find('all', $options);
$this->set(compact('empTables'));
The above query results the following array:-
Array
(
[0] => Array
(
[EmployeePlace] => Array
(
[id] => 1
[employee_id] => 1
[place_id] => 1
[Date] => 2011-02-02
)
[Employee] => Array
(
[id] => 1
[image] =>
[firstName] => Andy
[lastName] => Murray
[date_of_joining] => 2010-09-02
[date_of_leaving] => 2011-02-02
[date_of_birth] => 1991-08-10
[gender] => Male
[supervisor] => 0
[designation] => Manager
[user_id] => 0
)
[Place] => Array
(
[id] => 1
[placeName] => table-1
[section_id] => 1
[position] => Left
[seating_capacity] => 4
)
)
)
I just want firstName from employee - placeName from place table in the dropdown. How do I do this using condition in find.
Use containable: http://book.cakephp.org/view/1323/Containable
You will then only be dealing with data that you require.
Loop over the results and build the strings you want. Add the strings to an array indexed against the value you want the selection to return. Pass the array out to the view.
How are you going to handle the values? What do you want to do when a user has made a selection?
OR:
You could create a db view, which is a table that combines data from other tables on the fly. CakePHP can access this as a normal table.

Categories