I have a model template which hasmany themes.I want to show the list of templates with count of themes.I am using this
$this->Template->bindModel(
array(
'hasMany' => array(
'TemplateTheme' => array(
'className' => 'TemplateTheme',
'fields' => 'count(TemplateTheme.id) AS themes'
)
)
), false ...
it gives me 2 templates.But it gives me all the 3 themes count in the first template whereas 2 themes belongs to template 1 and the third theme belongs to template 2
in the query it is using id IN(template_id1,template_id2)
Any idea how to do this?
You are doing a common mistake, you are counting everyrow each time since you are not using group by, you should do is group by Template.id when you do your search. Butttttttt.... has many wont do a join :( so you have to force it a littleor use something like linkable component
example
$join = array(
array('table' => 'templateThemes',
'alias' => 'TemplateTheme',
'type' => 'LEFT',
'conditions' => array(
'Template.id = TemplateTheme.Template_id',
)
)
);
$fields = array('Template.id','count(TemplateTheme.id) AS themes');
$this->Template->find('all', array('fields'=>$fields, 'joins'=>$join', $group =>array('Template.id')));
You may also do it in reverse since belongsTo does the join something like this
in your model (it is always recommended to put it static in your model unless is not a normal association)
var belongsTo = array(
'Template'=> array(
'classname' => 'Template',
'foreign_key' => 'template_id'
);
and in controller
$fields = array('Template.id','count(TemplateTheme.id) AS themes');
$this->Template->find('all', array('fields'=>$fields, $group =>array('Template.id')));
Hope this helps you, if not just comment
Related
I have three Models Quotation, QuotationItem and Job.
My main Objective is to do a left join for tables of Quotation and QuotationItem in the Job controller. I cannot achieve it because instead of QuotationItem ,the job table is being used in left join!
$this->Job->unbindModel(
array('belongsTo' => array('Quotation','QuotationItem')), true
);
$options = array(
'fields' => array(
'QuotationItem.id',
'QuotationItem.Quot_id',
'QuotationItem.item_sno',
'QuotationItem.job_desc',
'QuotationItem.selected_qty',
'QuotationItem.paper_id',
'QuotationItem.plate_id',
'QuotationItem.design_id',
'QuotationItem.ink_id',
'QuotationItem.misel_id',
'QuotationItem.plate_size',
'QuotationItem.paper_size',
'QuotationItem.paper_type',
'QuotationItem.paper_gsm',
'QuotationItem.plate_qty',
'QuotationItem.paper_qty',
'QuotationItem.ink_qty',
'QuotationItem.plate_color',
'QuotationItem.ink_color',
'QuotationItem.ink_code',
'QuotationItem.plate_price',
'QuotationItem.paper_price',
'QuotationItem.ink_price',
'QuotationItem.design_price',
'QuotationItem.plate_total',
'QuotationItem.paper_total',
'QuotationItem.ink_total',
'QuotationItem.design_total',
'QuotationItem.printing_cost',
'QuotationItem.prepress_cost',
'QuotationItem.design_cost',
'QuotationItem.press_cost',
'QuotationItem.folding_cost',
'QuotationItem.binding_cost',
'QuotationItem.block_cost',
'QuotationItem.lamination_cost',
'QuotationItem.uv_cost',
'QuotationItem.stamping_cost',
'QuotationItem.diecutting_cost',
'QuotationItem.sewing_cost',
'QuotationItem.perfectbind_cost',
'QuotationItem.saddlestitch_cost',
'QuotationItem.emboss_cost',
'QuotationItem.cutting_cost',
'QuotationItem.labor_charges',
'QuotationItem.others',
'QuotationItem.cost_total',
'QuotationItem.total_item_sum',
'QuotationItem.net_total',
'QuotationItem.created',
'QuotationItem.modified',
'QuotationItem.status',
'Quotation.Quot_id',
'Quotation.job_item',
'Quotation.customer_id',
'Quotation.customer_name',
'Quotation.customer_phone',
'Quotation.customer_email',
'Quotation.customer_address',
'Quotation.salesperson',
'Quotation.pay_terms',
'Quotation.contact_id',
'Quotation.status',
'Quotation.discount',
'Quotation.total',
'Quotation.created',
'Quotation.modified',
),
'joins' => array(
array(
'table' => 'quotation',
'alias' => 'Quotation',
'type' => 'left',
'conditions' => array('QuotationItem.Quot_id = Quotation.Quot_id'),
)
),
'conditions' => array(
'1',
));$data = $this->Job->find('all', $options);`
You need to run the find on Quotation, not on Job. Models are tied to a table, think of them as one-in-the-same. You can't find something from a different table that has nothing to do with the Model you're executing the find function on.
If you're getting sql 42000 error. Table/alias not unique when loading a model, it's either already been loaded or you're trying to alias something with a name that's already been used for something else - which you can't do.
The simplest way to ensure you have access to all 3 Models from the Job Controller if you haven't grasped model associations well yet is to declare public $uses = array('Job','Quotation','QuotationItem'); in your Job Controller.
Whatever way you go about getting access to Quotation aside, what you want to do is then:
$this->Quotation->find('all',array(
'fields' => '*',
'joins' => array(
array(
'table' => 'quotation',
'alias' => 'Quotation',
'type' => 'left',
'conditions' => array(
'QuotationItem.Quot_id = Quotation.Quot_id'
)
)
)
));
Thank you #Tim for making me understand about model and controller. I have found the answer to my Question.
I used the join in my QuotationItem model since the QuotationItem belongs to Quotation .
public $belongsTo = array(
'Quotation' => array(
'className' => 'Quotation',
'foreignKey' => 'Quot_id',
'type'=>'left',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
Then I declared public $uses = array('Job','Quotation','QuotationItem'); in my jobs controller
after that in my add function I used
$quotationItem=$this->QuotationItem->find('all');
$this->set(compact( 'quotationItem'));
Finally I got my joins working
I`m having a problem with the Containable behaviour.
I would like to know if there is any way to access the contained model attributes for operations like ordering.
For example, I have a model B which belongs to a Model A. I need to order objects of B using an attribute (integer) of A. It would be something like:
'contain' => array(
'A' => array(
'B' => array(
'order' => 'A.integer_attribute'
)
)
)
I know that there are easier ways to do this without Containable, but for reasons which are not worth being detailed here, I need to use it. This is an abstract example, in truth model A belongs to other models and this is just a small part of a deep containable tree.
I'd be very glad with any help!
EDIT
OK, I'll try my best to describe the situation without being unclear:
I have 4 models: User, Category, Field and UserField, and their relationships are as follows:
Category hasMany User
Category hasMany Field
User hasMany UserField
Field hasMany UserField
The opposite of these relations are all belongsTo. The purpose here is that the user belongs to a category, which has many fields that he needs to fill with his information (city, state etc). The information he fills is stored in the UserField table, as each information needs to have its field and the user who provided it.
That said, I need to build a screen which displays, for each category, a list of users and their information. So, I retrieve all the categories and its fields, so I can build a table for each category. Each field has an attribute "no_order", which is the number that indicates the order in which the field appears.
At the same time, I need all of each category's users to display them correctly in the tables. Finally, and there's the problem, I need to have UserField objects ordered by the "no_order" of their respective fields, for each user. So I ended up with something like:
$categories = $this->Category->find('all', array(
'order' => 'Category.name',
'contain' => array(
'Field',
'User' => array(
'UserField' => array(
'order' => 'Field.no_order'
)
)
)
));
But this doesn't work, since UserField cannot reach its respective Field's no_order from there.
I apologize if this wasn't clear enough, but for anyone who would spend a little while reading this, I would be VERY grateful for your help!
I am not shure for what do you want so, but I thing that should use joins of cakephp
$A = $this->A->find('all', array(
'joins' => array(
array(
'table' => 'B',
'alias' => 'B',
'type' => 'LEFT',
'conditions' => array(
'B.field_id = A.id'
)
)
),
'conditions' => array(
'B.field' => 'if_you_want_more_conditions'
),
'contain' => array(
'A' => array(
'B' => array(
'order' => 'A.integer_attribute'
)
)
),
'fields' => array('A.field','B.field'),
'recursive' => -1
));
I finally came up with a solution. I don't know how efficient it is, but there it goes:
'contain' => array(
'Field',
'User' => array(
'User.privilege = "Solicitante"'
'UserField' => array(
'order' => '(SELECT f.no_order FROM fields AS f WHERE UserField.field_id = f.id LIMIT 1)'
)
)
)
Having the raw query solved my problem. Hope it helps anybody who comes across a similar problem!
hi guys i am new to cakephp
now i'm facing a little big problem
here is the situation
i hava a shop that hasMany Catalogs which is related to many products each product has a category
i want to fetch them all just by getting the shop
i dont know how to do it
trying to use hasMAny gives me just the ids
instead is there any way to get shop inside it array of catalogs each catalog has Product's array which has one array of category
thank you
Ok, I'm on my computer now :).
In ShopModel:
class ShopModel extends AppModel {
public $hasMany = array(
'Catalog' => array(
// binding params here...
),
);
}
In CatalogModel:
class CatalogModel extends AppModel {
public $hasMany = array(
'Product' => array(
// binding params...
),
),
}
... and this goes on...
If you don't want to get excessive data in all actions, you should set in AppModel:
class AppModel extends Model {
public $recursive = -1;
}
In the controller action where you call the find function with associations:
$this->Shop->Behaviors->load('Containable');
$big_array = $this->Shop->find('all', array(
'conditions' => array(
//...
),
'contain' => array(
'Catalog' => array(
'Product' => array(
// etc, you get the point :)
),
),
),
));
It is also nice to declare the $belongsTo associations too, so you can access anything from anywhere, something like this:
$this->Catalog->Behaviors->load('Containable');
$big_array = $this->Catalog->find('all', array(
'conditions' => array(
//...
),
'contain' => array(
'Product' => array(
// ...
),
'Shop' => array(
// ...
),
),
));
EDIT
I see you have a Product->Category relation that i guess would be defined with $belongsTo. If you do a query like the one above, you will get a lot of duplicate queries (same category in many products). You can use $this->Category->find('list') but very often I find this inappropriate as it is returning only one field (I would be grateful if someone knows a way how can I get more fields with the list type). For this purpose, my workaround is making a custom function in the Category model like this:
class Category extends AppModel {
public function getSorted ($options = array()) {
$temp= $this->find('all', $options);
$output = array();
foreach ($temp[$this->alias] as $row) {
$output[$this->alias][$row['id']] = $row;
}
unset($temp);
return $output;
}
}
Then in the controller I would declare two arrays, the big one without category association and the category list one:
$this->loadModel('Category');
$this->set('categories', $this->Category->getSorted());
This way, I can get the needed category row by category id wherever i need it in the view.
Do not use CakePHP association they are not good handling complex relationships, you will later face problems with....Instead create all your join queries on the fly...I am giving you one example below:
Create one function inside Shop model and join catalog and product as shown below:
$options = array(
'conditions' => array('Product.id'=>9),
'joins' => array(
array(
'alias' => 'Catalog',
'table' => 'catalogs',
'type' => 'LEFT',
'conditions' => array(
'Catalog.product_id = Product.id',
),
),
array(
'alias' => 'Product',
'table' => 'products',
'type' => 'LEFT',
'conditions' => array(
'Shop.id = Product.shop_id',
),
)
),
'fields' => array('Product.*'),
'group' => array('Product.id')
);
$returnData = $this->find('all',$options);
This will make coding little easier and you can escape from associations!
I have hasMany Through table which is Chats table with Chat model and I'm using loadModel in User controller to load Chat model then ran below query to bindModel with Chat.user_id and User.id :
$this->loadModel('Chat');
$this->Chat->bindModel(array(
'belongsTo' => array(
'User' => array(
'foreignKey' => false,
'conditions' => array('Chat.user_id = User.id')
)
)
));
$lastChat = $this->Chat->find('all', array(
'conditions' => array(
'Chat.receiver_id' => $user_id['User']['id']
),
'order' => array('Chat.id DESC'),
'fields' => array(
'Chat.id',
'Chat.chat',
'Chat.user_id',
'Chat.receiver_id',
'Chat.read',
'Chat.created'
),
'group' => array('Chat.user_id')
));
I want to join those tables together but this does not seem to work in Cake way I tried with normal SQL query and it works fine.
What could be wrong here?
Have you tried setting the recursive property before the find? Eg:
$this->Chat->recursive = 3;
You may need to set this after $this->Chat->bindModel, but I am not sure if this will make a difference or not. You will also need to re-bind the User model before each find, if, for example, your find queries are run within a loop ...
I'm using the Containable behavior to get a list of Comments (belongsTo Post, which belongs to Question; Question hasMany Post, and Post hasMany Comments; all of these belong to Users).
$data = $this->Question->find ( 'first',
array ('contain' =>
array ('User',
'Post' => array ('User', /* 'order' => 'User.created DESC'*/ )
)
)
);
It works, when I comment out the section in comments above. I suppose this is to be expected, but what I want is all of the Posts that are found, should be sorted in order of the 'created' field of the 'User' they belong to. How do I accomplish this deeper level sorting in CakePHP? I always get, "Warning (512): SQL Error: 1054: Unknown column 'User.created' in 'order clause'"
Thanks for your help!
Also, you might be trying to group on a related table from a find call that doesn't use joins.
Set your debug level to something greater than 1 so you can see the query log and make sure that Cake isn't doing two queries to fetch your data. If that is the case then the first query is not actually referencing the second table.
If you want to manually force a join in these situations you can use the Ad-Hoc joins method outlined by Nate at the following link.
http://bakery.cakephp.org/articles/view/quick-tip-doing-ad-hoc-joins-in-model-find
I have found two ways to get around this.
The first is to define the second level associacion directly in the model.
Now you will have access to this data everywhere.
It should look something like this.....
var $belongsTo = array(
'Foo' => array(
'className' => 'Foo', //unique name of 1st level join ( Model Name )
'foreignKey' => 'foo_id', //key to use for join
'conditions' => '',
'fields' => '',
'order' => ''
),
'Bar' => array(
'className' => 'Bar', //name of 2nd level join ( Model Name )
'foreignKey' => false,
'conditions' => array(
'Bar.id = Foo.bar_id' //id of 2nd lvl table = associated column in 1st level join
),
'fields' => '',
'order' => ''
)
);
The problem with this method is that it could make general queries more complex than they need be.
You can thus also add the second level queries directly into te find or paginate statement as follows: (Note: I found that for some reason you can't use the $belongsTo associations in the second level joins and will need to redefine them if they are already defined. eg if 'Foo' is already defined in $belongsTo, you need to create a duplicate 'Foo1' to make the association work, like the example below.)
$options['joins'] = array(
array('table' => 'foos',
'alias' => 'Foo1',
'type' => 'inner',
'conditions' => array(
'CurrentModel.foo_id = Foo1.id'
)
),
array('table' => 'bars',
'alias' => 'Bar',
'type' => 'inner',
'foreignKey' => false,
'conditions' => array(
'Bar.id = Foo1.bar_id'
)
)
);
$options['conditions'] = array('Bar.column' => "value");
$this->paginate = $options;
$[modelname] = $this->paginate();
$this->set(compact('[modelname]'));
I hope this is clear enough to understand and that it helps someone.
Check your recursive value. If it's too limiting, it will ignore the containable links, IIRC. I remember bumping into this a few times. I'd try containing multiple models, but my recursive option was set to 0 and nothing would get pulled. For your example, I'd think that a value of 1 (the default) would suffice, but maybe you've explicitly set it to 0 somewhere?
You can add before your call to find() the following:
$this->Question->order = 'Question.created DESC';
Yeah, I couldn't work out how to sort based on the related/associated model, so ended up using the Set::sort() method. Checkout this article for a good explanation.
// This finds all FAQ articles sorted by:
// Category.sortorder, then Category.id, then Faq.displaying_order
$faqs = $this->Faq->find('all', array('order' => 'displaying_order'));
$faqs = Set::sort($faqs, '{n}.Category.id', 'ASC');
$faqs = Set::sort($faqs, '{n}.Category.sortorder', 'ASC');
...And yes, it should probably be a Category->find() but unfortunately the original developer didn't code it that way, and I didn't wanna rework the views.