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 ...
Related
I have the following problem with CakePHP:
Two tables are joined (filters and accounts). Then I am building conditions and only the second condition Account.active =>1 gets executed. If I print the result, there are still showing filters that are having another mode_id than 3.
$joins= array(
array('table' => 'filters',
'alias' => 'Filter',
'type' => 'right',
'conditions' => array(
'Filter.account_id = Account.id',
)
),
);
Then I execute the request including joins and conditions
$activeAccounts = $this->Account->find('all',array(
'conditions'=>array('AND'=>array('Filter.mode_id'=>3,'Account.active'=>1)),
'joins'=>$joins));
The models were checked and no problems identified. Filter belongs to Account. Account has many Filter.
Below the query that is generated. The results are still showing filters with Filter.mode_id other than 3
Here is the query that is generated. The results are still containing rows with Filter.mode_id other than 3 despite the fact that one condition is 'Filter.mode_id'=>3
SELECT `Account`.`id`, `Account`.`user_id`, `Account`.`name`,
`Account`.`api_key`, `Account`.`account_number`, `Account`.`remaining_balance`,
`Account`.`investment_size`, `Account`.`active`
FROM `baseline_db`.`accounts` AS `Account`
right JOIN `baseline_db`.`filters` AS `Filter`
ON (`Filter`.`account_id` = `Account`.`id`)
WHERE ((`Filter`.`mode_id` = 3) AND
(`Account`.`active` = '1'))
Like say Oldskool, use the Model associations
and for your condition, The "AND" is not necessary,
you cant put :
$activeAccounts = $this->Account->find('all',array(
'conditions' => array(
'Filter.mode_id'=>3,
'Account.active'=>1
)
));
the request you want to make with the type of relation you have, seem to me weird.
If i understand, perhaps with something like that :
$this->loadModel('Filter');
$filters =$this->Filter->find("list", array(
'conditions' => array('Filter.mode_id' => 3),
'fields' => array('Filter.account_id')
));
$activeAccounts = $this->Account->find('all',array(
'conditions' => array(
'Account.account_id'=>$filters,
'Account.active'=>1
)
));
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 have a Cake Application v2.3.1 running locally on mamp v2.1.3 (not pro so this is the latest version) from myapp.dev:port and I'm experiencing some really slow response when I have pagination on the site (about 5 seconds), here's my code in the controller:
public $paginate = array(
'fields' => array('Artist.id, Artist.year_born, Artist.year_died, Artist.country_born'),
'limit' => 50,
'order' => array('Artist.id' => 'desc')
);
I use the twitter bootstrap plugin for my pagination https://github.com/slywalker/TwitterBootstrap like this in AppController.php:
public $helpers = array('Paginator' => array('className' => 'TwitterBootstrap.BootstrapPaginator'));
I tried to set the limit of the pagination to 1 and also tried using cakes own pagination instead of bootstrap, but it's still really slow. I has to be something with the pagination because I can access an artist like myapp.dev:port/artists/view/14532 and that works good so I don't think it has anything to do with the sql query.
I tried around changing names in the /etc/hosts file 127.0.0.1 localhost myapp.dev and ::1 localhost myapp.dev but nothing seems to work. Any ideas? I'm really stuck.
Update: Feel a bit bad about not mentioning the hasMany relationship that my Artist table have. This is my Artist model:
class Artist extends AppModel {
public $hasMany = array(
'ArtistBiography' => array('dependent' => true),
'ArtistSurname' => array('dependent' => true),
'ArtistSignature' => array('dependent' => true),
'ArtistForename' => array('dependent' => true),
'ArtistMonogram' => array('dependent' => true));
public $hasOne = array(
'ArtistActive' => array('dependent' => true));
}
I used the DebugKit and I found that the paginator does 2 unnecessary querys for two left joins that both takes around 2000 ms each to execute. How can I tell the paginator to ignore this? I tried something like this to give a new relationship:
public $paginate = array(
'fields' => array('Artist.id, Artist.year_born, Artist.year_died, Artist.country_born'),
'limit' => 50,
'order' => array('Artist.id' => 'desc'),
'joins' => array(
array('table' => 'artist_forenames', 'alias' => 'ArtistForename', 'type' `=> 'inner', 'conditions' => array('Artist.id = ArtistForename.artist_id')),`
array('table' => 'artist_surnames', 'alias' => 'ArtistSurname', 'type' => 'INNER', 'conditions' => array('Artist.id = ArtistSurname.artist_id'))),
'recursive' => -1
);
But I'm not sure how to make this work. What I want basically is to join the artist_surnames and artist_fornames and ignore two tables called artist.actives and artist_monogram that both together slows down the query with 4000ms.
My second question here is why do these two joins slows it down so much? I did a test-view called artists/all where I'm listing 100 artists and there my query with all the joins is done in around 40-50ms.
TLDR:
Set $recursive to -1
Use CakePHP's Containable Behavior to only retrieve the associated data you want
Example of code:
//wherever you set this variable
$paginate = array(
'recursive' => -1,
'limit' => 50,
'order' => array('Artist.id' => 'desc'),
'contain' => array(
'ArtistSurname',
'ArtistForName'
)
);
Remember to look through the documentation for Containable - it explains how you set your Model(s) to $actsAs Containable, set recursive to -1...etc etc.
I suggest actually setting public $recursive=-1' and public $actsAs =array('Containable'); both in your AppModel - that way ALL models are set up and ready to go with Containable whenever you want. Also, anything higher than -1 for recursive is bad IMHO - Containable is so much better in every respect.
I am stuck on pagination in CakePHP 1.3. I am trying to paginate feePayment records based on certain criteria.
feePayments belongs to Students which in turn belongs YearGroups.
I want to paginate 'unpaid' feePayments for each year group. The problem I am having is that the SQL query seems to only take into account the conditions I specified for the FeePayment model and ignores the YearGroup criteria so only overdue unpaid records are returned regardless of the year group specified.
Here is my code:
function unpaidClass($id) {
$this->paginate = array(
'FeePayment' => array ('recursive' => 1, 'conditions' => array('FeePayment.status' => 'Unpaid', 'FeePayment.due_date <= ' => date("Y-m-d"))),
'YearGroup' => array ('recursive' => 1, 'conditions' => array('YearGroup.school_year' => $id))
);
$this->set('feePayments', $this->paginate());
}
Hope this makes sense, appreciate any help.
Thanks,
Sid.
You should consider using the Containable behavior. This behavior allows you to group the necessary data you want without relaying on the "recursiveness" of your query. You can place conditions on your contained data similar to the way you would in your queries and is a more permanent way to structure data across your application instead of specifying conditions over and over again in each query.
Paginate should automatically pick up these associations when you Paginate your main model. Here's an example of what I mean here: http://cakephp.1045679.n5.nabble.com/Paginate-with-Containable-td1300971.html#a1300971
These should make your task easier.
After a lot of searching the net and reading CakePHP's documentation, here is the solution I came up with:
function unpaidClass($id) {
$this->FeePayment->unbindModel(array(
'belongsTo' => array('Student')
), $reset = 0);
$this->FeePayment->bindModel(array(
'belongsTo' => array(
'Student' => array(
'foreignKey' => false,
'conditions' => array('Student.id = FeePayment.student_id')
),
'YearGroup' => array(
'foreignKey' => false,
'conditions' => array('YearGroup.id = Student.year_group_id')
)
)
), $reset = 0);
$this->paginate = array(
'contain' => array('Student','YearGroup'),
'conditions' => array('YearGroup.school_year' => $id,
'FeePayment.status' => 'Unpaid',
'FeePayment.due_date <= ' => date("Y-m-d")));
$this->set('feePayments', $this->paginate());
}
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.