Model->find() conditions with no associated results - cakephp

To stay simple, I have a User model which hasMany Post.
[User] id
[Post] id, user_id
How can I write a $this->User->find('all', array('conditions' => $conditions) to get all User who has no Post ?

assuming Post belongsTo User:
$this->User->Post->find(
'all',
array(
'fields' => array('User.id', 'user_id', 'COUNT(user_id) AS posts_count'),
'group' => 'user_id HAVING posts_count = 0'
)
)

Create one function inside User model and try below code: It will give all the User.id having no posts.
$options = array(
'conditions'=>array('Post.id is NULL')
'joins' => array(
array(
'alias' => 'Post',
'table' => ‘posts’,
'type' => 'LEFT',
'conditions' => array(
'Post.user_id = User.id',
),
)
),
'fields' => array('User.id')
);
$returnData = $this->find('list',$options);

Related

CakePHP join only the latest entry

I have 2 tables :
ORDERS
------
id
type
LOGS
----
id
order_id
time
I want to query with cakephp so I have :
array(
'Order' => array(
'id' => '38',
'type' => 'online',
),
'Log' => array(
'time' => '2014-09-24 21:17:14'
)
)
The problem is that I want only the last order, not all orders with all logs.
I did something like this :
$ordersList = $this->Order->find('all', array(
'fields' => array(
'Order.*',
'Log.time'
),
'joins' => array(
array(
'table' => 'logs',
'alias' => 'Log',
'type' => 'right',
'conditions' => array(
'Log.order_id = Order.id'
),
)
),
);
To have only the last one, you can order the result by log.time and then take only the first record (with the param 'first' or just by fetching the first record of the recordset).
For example :
$order = $this->Order->find('first', array(
'order' => array('Log.time' => 'desc')
));
in your case :
$ordersList = $this->Order->find('first', array(
'fields' => array(
'Order.*',
'Log.time'
),
'joins' => array(
array(
'table' => 'logs',
'alias' => 'Log',
'type' => 'right',
'conditions' => array(
'Log.order_id = Order.id'
),
'order' => array('Log.time' => 'desc')
)
),
);
As information, if you want to have a simple method to join the models, take a look at the containable behavior. With this behavior, the link are setted in the model then you only have to declare which associated model you want to retrieve with your current model.
=> http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html

cakephp paginate with associated model

User has many User_Questionaries. I want paginate users that have particular questionnaire. I used following pagination for it.
$paginate = array(
'conditions' => array(
'User.role' => IWOA,
'UserQuestionary.questionary_id' => $id
),
'recursive' => 1,
'limit' => 10,
'order' => array(
'name' => 'asc'
),
'contain' => array('UserQuestionary')
);
But it is not create join query. It is showing Unknown column UserQuestionary.questionary_id' in 'where clause'
What is the issue? How can i do it?
Finally I used join query for do this.
$paginate = array(
'conditions' => array(
'Iwoa.role' => IWOA,
),
'joins' => array(
array(
'alias' => 'UserQuestionary',
'table' => 'user_questionaries',
'type' => 'LEFT',
'conditions' => 'UserQuestionary.user_id = Iwoa.id AND UserQuestionary.questionary_id = ' . $id
)
),
'limit' => 10,
'order' => array(
'name' => 'asc'
),
);

cakephp how to find belongs to by id

Is there a way in cakephp using ORM to get the item that belongs to a specific child item. For example I was to get the related Post record for a specific Comment records.
This is my Comment model:
var $belongsTo = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'post_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
I was trying this but it's pull back every post, even those that don't have the comment I'm querying against:
$this->Post->contain('Comment');
$results = $this->Post->find('all', array(
'contain' => array(
'Comment' => array(
'conditions' => array(
'id' => 15
)
)
)));
Any other way to do this?
Are you sure you don't have to specify the model in your conditions?
For example:
$this->Post->contain('Comment');
$results = $this->Post->find('all', array(
'contain' => array(
'Comment' => array(
'conditions' => array(
'Comment.id' => 15
)
)
)));
my research led me to this post regarding the issue with contains:
http://nuts-and-bolts-of-cakephp.com/2008/07/17/forcing-an-sql-join-in-cakephp/
so my final solution was as follows:
$this->Post->unbindModel(array('hasMany' => array('Comment')));
$results = $this->Post->bindModel(array('hasOne' => array(
'Comment' => array(
'foreignKey' => false,
'conditions' => array('Comment.post_id = Post.id'))
)));
$results = $this->Post->find('all', array(
'conditions' => array(
'Comment.id' => 10
)));
not pretty but gets the job done :)

CakePHP: Select DISTINCT in ContainableBehaviour

I am trying to select only distinct related model entries but it seems it doesn't work.
I have this:
$active_questions = $this->Question->find('all', array('conditions' => array('test_id' => $active_tests), 'fields' => array('answer_style_id'), 'contain' => array(
'Answer' => array(
'fields' => array('capital_category_id'),
'CapitalCategory' => array(
'fields' => array('id', 'DISTINCT capital_id', 'DISTINCT category_id', 'delete_flag'),
'Capital' => array(
'fields' => array('id', 'delete_flag')
),
'Category' => array(
'fields' => array('id', 'delete_flag')
)
)
)
)));
But Cake seems to automatically add the associated model key, even id I specified it with a DISTINCT keyword:
Query: SELECT `CapitalCategory`.`id`, DISTINCT `CapitalCategory`.`capital_id`, DISTINCT `CapitalCategory`.`category_id`, `CapitalCategory`.`delete_flag`, `CapitalCategory`.`capital_id`, `CapitalCategory`.`category_id` FROM `capital_categories` AS `CapitalCategory` WHERE `CapitalCategory`.`id` = 217
How do I filter out only DISTINCT capitals or categories? For the current example, Cake returns 20 categories with the same id. I want only one to be returned.
Thank you.
Off the top of my head, the following may work using the 'group' option
$active_questions = $this->Question->find(
'all',
array(
'conditions' => array('Question.test_id' => $active_tests),
'fields' => array('answer_style_id'),
'contain' => array(
'Answer' => array(
'fields' => array('capital_category_id'),
'CapitalCategory' => array(
'fields' => array('id', 'capital_id', 'category_id', 'delete_flag'),
'group' => array('capital_id', 'category_id'),
'Capital' => array(
'fields' => array('id', 'delete_flag')
),
'Category' => array(
'fields' => array('id', 'delete_flag')
)
)
)
)
)
);
Why don't you put 'group' just below 'fields' and not inside 'contain', also you may have to remove 'fields' altogether.

How do I filter deep associations in CakePHP

I have the following tables: binders, docs, users, docs_users. Doc belongsTo Binder, Doc hasAndBelongsToMany User.
I want to get binders and their associated docs for the user that is currently logged in (the associated user_id in the docs_users table).
I have tried Containable and find('all') with joins, conditions, etc. but I can't figure out how to remove the Docs that are from Users who are not associated in the docs_users table.
This code does NOT work:
$binders = $this->Binder->find(
'all',
array(
'joins' => array(
array(
'table' => 'binders_users',
'alias' => 'BindersUser',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array(
'BindersUser.binder_id = Binder.id',
'BindersUser.user_id = ' . $this->Auth->user('id')
)
),
array(
'table' => 'docs',
'alias' => 'Doc',
'type' => 'left',
'foreignKey' => false,
'conditions'=> array(
'Doc.binder_id = Binder.id',
)
),
array(
'table' => 'docs_users',
'alias' => 'DocsUser',
'type' => 'left',
'foreignKey' => false,
'conditions'=> array(
'DocsUser.doc_id = Doc.id',
'DocsUser.user_id = ' . $this->Auth->user('id')
)
)
),
'recursive'=>0
)
);
$this->set('binders', $binders);
And neither does this:
$this->Binder->recursive = 2;
$this->Binder->Behaviors->attach('Containable');
$this->Binder->contain(array(
'Branch',
'Doc' => array(
'User' => array(
'DocsUser' => array(
'conditions' => array('id = "17"')
)
)
)
));
$binders = $this->Binder->find('all');
Any help from you seasoned pros would be great! Thanks!
Alternative/Simplified Solutions?
This works if I just want to get binders to which users have permissions. Short and sweet. However, it will still send ALL associated docs through, which is NOT the behavior I want. It needs to only pass on the docs to which the user has permissions (as described previously).
$binders = $this->Binder->find(
'all',
array(
'joins' => array(
array(
'table' => 'binders_users',
'alias' => 'BindersUser',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array(
'BindersUser.binder_id = Binder.id',
'BindersUser.user_id = ' . $this->Auth->user('id')
)
)
)
)
);
These are a few of the options available for doing deep finds on data in CakePHP:
https://github.com/Terr/linkable/wiki
http://mark-story.com/posts/view/using-bindmodel-to-get-to-deep-relations
http://bakery.cakephp.org/articles/view/quick-tip-doing-ad-hoc-joins-in-model-find
Here's the final solution I came up with based on all of the great feedback I got. I think this is an elegant solution that can be reused in any scenario where deep associations are required.
In the binder_controller I unbound the Doc model, and bound it back using the finderQuery to select only the Docs that a user has permission to see. Then in joined the binders_users table selecting only the binders that users have permissions to.
Thank you everyone for all your help!
$this->Binder->unbindModel(array('hasMany' => array('Doc')));
$this->Binder->bindModel(
array('hasMany' => array(
'Doc' => array(
'className' => 'Doc',
'foreignKey' => 'binder_id',
'dependent' => false,
'finderQuery' => 'SELECT Doc.* FROM docs AS Doc INNER JOIN docs_users AS DocsUser ON DocsUser.doc_id = Doc.id AND DocsUser.user_id = ' . $this->Auth->user('id')
)
)
)
);
$binders = $this->Binder->find(
'all',
array(
'joins' => array(
array(
'table' => 'binders_users',
'alias' => 'BindersUser',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array(
'BindersUser.binder_id = Binder.id',
'BindersUser.user_id = ' . $this->Auth->user('id')
)
)
)
)
);
More on binding/unbinding models
On this line, you need to tell Cake which Model's id you are talking about:
'conditions' => array('id = "17"')
e.g. DocsUser.id
...and you don't use recursive with containable. Get rid of it.
Have you tried coming in from a user perspective?
$this->Binder->Doc->User->Behaviors->attach('Containable');
$this->Binder->Doc->User->contain(array('Doc'=>'Binder'));
$user = $this->Binder->Doc->User->find('all',array('conditions'=>'User.id'=>$user_id));
The 'DocsUser' association should be detected anyway.
From a Binders perspective maybe
In your Binders MODEL add ( please check table, key and model names in case I made a typo )
function getBindersByUserSql($user_id)
{
$dbo = $this->getDataSource();
$subQuery = $dbo->buildStatement(
array(
'fields' => array('DISTINCT(Doc.binder_id)'),
'table' => "docs_users",
'joins' => array(
array('table' => 'users',
'alias' => 'User',
'type' => 'INNER',
'conditions' => array('DocsUser.user_id = User.id')
),
array('table' => 'docs',
'alias' => 'Doc',
'type' => 'INNER',
'conditions' => array('Doc.id = DocsUser.doc_id')
)
),
'alias'=>"DocsUser",
'conditions' => array("User.id"=>$user_id),
'order' => null,
'group' => "Doc.binder_id"
),
$this
);
return $dbo->expression($subQuery);
}
Then in your binders CONTROLLER try
$this->Binder->Behaviors->attach('Containable');
$this->Binder->contain(array('Doc'));
$conditions = array();
$conditions = $this->Binder->getBindersByUserSql($this->Auth->user('id'));
$binders = $this->Binder->find('all',array('conditions'=>$conditions)));
Any good?

Resources