I have 2 models product and image. Product has an Has-many relationship with Image. I want to fetch both products and images records based on product id.So I have written the query like this
$this->Product->find('all',array('conditions'=>array('product_id'=>230));
I am getting all product table entries but not image tables records. I checked with var_dump(), then images table entries are coming like this
array('Images =>
array
0 =>
array
...
1 =>
array
... );
What might be the problem? Any help would be appreciated.
Thanks in advance
Pushpa
$this->Product->find('first', array(
'conditions' => array('Product.id' => 230),
'recursive' => 1 // ensures we are retrieving related models
)
);
Related
i.e : i have 2 tables
Product ( id, name )
Photo ( id, name, photo_id )
And I need to get result in array like this:
array(
'id' => 1,
'name' => product,
'photos' => array(
array('id' => 1, 'name' => 'photo1')
array('id' => 2, 'name' => 'photo2')
)
}
Is it possible in PHP using clear SQL?
I know that is possible to get 2 arrays and connect it but I have many records and I dont want to wase time to quering.
You have to add a foreign_key in your photo table "product_id".
Then create a method getPhotos() in your Product class with will collect all photos for your product.
Is it possible in PHP using clear SQL?
Not in a single SQL call. With a single call, this is the closest you can get:
array(
'id' => 1,
'name' => product,
'photo_id' => 1,
'photo_name' => 'photo1')
),
array(
'id' => 1,
'name' => product,
'photo_id' => 2,
'photo_name' => 'photo2')
)
Your only choice for the format you want is to run queries separately or to combine them into the data structure you want.
As mentioned, this is not possible with SQL. SQL is based on the relational model which is a 1-Normal-Form data model. That means, the result relation is also flat (no nested relations in a relation).
However, there are good frameworks which generate intermediary models in your corresponding target language (e.g. Python, Java, ...) that circumvent the impression of a flat data model. Check for example Django.
https://docs.djangoproject.com/en/1.8/topics/db/models/
Moo
I'm fairly new to cakePHP and I'm trying to build a simple webapp.
I have a 3 Models: Property, Operation and Category.
My relationships are:
Category -> hasMany -> Property
Operation -> hasMAny -> Property
each property has a foreign key for the category (cat_id) and for the operation (op_id).
What I want to do is show each property (in the corresponding view) with the NAME of the category and operation (the field is 'name' in the respective tables), and not the ID's. How can I do this?
UPDATE:
An example of a desired output would be:
ID category operation description ....
1 House Sell a house ....
What I have now is
ID category operation description ....
1 2 3 a house ....
2 and 3 being the respective ID's of 'house'(category, cat_id) and 'sell'(operation, op_id)
This is the code of the Category model:
class Category extends AppModel{
public $hasMany = array(
'Property'=>array(
'foreignKey' => 'cat_id'
));
}
Thank you in advance.
properties(id, name, category_id, operation_id)
categories(id, name)
operations(id, name)
when you do a find on the property table with a recursive = 1 or 2 you will get all the related data automatically
Found the solution! Easier than I thought.
First, The use of 'hasMany' was not ideal. What I did was to use the relation "belongsTo" in the Property Model:
class Property extends AppModel{
var $name = 'Property';
public $belongsTo = array(
'Category' => array(
'foreignKey' => 'idCat'
),
'Operation' => array(
'foreignKey' => 'idOp'
)
);
}
Now the magic: Whenever I do a find() in the PropertiesController, What I've got is a two-dimensional array which contains the property data, along with the data of the tables it belongs to. :
Array
(
[Property] => Array
(
[id] => 1
)
[Category] => Array
(
[id] => 2
[name] => House
)
[Operation] => Array
(
[id] => 3
[name] => Sell
)
)
Now in the view, instead of, for instance, ['Property']['idCat'] I just use ['Category']['name'] and everything works.
Thanks for the help!
P.S.: sorry for any mis-understanding, not a native speaker.
I have a HABTM relationship between two tables: items and locations, using the table items_locations to join them.
items_locations also stores a bit more information. Here's the schema
items_locations(id, location_id, item_id, quantity)
I'm trying to build a page which shows all the items in one location and lets the user, through a datagrid style interface, edit multiple fields at once:
Location: Factory XYZ
___________________________
|___Item____|___Quantity___|
| Widget | 3 |
| Sprocket | 1 |
| Doohickey | 15 |
----------------------------
To help with this, I have a controller called InventoryController which has:
var $uses = array('Item', 'Location'); // should I add 'ItemsLocation' ?
How do I build a multidimensional form to edit this data?
Edit:
I'm trying to get my data to look like how Deceze described it below but I'm having problems again...
// inventory_controller.php
function edit($locationId) {
$this->data = $this->Item->ItemsLocation->find(
'all',
array(
"conditions" => array("location_id" => $locationId)
)
);
when I do that, $this->data comes out like this:
Array (
[0] => Array (
[ItemsLocation] => Array (
[id] => 16
[location_id] => 1
[item_id] => 1
[quantity] => 5
)
)
[1] => Array (
[ItemsLocation] => Array (/* .. etc .. */)
)
)
If you're not going to edit data in the Item model, it probably makes most sense to work only on the join model. As such, your form to edit the quantity of each item would look like this:
echo $form->create('ItemsLocation');
// foreach Item at Location:
echo $form->input('ItemsLocation.0.id'); // automatically hidden
echo $form->input('ItemsLocation.0.quantity');
Increase the counter (.0., .1., ...) for each record. What you should be receiving in your controllers $this->data should look like this:
array(
'ItemsLocation' => array(
0 => array(
'id' => 1,
'quantity' => 42
),
1 => array(
...
You can then simply save this like any other model record: $this->Item->ItemsLocation->saveAll($this->data). Adding an Item to a Location is not much different, you just leave off the id and let the user select the item_id.
array(
'location_id' => 42, // prepopulated by hidden field
'item_id' => 1 // user selected
'quantity' => 242
)
If you want to edit the data of the Item model and save it with a corresponding ItemsLocation record at the same time, dive into the Saving Related Model Data (HABTM) chapter. Be careful of this:
By default when saving a HasAndBelongsToMany relationship, Cake will delete all rows on the join table before saving new ones. For example if you have a Club that has 10 Children associated. You then update the Club with 2 children. The Club will only have 2 Children, not 12.
And:
3.7.6.5 hasAndBelongsToMany (HABTM)
unique: If true (default value) cake will first delete existing relationship records in the foreign keys table before inserting new ones, when updating a record. So existing associations need to be passed again when updating.
Re: Comments/Edit
I don't know off the top of my head if the FormHelper is intelligent enough to autofill Model.0.field fields from a [0][Model][field] structured array. If not, you could easily manipulate the results yourself:
foreach ($this->data as &$data) {
$data = $data['ItemsLocation'];
}
$this->data = array('ItemsLocation' => $this->data);
That would give you the right structure, but it's not very nice admittedly. If anybody has a more Cakey way to do it, I'm all ears. :)
i would like to paginate a list of games, where the sport is a chosen sport.
the relatition is as followed:
Game BelongsTo Competition BelongsTo Team BelongsTo sport
What i would like to do is show all games where the teams sport_id = 1.
The following doesn't work:
$this->paginate = array('limit' => 30, 'page' => 1,
'conditions' => array('Competition.Team.sport_id' => '1'),
'contain' => array('Competition', 'Competition.Team',
'Gamefield', 'Changingroom', 'ChangingroomAway', 'Gametype'),
'order'=>array('game_date'=>'asc'),
);
Can anyone help me with this one?
I've found my solution:
var $paginate=array(
'Game'=>
array(
'joins'=>array(
array('table'=>'competitions',
'alias'=>'Competition2',
'type'=>'left',
'conditions'=>array('Game.competition_id=Competition2.competition_id')
),
array('table'=>'teams',
'alias'=>'Team2',
'type'=>'left',
'conditions'=>array('Competition2.team_id=Team2.team_id')
),
),
'order'=>array('game_date'=>'asc'),
'contains'=>array('Competition2'=>array('Team2'))
));
function index() {
$datum = date('Y-m-d H:m');
$this->Game->recursive = 0;
$scope=array('OR' => array(array('Team2.sport_id' => 2), array('Team2.sport_id' =>3)), 'Game.game_date >' => $datum);
// Configure::write('debug',2);
$this->set('games', $this->paginate(null,$scope));
}
Thanks to TehThreag for helping me
Containable doesn't create any joins unless the relation would have been joined anyways, despite using Containable.
This means that for habtm, if you want a join you have to do as Michael did and specify them.
Also, doing a couple of joins should be faster with logical indexes than doing a habtm with Containable anyways, as the results from a contain for the same data as above would require an in ( id1,id2,...id# ) condition and a number of queries from there to fetch the individual related records.
The joins solution gets the data back in one db query.
I've got a series of Post models that hasAndBelongsToMany Media models. In certain function calls inside of the Post model, I don't need to retrieve the entire list of Media models. However, when I use the following code:
$this->unbindModel( array('hasAndBelongsToMany' => array('Media')) );
// Rebind to get only the fields we need:
$this->bindModel(
array('hasAndBelongsToMany' => array(
'Media' => array(
'className' => 'Media',
'joinTable' => 'media_posts',
'foreignKey' => 'post_id',
'associationForeignKey' => 'media_id',
'limit' => 1,
'fields' => array('Media.type', 'Media.path', 'Media.title')
)
)
)
);
$this->find('all', $params);
This limit only works on one of the first retrieved Post model and all following Post models have no associated Media:
Array
(
[0] => Array
(
[Profile] => Array
(
)
[Media] => Array
(
[0] => Array
(
[type] => photo
[path] => ''
[title] => ''
)
)
)
[1] => Array
(
[Profile] => Array
(
)
[Media] => Array
(
)
)
)
Any suggestions would be great. Thanks!
why not use the containable behaviour
// you would probably want the next line in the app_model ot be able to use it with all models
$this->Post->actsAs = array('Containable')
$params['conditions'] = array(
);
$params['contain'] = array(
'Media' => array(
'fields' => array(
'type', 'path', 'title'
),
'limit' => 1
)
);
$this->Post->find('all', $params);
EDIT:
Just tried that and got this sql (Module <-> Tag):
SELECT `Module`.`id` FROM `modules` AS `Module` WHERE 1 = 1
and
SELECT `Tag`.`id`, `ModulesTag`.`module_id`, `ModulesTag`.`tag_id`
FROM `tags` AS `Tag`
JOIN `modules_tags` AS `ModulesTag`
ON (`ModulesTag`.`module_id` IN (1, 2, 3, 4) AND `ModulesTag`.`tag_id` = `Tag`.`id`)
WHERE `Tag`.`belongs_to` = 'Module'
ORDER BY `Tag`.`name` ASC
LIMIT 1
obviously that cannot return the wanted result, as you would have to do a query for each Module result (which then again would result in way too many queries).
As a conclusion I would return all Tags (in my example) as the overhead in too many result rows is better than the overhead of too many queries..
Cake fetches all the Habtm-related records in one batch query and then assembles them into the results array afterwards. Any additional conditions you specify in the association will be used as is in the query, so it'll look something like this:
SELECT … FROM Media WHERE Media.id in (1, 2, 3, …) LIMIT 1
So it'll only retrieve a single HABTM model.
There's no apparently easy solution for this. Maybe you could think about the original premise again and why the "first" (LIMIT 1) record is supposedly the right one, maybe you can find a different condition to query on.
Failing that, you could rebind your models so Media has a hasMany relationship to medias_posts, the pivot table. For hasMany and belongsTo queries, Cake automatically does JOIN queries. You could use a GROUP BY clause then, which would give you the desired result:
SELECT … FROM Media JOIN medias_posts … GROUP BY medias_posts.post_id
You might also want to experiment with passing the 'join' parameter with the query, to achieve that effect without extensive rebinding.
$this->Media->find('all', array('join' => array(…), …));
Try this:
$this->yourModel->hasAndBelongsToMany['Media'] = false; // or null
And then set your HABTM association manually
$this->yourModel->hasAndBelongsToMany['Media'] = array(........);
Or simply modify the association without nulling it:
$this->yourModel->HABTM['Media']['fields'] = array(....)
CakePHP has a very powerful tool for this containable behaviour