How to get data based on 2 column condition in CakePHP - cakephp

I am trying to get some data in CakePHP 3.6. The condition is based on 2 column in a same table. first column is status as second column is is_done_by_user. So I want to fetch data if status is 1 or 2. Also if the status is 3. But for status 3 there need to be a check if the column is_done_by_user have value 1. So the final thing is. I want to get all the data with status 1 and 2. And all the data with status 3 where is_done_by_user is 1.
I have written the query but it is not working the way I am wanting. Here is the query that I had tried till now.
$query = $this->find('all',[
'conditions' => [
'Appointments.is_active' => 1,
'Appointments.status IN' => (1,2,3),
'Appointments.is_done_by_user' => 1
]
]);
Maybe I am far away from the actual query.

Your query as written will find anything where all of the stated conditions are true, which is obviously not what you want. I think that this may be what you're looking for?
$query = $this->find('all',[
'conditions' => [
// This must always be true
'Appointments.is_active' => 1,
// One of the conditions in this list must be true
'OR' => [
// Either the status is 1 or 2
'Appointments.status IN' => (1,2),
// Or the status is 3 AND the user is 1
[
'Appointments.status' => 3,
'Appointments.is_done_by_user' => 1,
],
],
]
]);

Related

select count of records group by month in cakephp 3

I'm using CakePHP 3.x+
I have to show a graph on the page and thus want to build script for that.
I have to select count of records group by month for current year.
This is what I have tried.
$graph = $this->GenerateVideos->find()
->select('COUNT(id)', 'MONTH(created)')
->where(['YEAR(created)' => date('Y')])
->group(['MONTH(created)']);
which generates sql like
'sql' => 'SELECT GenerateVideos.COUNT(id) AS GenerateVideos__COUNT(`id`) FROM generate_videos GenerateVideos WHERE YEAR(created) = :c0 GROUP BY MONTH(created) ',
'params' => [
':c0' => [
'value' => '2018',
'type' => null,
'placeholder' => 'c0'
]
],
But this is giving error as
Error: SQLSTATE[42000]: Syntax error or access violation:
1064 You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use near '(`id`)
FROM generate_videos GenerateVideos WHERE YEAR(created) = '2018' GROUP BY' at line 1
Try using an array in your ->select() value:
->select(['COUNT(id)', 'MONTH(created)'])
In the book, it always shows an array, and it doesn't appear to be utilizing your second select value.
Or, per the book here, you could try this:
$query = $this->GenerateVideos->find();
$query->select(['count' => $query->func()->count('id'), 'month' => 'MONTH(created)']);
$query->where(['YEAR(created)' => date('Y')])
$query->group(['month' => 'MONTH(created)']);

Subquerys in cakephp 3.x, new ORM?

I'm new in Cakephp 3.x and I'm having some trouble to create a subquery in the new ORM format. I have this report in my application, that needs to return the follow result:
1. There are three entities - Users, Calls, CallStatus.
2. Users hasMany Calls, Calls hasMany CallStatus.
3. I need to count how many CallStatus each user has in Calls.
Now follow the query that I need to put on new ORM format:
SELECT U.name,
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =1 and C.user_id=U.id) AS 'Unavailable',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =2 and C.user_id=U.id) AS 'Busy',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =3 and C.user_id=U.id) AS 'Contacted',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =4 and C.user_id=U.id) AS 'Error'
FROM `users` AS U
WHERE U.profile=3 and U.is_active=1
Could someone give me a help, please? Thanks
If I understand you correctly, you want to see the number of calls for every callstatus you have for a specific user.
Try the following. Note that I used the CakePHP convention for naming the callstatuses (which is plural).
// get the tableregistry
use Cake\ORM\TableRegistry;
$callstatuses = Cake\ORM\TableRegistry::get('Callstatuses');
// for user with id 2, get the number of calls for each callstatus
$callstatuses->find()
->contain(['Calls'])
->where(['Calls.user_id' => 2, 'User.is_active' => 1])
->countBy('name')
->toArray();
// output could be:
//[ 'Unavailable' => 2, 'Busy' => 1 ]
You can find information about creating queries in the CakePHP book: see 'Query Builder'.
If you want to know more about working with/on queries, note that queries are Collections. Anything you can do on a Collection object, you can also do in a Query object. See the Collection section in the CakePHP book.
You have to use subqueries, as many as you want!
Here is an example for your case:
$q = $this->Calls->find();
$q1->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 1]);
$q2->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 2]);
$q3->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 3]);
$q4->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 4]);
$qUsers = $this->Users->find()
->select([
'id',
'first_name',
'Unavailable' => $q1,
'Busy' => $q2,
'Contacted' => $q3,
'Error' => $q4
])
->where(['profile' => 3, 'active' => 1])
->all();
Note: That nicer if you use a loop to create suqueries in this case.

CakePHP count query gives different result when run in phpmyadmin

I am running the following query on my database:-
SELECT COUNT(*) AS COUNT, `Doctor`.`device_type` FROM `doctors` AS `Doctor` WHERE 1 = 1 GROUP BY `Doctor`.`device_type`
and it gives the result:-
count device_type
47 Android
23 iPhone
Whereas when running this query as a CakePHP query it gives the result as '2':-
$this->Doctor->find('count',array('group'=>'Doctor.device_type'));
Can anyone please suggest why this is happening?
The CakePHP result is correct as it is returning a count of the number of results returned by your query. In your case you have 2 rows: 'Android' and 'iPhone'.
find('count') always returns an integer, not an array. What Cake is doing is basically this:-
$data = $this->Doctor->find('all', array('group' => 'Doctor.device_type'));
$count = count($data); // This is what find('count') will return.
You need to do something like the following instead:-
$data = $this->Doctor->find('all', array(
'fields' => array(
'Doctor.device_type',
'COUNT(Doctor.*) AS count'
)
'group' => 'Doctor.device_type'
));

CakePhp 2 tree behaviour's generateTreeList() returns flat list

I followed the docs at the book to use the tree behaviour.
Everything looks ok. parent_id, lft and rght are saved properly but
when I call:
$this->Model->generateTreeList()
The returned list is flat, i.e.:
array(
(int) 8 => 'p1',
(int) 11 => 'child of p1',
(int) 9 => 'p2',
(int) 2 => 'child of p2',
)
Is there anything else I need to be aware of?
It works as expected (according to documentation). generateTreeList returns array where key is id and diplay field is the value.
generateTreeList($conditions=null, $keyPath=null, $valuePath=null, $spacer= '_', $recursive=null)
You can specify $spacer parameter, and have result like i.ex:
array(
1 => 'p1',
2 => '_p2',
3 => '_p3',
4 => '_p4',
5 => '__p5',
);
The list is "flat" but with spacer. If you want to have hierarchical nested array you have to use: $this->Model->find('threaded')

Paginate results filtered by condition on associated model (HABTM) using Containable

I need to paginate list of Products belonging to specific Category (HABTM association).
In my Product model I have
var $actsAs = array('Containable');
var $hasAndBelongsToMany = array(
'Category' => array(
'joinTable' => 'products_categories'
)
);
And in ProductsController
$this->paginate = array(
'limit' => 20,
'order' => array('Product.name' => 'ASC'),
'contain' => array(
'Category' => array(
'conditions' => array(
'Category.id' => 3
)
)
)
);
$this->set('products', $this->paginate());
However, resulting SQL looks like this:
SELECT COUNT(*) AS `count`
FROM `products` AS `Product`
WHERE 1 = 1;
SELECT `Product`.`*`
FROM `products` AS `Product`
WHERE 1 = 1
ORDER BY `Product`.`name` ASC
LIMIT 20;
SELECT `Category`.`*`, `ProductsCategory`.`category_id`, `ProductsCategory`.`product_id`
FROM `categories` AS `Category`
JOIN `products_categories` AS `ProductsCategory` ON (`ProductsCategory`.`product_id` IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20) AND `ProductsCategory`.`category_id` = `Category`.`id`)
WHERE `Category`.`id` = 3
(I.e. it selects 20 Products and then queries their Categories)
while I'd need
SELECT COUNT(*) AS `count`
FROM `products` AS `Product`
JOIN `products_categories` AS `ProductsCategory` ON `ProductsCategory`.`product_id` = `Product`.`id`
JOIN `categories` AS `Category` ON `Category`.`id` = `ProductsCategory`.`category_id`
WHERE `Category`.`id` = 3;
SELECT `Product`.*, `Category`.*
FROM `products` AS `Product`
JOIN `products_categories` AS `ProductsCategory` ON `ProductsCategory`.`product_id` = `Product`.`id`
JOIN `categories` AS `Category` ON `Category`.`id` = `ProductsCategory`.`category_id`
WHERE `Category`.`id` = 3
ORDER BY `Product`.`name` ASC
LIMIT 20;
(I.e. select top 20 Products which belong to Category with id = 3)
Note:
Possible solution without Containable would be (as Dave suggested) using joins.
This post offers a very handy helper to build $this->paginate['joins'] to paginate over HABTM association.
Note: Still looking for more elegant solution using Containable than fake hasOne binding.
Finally I found a way to do what I want, so posting it as an answer:
To force JOIN (and be able to filter via condition on associated model) in Containable - you've got to use fake hasOne association.
In my case, code in ProductsController should be:
$this->Product->bindModel(array('hasOne' => array('ProductsCategory')), false);
$this->paginate = array(
'limit' => 20,
'order' => array('Product.name' => 'ASC'),
'conditions' => array(
'ProductsCategory.category_id' => $category
),
'contain' => 'ProductsCategory'
);
$this->set('products', $this->paginate());
Note false as a second argument to bindModel - which makes binding persistent. This is needed because paginate() issues find('count') before find('all'), which would reset temporary binding. So you might want to manually unbindModel afterwards.
Also, if your condition includes multiple IDs in HABTM associated model, you might want to add 'group' => 'Product.id' into your $this->paginate[] (as Aziz has shown in his answer) to eliminate duplicate entries (will work on MySQL only).
UPDATE:
However, this approach has one serious drawback compared to joins approach (suggested by Dave): condition can apply only to intermediate model's foreign key (category_id in my case); if you want to use condition on any other field in associated model - you'd probably have to add another bindModel('hasOne'), binding intermediate model to HABTM associated model.
When you put the condition in the nested Contain, you're asking it to retrieve only the Categories with that ID. So - it's doing what you're asking, but that's not what you want.
Though it seems like it should be possible, the only luck I've had doing what you're trying to do (after MANY hours and a few stackoverflow questions) is via Joins instead of Contain.
http://book.cakephp.org/view/1047/Joining-tables
It's not the exact same problem, but you can go through some of my code where I query against HABTM conditions (I answered my question at the bottom) here: Select All Events with Event->Schedule->Date between start and end dates in CakePHP

Resources