I have an Articles table and a Comments Table.
Articles Has Many Comments.
Each Comment Belongs to a Comment Status (comment_status_id):
1. Good, or
2. Bad, or
3. Ugly.
I want to make a query to find all the Articles that have ONLY Comments with status 3 (Ugly). That is, exclude Articles that have any Comments with status 1 or 2.
I can write a subquery and query to get all the Article that have a comment with Status Ugly:
$matchingComments = $this->Articles->getAssociation('Comments')->find()
->select(['article_id'])
->distinct()
->where(['comment_status_id' => 3]);
$query = $this->Articles->find()
->where(['Articles.id IN' => $matchingComments]);
This gives me all the Articles that have any Comment that has status 3. But it also includes Articles that have Comments with status 2 or 1 in addition at least one Comment with status 3.
So my question is:
Is there an efficient/elegant way to make this query work with the Query Builder so the result is only Articles that have Comments that are ALL comment_status 3 (Ugly)?
I'm sure I can parse the $query results with a for loop and build a new array of results, but I would like to see if there is a better way to do this in the initial query and/or/with subquery.
Thanks in advance for any advice!
D.
Following ndm's advice to get the raw sql to work first, this query worked for my matchingComments query
SELECT `article_id`
FROM `comments`
GROUP BY `article_id`
HAVING MIN(`comment_status_id`) = 3
AND MAX(`comment_status_id`) = 3;
So then in my Cake controller, this works:
$matchingComments = $this->Articles->getAssociation('Comments')->find()
->select(['article_id'])
->group('article_id')
->having(['MIN(comment_status_id)' => 3])
->having(['MAX(comment_status_id)' => 3])
$query = $this->Articles->find()
->where(['Articles.id IN' => $matchingComments]);
Not sure if there is a better way, but this works nicely.
Thanks again ndm.
D.
From Cake Book - Retrieving Data & Results Sets
$articles = $this->Articles
->find()
->contain(['Comments'])
->notMatching('Comments.CommentStatus', function($q) {
return $q->where(['CommentStatus.comment_status_id' => '3'];
});
I currently have a users table and a posts table. My posts table has a field for user_id, however, I am not sure how I would grab the users name field which is in the users table. I was thinking of using the models afterFind() method and then using SQL to select the data, but there has to be a better way than this. Also, on my view action, I am using the read() function to grab a single post. Would the models afterFind() kick in after it runs read()? If not, is there an equivalent such as afterRead()?
Just make an association of Post belongsTo User, and every regular find/read operation that has a sufficiently high recursive value will automatically fetch the user with each post.
$post = $this->Post->find(...);
echo $post['User']['name'];
you have to go like below :
$this->Cake->findById(7); Cake.id = 7
here , you can you use your user_id instead of 7 like..
Find BY CAKE PHP
$this->Post->bindModel(array('belongsTo'=>'User'));
$post = $this->Post->findById($post_id);
$userName = $post['User']['name'];
Here are errors, I've typed it in eclipse (:
Is getLastInsertID and Model->id same? And which one can happen concurrency problem ?
$this->Model->save($this->data);
__thisFunctionTakesAVeryLongTimeToExecute(); //function 1
$insertId = $this->Model->getLastInsertId();
Does getLastInsertId() return the ID from the data I've saved 2 lines above. Or does it return the latest ID that's created?
I mean
what happen if when the function 1 (__thisFunctionTakesAVeryLongTimeToExecute();) execute another user do an another save. then which id will i get?
$this->Model->id
Is used to set an id and read or modify data related.
$this->Model->getLastInsertID();
Return the id of the last inserted row in this model.
For your last question, make a test ! And publish your answer here.
I have another similar problem.
If I put
$oid = $this->Home->Order->getLastInsertID();
$order = $this->Home->Order->find('first',array('conditions'=>array( 'Order.id'=>$oid)));
the model associations between Order and its hasMany tables are destroyed.
If I put
$oid = 1; // for example
$order = $this->Home->Order->find('first',array('conditions'=>array( 'Order.id'=>$oid)));
the model associations are maintained!!!!
Use below code for cakePHP 3.0 or above
$result = $this->ModelName->save($data)
echo $result->id;
I have to run a query after every update and I want to know if there is a way to automate a $this->db->query() before every $this->db->update()
I'm using it for log.
you can write your own function in the file core/MY_Model.php to do that:
function queryThenUpdate($query,$update)
{
$query = $this->db->query($query);
//use as you need $query
$this->db->update($update['table'],$update['data']);
}
where:
$query is your actual query: SELECT * FROM ...
$update is an array of two elements $update['table'] is the table to update and $update['data'] is the updating data as specified on codeigniter active record's documentation
then make every model extend MY_Model
class Your_Model extend MY_Model
and every time you need to update something:
$this->Your_Model->queryThenUpdate($query,$update)
I believe you want to use codeigniters 'hook'
I have been trying to figure out how to do this and it seems that its not something that many people are trying to do in cakephp or I am just completely misunderstanding the documentation.. I am using the following query below to get a field value so I get a value where the "findbycreated" is 0... this part works fine
$unregisteredemail = $this->Testmodel->findBycreated('0');
$emailaddress = $unregisteredemail['Testmodel']['emailaddress'] ;
$emailpassword = $unregisteredemail['Testmodel']['password'] ;
But now, after I do some things with this data that I retrieved, I want to mark a field, in the same row, in the same model / table as a value of '1' to indicate that an action has taken place (email address has been successfully created, for example)... I just can't figure out how to do this in cakephp despite my efforts of going through the documentation and searching, this should be rather simple, I am tempted, at this point, to just use a regular mysql query as its a simple query.. basically the query is (please excuse my syntax as I haven't used direct mysql queries in a while) "update (database / table) set 'created'='1' where 'emailaddress'=$emailaddress"
Or I could use the row ID, if needed, as cakephp seems to prefer this, but still can't get how to do this.. this is my attempt below that is not working:
// update database to show that email address has been created
$this->Testmodel->read('emailaddress', $this->Testmodel->findBycreated('0'))
$this->Testmodel->id = 1;
$this->Testmodel->set(array(
'created' => '1'
));
$this->Testmodel->save();
There are, as you can see from the previous answers, several ways to achieve the same end. I'd just like to explain a little about why your way didn't work.
In the model, CakePHP has abstracted the database row(s) into an array according its implementation of ORM . This provides us with a handy way of manipulating the data and chucking it around the MVC architecture.
When you say:
$this->Testmodel->set(array(
'created' => '1'
));
You are dealing directly with the model, but the data is actually stored, as an array, in a class variable called $data. To access and manipulate this data, you should instead say:
$this->data['Testmodel']['created'] => '1';
The reason for specifying the model name as the first index is that where associated tables have been retrieved, these can be accessed in the same way, so you might have , for instance:
Array([Testmodel] => Array ([id] => 1,
[created] => [1],
...
)
[Relatedmodel] => Array ([id] => 1,
[data] => asd,
...
)
)
...and so on. Very handy.
Now, when you use $this->MyModelName->save() with no parameters, it uses $this->data by default and uses the part of the array of data appropriate to the model you are calling the save method on. You can also pass an array of data, formatted in the same way if, for some reason, you don't (or can't) use $this->data.
Your use of the method read() is incorrect. The first parameter should be null, a string or an array of strings (representing fieldname(s)). The second parameter should be the id of the record you wish to read. Instead, for param 2, you are passing the result of a find, which will be an array. The result, which you are not capturing, will be empty.
I would write your code like:
$this->data = $this->Testmodel->read('emailaddress',1);
$this->data['Testmodel']['created'] = 1;
$this->Testmodel->save();
or more succinctly:
$this->Testmodel->id = 1;
$this->Testmodel->saveField('created', 1);
In this situation I would let Cake deal with the id's and just focus on changing the row data and resaving it to the database
$row = $this->Model->findBycreated(0);
$row['Model']['emailaddress'] = 1;
$this->Model->save($row);
This way, you don't have to worry about the id's, as the id will be in your dataset anyway, so just change what you want and then tell Cake to save it.
Ninja edit, Be sure that you are returning a full row with an id from your findBycreated() method.
There're many ways to do your work.I suggest you to read the cookbook about saving data in cakephp.And besides david's solution another simple way would be
$this->Testmodel->id = 1;
$this->Testmodel->saveField('created' =>'1');
Ok, I think I finally found the solution, I was able to get this to work:
$this->Test->updateAll(
array(
'Test.field' => 'Test.field+100'
),
array(
'Test.id' => 1
)
);
I think you have to use updateAll as anything else will just create a new row.. basically CakePHP, for whatever reason, neglected to include a function for updating just one field so you have to put it into an array with the updateAll to make it work...
the +100 is where the updated info goes, so in this case "100" would be what the field is updated to.
In cakephp 3.x the syntax seems to be different. This is what worked for me in 3.x:
$this->Tests->updateAll(
[
'Tests.field = Tests.field+100'
],
[
'Tests.id' => 1
]
];
The difference is that the entire expression needs to be in the value of the first array.