How can I convert this query into a CakePHP query?
SELECT COUNT(invoices.id) FROM invoices,sales
WHERE invoices.id=sales.invoice_id AND to_id='13' AND is_updated='0'
GROUP BY invoice_id
I have two controllers and two models, invoices and sales.
For aggregate functions like COUNT, you need to create virtual field.
$this->Invoice->virtualFields['total'] = 'COUNT(`Invoice`.`id`)';
$result = $this->Invoice->find('all', array(
'fields' => array('Invoice.total'),
'joins' => array(
array(
'table' => 'sales',
'alias' => 'Sale',
'type' => 'INNER',
'conditions' => array(
'Invoice.id = Sale.invoice_id',
)
)
),
'conditions' => array(
'to_id' => 13,
'is_updated' => 0
),
'group' => array('Sale.invoice_id')
));
pr($result); // Check your result
Use the following links as references:
Joining tables
Retrieving Your Data
Virtual fields
Try to use Cake's Cookbook solution:
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#prepared-statements
Related
hello I have the following models
Restaurant(id, name)
RestaurantMenu(id, name, restaurant_id) [each restaurant has many main menus]
RestaurantMenuItem(id, name, restaurant_menu_id) [Each Menu has many menu items]
I need to find those RestaurantMenu which has at least 1 RestaurantMenuItem. If It has not then it shouldn't have to get the RestaurantMenu also. Atm it is giving me empty array RestaurantMenuItem which I don't want. I don't want an empty object.
$this->Behaviors->attach('Containable');
return $this->find('all', array(
'contain' => array('RestaurantMenu.RestaurantMenuItem'),
'conditions' => array(
'Restaurant.id' => $restaurant_id,
),
'recursive' => 0
));
This could be achieved by applying an INNER join with RestaurantMenuItem on the RestaurantMenu containment. That would cause RestaurantMenu to be selected only if at least 1 associated RestaurantMenuItem row exists, which should satisfy both of your conditions.
Unfortunately there's no joins and group option for containments, so one has to workaround this by for example querying the RestaurantMenu data manually, which should be pretty easy in your case, as you're only selecting a single Restaurant (btw, you should probably use the first finder instead of the all one in that case):
$restaurant = $this->find('first', array(
'conditions' => array(
'Restaurant.id' => $restaurant_id,
),
'recursive' => 0
));
if ($restaurant) {
// RestaurantMenu needs to have the containable behavior attached
// in order for this to work
$restaurant['RestaurantMenu'] = $this->RestaurantMenu->find('all', array(
'contain' => array(
'RestaurantMenuItem'
),
'joins' => array(
array(
'table' => 'restaurant_menu_item',
'alias' => 'RestaurantMenuItem',
'type' => 'INNER',
'conditions' => array(
'RestaurantMenu.id = RestaurantMenuItem.restaurant_menu_id'
)
)
),
'conditions' => array(
'RestaurantMenu.restaurant_id' => $restaurant_id,
),
'group' => array(
'RestaurantMenu.id'
)
));
}
return $restaurant;
or by using the finderQuery option to define a custom query that includes the join:
return $this->find('all', array(
'contain' => array(
'RestaurantMenu' => array(
'finderQuery' => '
SELECT
RestaurantMenu.*
FROM
restaurant_menus AS RestaurantMenu
INNER JOIN
restaurant_menu_items AS RestaurantMenuItem ON
RestaurantMenuItem.restaurant_menu_id = RestaurantMenu.id
WHERE
RestaurantMenu.restaurant_id IN ({$__cakeID__$})
GROUP BY
RestaurantMenu.id
',
'RestaurantMenuItem'
)
),
'conditions' => array(
'Restaurant.id' => $restaurant_id,
)
));
This could also be applied in the RestaurantMenu association configuration in the Restaurant model.
See also
Cookbook > Models > Associations: Linking Models Together > Joining tables
Cookbook > Models > Associations: Linking Models Together > hasMany
UPDATED!
i am relativly new in cakephp but having experience with mysql and php.
The model look like:
Person->Father
Father is self refered to person.
I wrote the following based on mysql query which gives back the father of "1" person
mysql:
SELECT `Father`.name,`Father`.id from persons as `Father` left join persons as `Person` on `Person`.`father_id`=`Father`.`id` where `Person`.id=1
cakephp
$options = array(
'fields' => array(
//'Father.name',
'Father.id',
),
'joins' => array(
array(
'conditions' => array(
'Person.father_id = Father.id',
),
'table' => 'persons',
// 'alias' => 'Person', i commented because having conflict with scaffolded model
'type' => 'left',
),
),
'conditions' => array(
'Person.id' => '1',
),
'contain' => array(
'Person','Father',
),
);
$data = $this->Person->find('first', $options);
$fatherquery=$this->Person->find('first',array('conditions'=>array('Person.id'=>$data['Father']['id'])));
To get the same as mysql i have add this extra line(the last one $father=....,but now it seems subquery and look like the join isn't working) because of Father and Person are not the same model and if i have
$data['Father']['name'] and $data['Person']['name'] they are not equal
By the way i have a solution already,but maybe i misunderstand some concept.
Is there a way the get the mysql query easier?
Try this. (Notice the lack of a contain. The only reason to use contain is if you're not already getting the data in the main-model's find() or a join):
//Person model (cake 2.x code)
$this->find('first', array(
'fields' => array(
'Father.id',
'Father.name'
),
'conditions' => array(
'Person.id' => 1
),
'joins' => array(
array(
'table' => 'persons',
'alias' => 'Father',
'type' => 'left',
'conditions' => array(
'Person.father_id = Father.id'
)
)
)
));
I am using this code to make a list of truck types counted and sorted by type.
$count['type'] = $this->Type->find('all',
array('joins' => array(
array(
'table' => 'truck_has_types',
'alias' => 'TruckHasTypes',
'type' => 'LEFT',
'conditions' => array(
'Type.id' => 'TruckHasTypes.types_id',
)
)
),
'fields' => array(
'Type.id',
'Type.name',
'COUNT(TruckHasTypes.types_id) as N'),
'group' => 'TruckHasTypes.types_id'
)
);
The query only returns a single result even thought there should be many more. I found the culprit. The query looks like this (got this from sql_dump)
SELECT `Type`.`id`, `Type`.`name`, COUNT(`TruckHasTypes`.`types_id`) as N FROM `douglass_cake`.`types` AS `Type` LEFT JOIN `douglass_cake`.`truck_has_types` AS `TruckHasTypes` ON (`Type`.`id` = 'TruckHasTypes.types_id') WHERE 1 = 1 GROUP BY `TruckHasTypes`.`types_id`
You can see that
ON (`Type`.`id` = 'TruckHasTypes.types_id')
Does no have the same quotes as
ON (`Type`.`id` = `TruckHasTypes`.`types_id`)
I have added those quotes manually in phpmyadmin and the query is successful, but I cannot not get cakephp to automatically generate this query. Any ideas?
Thank you!
Ryan
Do it this way:
$count['type'] = $this->Type->find('all',
array(
'joins' => array(
array(
'table' => 'truck_has_types',
'alias' => 'TruckHasTypes',
'type' => 'LEFT',
'conditions' => array(
'Type.id=TruckHasTypes.types_id',
)
)
),
'fields' => array(
'Type.id',
'Type.name',
'COUNT(TruckHasTypes.types_id) as N'
),
'group' => 'TruckHasTypes.types_id'
)
);
Instead of writing joins do it "Cake way" — write model association: Associations: Linking Models Together
Also does 'group' => 'TruckHasTypes.types_id' needs to be one level upper?
I have a pretty basic paginate query which works fine until I put DISTINCT on one of the columns.
The following works:
$this->paginate = array(
'conditions' => array(Message.recipient_id' => $this->Auth->user('id')),
'fields' => array(
'DISTINCT Message.sender_id',
'Message.recipient_id',
'Message.thread_id',
),
'limit' => 15,
'order' => array('Message.created' => 'DESC')
);
This query doesn't work:
$this->paginate = array(
'conditions' => array(Message.recipient_id' => $this->Auth->user('id')),
'fields' => array(
'DISTINCT Message.sender_id',
'Message.recipient_id',
'Message.thread_id',
// If I add any of the following columns, the DISTINCT doesn't work at all
'Message.created',
'Message.modified',
'Message.id'
),
'limit' => 15,
'order' => array('Message.created' => 'DESC')
);
Why would any of those other columns in the 'fields' option trip up the DISTINCT keyword?
It doesn't make any sense to me.
If you mean by "it doesn't work" that despite the distinct column you are getting multiple messages with the same sender_id... Then the reason for that is because when using multiple columns with the distinct keyword, it looks for unique combinations of all the columns. Message.created, Message.modified, and Message.id are going to be unique per row most or all of of the time, so those columns by themselves will make your combinations of fields distinct. Try using group by Message.sender_id instead.
$this->paginate = array(
'conditions' => array(Message.recipient_id' => $this->Auth->user('id')),
'fields' => array(
'Message.sender_id',
'Message.recipient_id',
'Message.thread_id',
'Message.created',
'Message.modified',
'Message.id'
),
'limit' => 15,
'order' => array('Message.created' => 'DESC'),
'group' => array('Message.sender_id'),
);
I have the problem with the sequence of joins. The similar problem was in another question Manipulating Order of JOINS in CakePHP. The answer was to use Containable behavior. In my case that is unacceptable because I have deeper associations and containable generates too many queries. Containable does not generate joins for the three level associations. It generates additional queries for every entry from the second level table.
My query is:
$this->LevelOne->find('all', array(
'joins' => array(array(
'table' => 'level_three',
'alias' => 'LevelThree',
'type' => 'LEFT',
'conditions' => array(
'LevelThree.id = LevelTwo.level_three_field_id'
)
))
));
The problem here is that cake generates several joins but the join of the LevelThree table is done before the joins of the LevelTwo tables and that throws an SQL error "Unknown column 'LevelTwo.level_three_field_id' in 'on clause'". If the LevelThree join would be at the end of the query after all LevelTwo joins the query would be okay.
So, the question is how to change the sequence of joins?
Finally I figured out how to do that:
$this->LevelOne->unbindModel(array('belongsTo' => array('LevelTwo')));
$this->LevelOne->find('all', array(
'joins' => array(
array(
'table' => 'level_two',
'alias' => 'LevelTwo',
'type' => 'LEFT',
'conditions' => array(
'LevelTwo.id = LevelOne.level_two_field_id'
)
),
array(
'table' => 'level_three',
'alias' => 'LevelThree',
'type' => 'LEFT',
'conditions' => array(
'LevelThree.id = LevelTwo.level_three_field_id'
)
)
)
));
To those who has similar problems but in relations a-la $belongsTo, to have correct order you should set it correctly.
For example when you have such code:
var $belongsTo = array(
'Vacancy',
'Applicant' => array(
'className' => 'Person',
),
'Recruiter' => array(
'className' => 'Person',
),
'Company' => array(
'conditions' => array('Company.id = Vacancy.company_id'),
),
);
But in the result always receive results where Vacancy always joins last, you should do juts simple thing: add those "Vacancy" model not as array value, but as a key=>value, as others:
var $belongsTo = array(
'Vacancy' => array(), // Just add empty array here -- all magic is here :)
'Applicant' => array(
'className' => 'Person',
),
'Recruiter' => array(
'className' => 'Person',
),
'Company' => array(
'conditions' => array('Company.id = Vacancy.company_id'),
),
);
Now all will be in straight order: Vacancy, Applicant, Recruiter & only then Company.
Have you thought about creating a HABTM model and inserting your own 'finderQuery' to override the model query?
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasandbelongstomany-habtm
Sometimes with complex queries, it make more sense to create the custom query so cake doesn't have to deal with it. To do that, you can just create the function in the model and then call it like you would any other query.
function customJoin(){
return $this->query('CUSTOM QUERY HERE');
}
Then call it from the controller:
$this->ModelName->customJoin();
I think sometimes we rely too heavily on the automation that a framework provides and forget that WE are in control and can implement code outside of the core. I do it all the time. Hope this helps.
public function selectdata(){
$option= $this->Group->find('all',
array(
'joins' =>
array(
array(
'table'=> 'user_groups',
'alias'=>'g',
'type'=> 'INNER',
'conditions'=> array('g.group_id =Group.id ')
),
array(
'table'=> 'users',
'alias'=>'u',
'type'=> 'INNER',
'conditions'=> array('u.id = g.user_id')
),
),'fields'=>array('Group.name,Group.created,Group.modified,Group.status,Group.created_by,u.first_name,u.last_name'),
'conditions'=>array('g.group_id=Group.created_by or g.user_id=u.id')
// ."'".$created_by."'"
)
);
var_dump($option);//die();
if(isset($this->params['requested']))
{
return $option;
}
$this->set('groups', $option);
}