Hi,
I need to do the following query using the CakePHP find method:
SELECT *
FROM `messages`
INNER JOIN users ON messages.from = users.id
WHERE messages.to = 4
ORDER BY messages.datetime DESC
Basically I have:
messages table with a Message model
users table with User model
and want to retrieve information from both tables in one query. The users.id field is the same as the messages.from field, so that's what the join is on.
I am doing it in my MessagesController so it would need to be something like:
$this->Message->find();
Thanks
There are two main ways that you can do this. One of them is the standard CakePHP way, and the other is using a custom join.
It's worth pointing out that this advice is for CakePHP 2.x, not 3.x.
The CakePHP Way
You would create a relationship with your User model and Messages Model, and use the containable behavior:
class User extends AppModel {
public $actsAs = array('Containable');
public $hasMany = array('Message');
}
class Message extends AppModel {
public $actsAs = array('Containable');
public $belongsTo = array('User');
}
You need to change the messages.from column to be messages.user_id so that cake can automagically associate the records for you.
Then you can do this from the messages controller:
$this->Message->find('all', array(
'contain' => array('User')
'conditions' => array(
'Message.to' => 4
),
'order' => 'Message.datetime DESC'
));
The (other) CakePHP way
I recommend using the first method, because it will save you a lot of time and work. The first method also does the groundwork of setting up a relationship which can be used for any number of other find calls and conditions besides the one you need now. However, cakePHP does support a syntax for defining your own joins. It would be done like this, from the MessagesController:
$this->Message->find('all', array(
'joins' => array(
array(
'table' => 'users',
'alias' => 'UserJoin',
'type' => 'INNER',
'conditions' => array(
'UserJoin.id = Message.from'
)
)
),
'conditions' => array(
'Message.to' => 4
),
'fields' => array('UserJoin.*', 'Message.*'),
'order' => 'Message.datetime DESC'
));
Note, I've left the field name messages.from the same as your current table in this example.
Using two relationships to the same model
Here is how you can do the first example using two relationships to the same model:
class User extends AppModel {
public $actsAs = array('Containable');
public $hasMany = array(
'MessagesSent' => array(
'className' => 'Message',
'foreignKey' => 'from'
)
);
public $belongsTo = array(
'MessagesReceived' => array(
'className' => 'Message',
'foreignKey' => 'to'
)
);
}
class Message extends AppModel {
public $actsAs = array('Containable');
public $belongsTo = array(
'UserFrom' => array(
'className' => 'User',
'foreignKey' => 'from'
)
);
public $hasMany = array(
'UserTo' => array(
'className' => 'User',
'foreignKey' => 'to'
)
);
}
Now you can do your find call like this:
$this->Message->find('all', array(
'contain' => array('UserFrom')
'conditions' => array(
'Message.to' => 4
),
'order' => 'Message.datetime DESC'
));
Otro example, custom Data Pagination for JOIN
CODE in Controller CakePHP 2.6 is OK:
$this->SenasaPedidosFacturadosSds->recursive = -1;
// Filtro
$where = array(
'joins' => array(
array(
'table' => 'usuarios',
'alias' => 'Usuarios',
'type' => 'INNER',
'conditions' => array(
'Usuarios.usuario_id = SenasaPedidosFacturadosSds.usuarios_id'
)
),
array(
'table' => 'senasa_pedidos',
'alias' => 'SenasaPedidos',
'type' => 'INNER',
'conditions' => array(
'SenasaPedidos.id = SenasaPedidosFacturadosSds.senasa_pedidos_id'
)
),
array(
'table' => 'clientes',
'alias' => 'Clientes',
'type' => 'INNER',
'conditions' => array(
'Clientes.id_cliente = SenasaPedidos.clientes_id'
)
),
),
'fields'=>array(
'SenasaPedidosFacturadosSds.*',
'Usuarios.usuario_id',
'Usuarios.apellido_nombre',
'Usuarios.senasa_establecimientos_id',
'Clientes.id_cliente',
'Clientes.consolida_doc_sanitaria',
'Clientes.requiere_senasa',
'Clientes.razon_social',
'SenasaPedidos.id',
'SenasaPedidos.domicilio_entrega',
'SenasaPedidos.sds',
'SenasaPedidos.pt_ptr'
),
'conditions'=>array(
'Clientes.requiere_senasa'=>1
),
'order' => 'SenasaPedidosFacturadosSds.created DESC',
'limit'=>100
);
$this->paginate = $where;
// Get datos
$data = $this->Paginator->paginate();
exit(debug($data));
OR Example 2, NOT active conditions:
$this->SenasaPedidosFacturadosSds->recursive = -1;
// Filtro
$where = array(
'joins' => array(
array(
'table' => 'usuarios',
'alias' => 'Usuarios',
'type' => 'INNER',
'conditions' => array(
'Usuarios.usuario_id = SenasaPedidosFacturadosSds.usuarios_id'
)
),
array(
'table' => 'senasa_pedidos',
'alias' => 'SenasaPedidos',
'type' => 'INNER',
'conditions' => array(
'SenasaPedidos.id = SenasaPedidosFacturadosSds.senasa_pedidos_id'
)
),
array(
'table' => 'clientes',
'alias' => 'Clientes',
'type' => 'INNER',
'conditions' => array(
'Clientes.id_cliente = SenasaPedidos.clientes_id',
'Clientes.requiere_senasa = 1'
)
),
),
'fields'=>array(
'SenasaPedidosFacturadosSds.*',
'Usuarios.usuario_id',
'Usuarios.apellido_nombre',
'Usuarios.senasa_establecimientos_id',
'Clientes.id_cliente',
'Clientes.consolida_doc_sanitaria',
'Clientes.requiere_senasa',
'Clientes.razon_social',
'SenasaPedidos.id',
'SenasaPedidos.domicilio_entrega',
'SenasaPedidos.sds',
'SenasaPedidos.pt_ptr'
),
//'conditions'=>array(
// 'Clientes.requiere_senasa'=>1
//),
'order' => 'SenasaPedidosFacturadosSds.created DESC',
'limit'=>100
);
$this->paginate = $where;
// Get datos
$data = $this->Paginator->paginate();
exit(debug($data));
$services = $this->Service->find('all', array(
'limit' =>4,
'fields' => array('Service.*','ServiceImage.*'),
'joins' => array(
array(
'table' => 'services_images',
'alias' => 'ServiceImage',
'type' => 'INNER',
'conditions' => array(
'ServiceImage.service_id' =>'Service.id'
)
),
),
)
);
It goges to array is null.
Related
I need help for the simple relationchip in cakephp 2.x, I believe this right however not working:
Customer.php (Model)
public $hasAndBelongsToMany = array(
'Note' => array(
'className' => 'Note',
'joinTable' => 'customers_notes',
'foreignKey' => 'customer_id',
'associationForeignKey' => 'note_id',
'unique' =>false,
'dependent'=>true,
)
);
Note.php (Model)
public $hasAndBelongsToMany = array(
'Customer' => array(
'className' => 'Customer',
'joinTable' => 'customers_notes',
'foreignKey' => 'note_id',
'associationForeignKey' => 'customer_id',
'unique' =>false,
'dependent'=>true
)
);
CustumersController.php (Controller I make the find)
public function admin_notes($id=null){
$this->layout="Adm.admin";
$all = $this->Customer->Note->find('all', array(
'limit' => 10,
'conditions' => array(
"CustomersNote.customer_id" => $id
),
'order'=>'CustomersNote.id DESC'
));
$this->set('notes', $all);
}
The error
SQL Query: SELECT `Note`.`id`, `Note`.`titulo`, `Note`.`conteudo`, `Note`.`created`, `Note`.`modified`, `Note`.`user_id` FROM `crm_prado`.`notes` AS `Note` WHERE `CustomersNote`.`customer_id` = '1' LIMIT 10
Thanks!
The soluction is really simple.
In find uses
$all = $this->Customer->find('all', array(
'recursive'=>2,
'conditions'=>array(
'Customer.id'=>$id
)
));
Works!, but now the recursive mode don't work...
I have 4 tables:
wizards (id, name)
pages (id, page_number, wizard_id)
modules (id, name)
modules_pages(id, page_id, module_id, wizard_id)
The models are baked defaults.
A Wizard has a Page, a Page has a Module. modules_pages is a junction table. I cannot get a list of modules when I display an array from wizard or pages that is filtered by the current wizard_id of the page, or one that I pass. It just displays the modules multiple times.
Page Model
public $belongsTo = array(
'Wizard' => array(
'className' => 'Wizard',
'foreignKey' => 'wizard_id',
),
);
public $hasAndBelongsToMany = array(
'Module' => array(
'className' => 'Module',
'joinTable' => 'modules_pages',
'foreignKey' => 'page_id',
'associationForeignKey' => 'module_id',
'unique' => 'keepExisting',
)
);
Module Model
public $hasAndBelongsToMany = array(
'Page' => array(
'className' => 'Page',
'joinTable' => 'modules_pages',
'foreignKey' => 'module_id',
'associationForeignKey' => 'page_id',
'unique' => 'keepExisting',
),
);
public $hasOne = array(
'CodeReference' => array(
'className' => 'CodeReference',
'foreignKey' => 'code_reference_id',
),
);
Wizard Model
public $hasMany = array(
'Page' => array(
'className' => 'Page',
'foreignKey' => 'wizard_id',
'dependent' => false,
'conditions' => 'ModulesPage.wizard_id = Wizard.id',
)
);
The Controller
$this->loadModel('Page');
$this->Page->recursive = 1;
$options = array('Page.wizard_id' => $wizard_id);
$page = $this->Page->find('first', $options);
$this->set('page');
$this->loadModel('ModulesPage');
$this->ModulesPage->recursive = 2;
$options = array('ModulesPage.wizard_id ' => $wizard_id,
'ModulesPage.page_number' => $page_number,
'ModulesPage.enabled' => 1);
$modules = $this->ModulesPage->find('all', $options);
Start by setting the following in your AppModel:
public $recursive = -1;
What Controller are you putting that code in? You shouldn't need to use loadModel - because you have joined the models together in the model files, you can access like this.
// access Module model from within PagesController
$this->Page->Module->find('all', $options);
There is also an error in your $options set-up. Conditions are provided in an array but they need to be inside another array called 'conditions', e.g.
$options = array(
'conditions' => array(
//conditions go here
)
);
Finally, after turning off recursive you need to enable containable behaviour. Change the $options array I've just given above as follows (this example would get all Pages along with their Wizards and Modules)
$options = array(
'conditions' => array(
//conditions go here
),
'contain' => array(
'Wizard',
'Module'
)
);
$data = $this->Page->find('all', $options);
You can further define additional options for the query with other array keys, e.g. 'order', 'group', 'limit', etc.
I have an Item model which has the following associations:
public $hasOne = array(
'Project' => array(
'className' => 'Project',
'foreignKey' => 'item_id'
)
);
public $hasMany = array(
'ItemPic' => array(
'className' => 'ItemPic',
'foreignKey' => 'item_id',
'dependent' => false
)
);
I am wanting custom data for different views of Item. It seems like CakePHP automatically includes Project data (maybe because it is hasOne?) and does not include the ItemPic data. In the index I really don't even want the Project data... however, I do want the ItemPic data. For each Item record pulled, I want a single ItemPic record joined to it. This ItemPic should be basically ItemPic.item_id = Item.id and ORDER BY ItemPic.rank LIMIT 1.
The purpose of this is basically so that in the index I can show a list of Items and a picture associated with each item. I would like all of the images along with the Project data in the view for a single Item, but not in the list/index.
I was told I could use containable like this:
// In the model
public $actsAs = array('Containable');
// In the controller
$this->paginate = array(
'conditions' => $conditions,
'contain' => array(
'ItemPic' => array(
'fields' => array('file_name'),
'order' => 'rank',
'limit' => 1
)
)
);
The above actually works how I want... however, I was also told that doing this would cause an extra query to be ran for every single Item... which I feel I should avoid.
I tried doing this, but I get duplicate data and it doesn't attach any ItemPic data:
$this->paginate = array(
'conditions' => $conditions,
'joins' => array(
array(
'table' => 'item_pics',
'alias' => 'ItemPic',
'type' => 'LEFT',
'conditions' => array(
'ItemPic.item_id = Item.id'
),
'order' => 'rank ASC',
'limit' => 1
)
)
);
$paginated = $this->Paginator->paginate();
Can you please try this:
$this->paginate = array(
'conditions' => $conditions,
'joins' => array(
array(
'table' => 'item_pics',
'alias' => 'ItemPic',
'type' => 'LEFT',
'conditions' => array(
'ItemPic.item_id = Item.id'
),
'order' => 'rank ASC',
'limit' => 1
)
),
'fields' => array('Item.*','Project.*','ItemPic.*')
);
In the fileds section you may or may not assign "Item" , "Project" according to your requirment.
Thanks
Ok. This must be an easy one for you cakePHP ninjas out there! But my cakePHP skills are still pretty much at the lowest level. So I have 2 Models; Donor Model, and Donations Model.
The setup is as follows :
Donor Model hasMany Donation Model
Donor Model
public $hasMany = array(
'Donation' => array(
'className' => 'Donation',
'foreignKey' => 'donor_id',
'order' => 'Donation.created DESC',
'limit' => 10,
'dependent' => true
)
);
Now in the DonorsController I am using the paginatorComponent's paginate() method. Within my index I have this code
public function index($id = null){
$options['joins'] = array(
array(
'table' => 'donations',
'alias' => 'Donation',
'type' => 'LEFT',
'conditions' => array(
'Donor.id = Donation.donor_id',
))
);
$donors = $this->Paginator->paginate('Donor',$options);
$this->set('donors',$donors);
}
However this returns an sql Error :
'1054 Unknown column 'joins' in 'where clause' ''
Anyone knows why this is happening, Or if the above code is correct? thanks
Your code is incorrect, please see the below function:
public function index($id = null){
$options['joins'] = array(
array(
'table' => 'donations',
'alias' => 'Donation',
'type' => 'LEFT',
'conditions' => array(
'Donor.id = Donation.donor_id',
))
);
$this->paginate = $options;
$donors = $this->Paginator->paginate('Donor');
$this->set('donors',$donors);
}
I am using CakePHP 2.0 and I have a Model with this 'virtualFields':
Country.php:
var $virtualFields = array(
'path' => "CONCAT_WS('/', dirname, basename)"
);
If I do this in a Controller which uses "User"
$users = $this->User->find('all');
the virtual field path is set.
If I use this in my Controller, which also uses "User"
$options['fields'] = array(
'DISTINCT User.*'
);
$options['joins'] = array(
array(
'table' => 'courses',
'type' => 'inner',
'conditions' => array(
'User.id = courses.user_id'
)
),
array(
'table' => 'times',
'type' => 'inner',
'conditions' => array(
'courses.id = times.course_id'
)
)
);
$options['conditions'] = array(
'times.amount > ' => 0
);
$users = $this->User->find('all', $options);
With the options, the path field is not set, of course, the SQL query does not seem to have the "CONCAT_WS('/', dirname, basename)" field included, which is included if I do the find operation without the options.
What can I do having the options, so that the virtual field is automatically included? Of course I can write in the fields options the CONCAT, but that's not very nice, especially if I change it.
Best regards.
Use group by rather than distinct and remove the fields option completely.
$options['group'] = array(
'User.*'
);
$options['joins'] = array(
array(
'table' => 'courses',
'type' => 'inner',
'conditions' => array(
'User.id = courses.user_id'
)
),
array(
'table' => 'times',
'type' => 'inner',
'conditions' => array(
'courses.id = times.course_id'
)
)
);
$options['conditions'] = array(
'times.amount > ' => 0
);
$users = $this->User->find('all', $options);