Cakephp 2.8.4 ignoring hasMany - php

This is a very odd issue that I have not been able to figure out for a very long time.
I have a model Upload, with several hasMany all being ignored by UploadModel.
For example. Upload hasMany Face.
On comments table I have a field called foreign_key that hosts the Upload.id.
So the relationship in UploadModel looks like this:
$hasMany = ['Face' => ['foreignKey' => 'Face.upload_id']];
When performing a find in Upload with contain Face + conditions [Face.celebrity_id = 4018] I get the following error because the query is missing the Left Join to Face:
$media = $this->Upload->find('all',array(
'conditions' => array(
'Face.celebrity_id' => 4018
),
'contain' => array(
'Face'
)
));
SQL Query:
SELECT `Upload`.`id`, `Upload`.`user_id`, `Upload`.`filename`, `Upload`.`created`
FROM `uploads` AS `Upload`
WHERE `Face`.`celebrity_id` = 4018;
SQL Error:
Unknown column 'Face.celebrity_id' in 'where clause'
As you can see the query is missing the left joing to Face, that's all the problem
If instead of in the $hasMany, I add the Face relationship in $belongsTo, it adds the relationship to the query and it works!
But an Upload can have many Faces, and Face.upload_id has the foreignKey to Upload, so IT NEEDS TO BE A motherf***** HASMANY... lol
As you can see, this is terrible, I am already desperate, I have bypassed the issue by adding a bindModel() before each query, but it is totally unnecessary, the hasMany should work!!!! f****!!!!!
The query that I want Cake to perform for me is this:
SELECT `Upload`.`id`, `Upload`.`filename` FROM `uploads` AS `Upload`
LEFT JOIN `upload_faces` AS `Face` ON (`Upload`.id = `Face`.`upload_id`)
WHERE `Face`.`celebrity_id` = 4018
I appreciate any help, thanks.

ok, so basically, it is not ignoring the hasMany, but I can not use conditions on a hasMany relationship, can only be done if it is a hasOne

In your test() function, you run query on upload table. So it can not match Face.celebrity_id field from your query. Two things you have know first:
Condition you are writing in conditions clause is applied on the table matching with your model. In your case, upload is the table on which query is executed and your table contains no field Face.celebrity_id.
$hasMany creates application level relations(associations). So by doing query like you have written in test() function, it doesn't join the query results.
What you can do is,
$this->find('all', array(
'contain' => array(
'Face' => array('conditions' => 'Face.celebrity_id = 4018'))))
This will return all rows of upload table with associated row of face table if celebrity_id is 4018, otherwise null array.
Hope this will help you and if you want that only those many rows will be returned which are associated with Face.celebrity_id = 4018, first you have to run query on face table and then another on first query result.
For more detail of second case you can refer this.

Related

Yii - Joining multiple tables using active record

I am currently working on a YII application, which I must admit is my first attempt. I am having difficulty mastering the active record component of YII. In particular I would like to join three tables using with().
My mysql tables are as follows:
video_specific
- id (primary key)
- random_id
- user_id
- video_link
- quality (enum, high,low)
video_details
- video_id
- upvote_count
- downvote_count
- timestamp
ladder_videos
- ladder_id
- video_id
ladder_specific
- id
- random_id
- name
- description
- ladder_type
- status
- video_count
Thus after using the gii tool I was give models with the following relationships. Please note that I did not create a model for the ladder_videos table.
In videoSpecific model
'ladderSpecifics' => array(self::MANY_MANY, 'LadderSpecific',
'ladder_videos(video_id, ladder_id)'),
'videoDetails' => array(self::HAS_ONE, 'VideoDetails', 'video_id')
In ladderSpecific model
'videoSpecifics' => array(self::MANY_MANY, 'VideoSpecific',
'ladder_videos(ladder_id, video_id)')
With these relations I thought I could right the following query
$ladders = LadderSpecific::model()->with(
array('videoSpecifics'=>array('select'=>'id,video_link,random_id',
'join'=>'videoDetails')))->findAll();
But I get the following error
CDbCommand failed to execute the SQL statement: 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 'videoDetails'
at line 1. The SQL statement executed was: SELECT `t`.`id` AS `t0_c0`, `t`.`random_id` AS
`t0_c1`, `t`.`name` AS `t0_c2`, `t`.`description` AS `t0_c3`, `t`.`ladder_type` AS
`t0_c4`, `t`.`status` AS `t0_c5`, `t`.`video_count` AS `t0_c6`, `videoSpecifics`.`id` AS
`t1_c0`, `videoSpecifics`.`video_link` AS `t1_c3`, `videoSpecifics`.`random_id` AS `t1_c1`
FROM `ladder_specific` `t` LEFT OUTER JOIN `ladder_videos` `videoSpecifics_videoSpecifics`
ON (`t`.`id`=`videoSpecifics_videoSpecifics`.`ladder_id`) LEFT OUTER JOIN `video_specific`
`videoSpecifics` ON (`videoSpecifics`.`id`=`videoSpecifics_videoSpecifics`.`video_id`)
videoDetails
Any ideas why?? Please limit your answers to active record, and not DAO or query builder. Thanks
It would help to review how relations work in Yii.
http://www.yiiframework.com/doc/guide/1.1/en/database.arr
When querying the model, Yii automatically generates the query to fetch the relational data, so you don't have to supply it.
You must therefore supply the name given to your relation.
$ladders = LadderSpecific::model()->with('videoSpecifics')->findAll();
SO the answer was to update the ladderSpecific relations. So once you update them they should like this:
public function relations(){
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'ladderDetails' => array(self::HAS_ONE, 'LadderDetails', 'ladder_id'),
'videoSpecifics' => array(self::MANY_MANY, 'VideoSpecific', 'ladder_videos(ladder_id, video_id)'),
array(self::HAS_ONE, 'VideoDetails', 'video_id'),
);
}
Once you have updated this relation you can do this:
$ladders = LadderSpecific::model()->with(
array('videoSpecifics.videoDetails'=>
array(
'order'=>'videoDetails.upvote_count - videoDetails.downvote_count DESC',
'limit'=>'1'
)))->findAll(array('order'=>'name',));
The only issue is for some reason I can not limit the number of records returned. Once I figure that out I will update my answer. If you have a better way of doing it let me know.

Kohana add function return province name

I'm back again for another question, i'm trying and trying. But i can't get it fixed.
This is my issue;
I have a database table, with a ProvinceID this can alter from 1 to 12. But it's an ID of the province. The provinces are stored with the value pName in the table Provinces
I have the following code to alter the table, and join it with the preferences table
$veiling = ORM::factory('veilingen')
->select('veilingvoorkeur.*')
->join('veilingvoorkeur', 'LEFT')
->on('veilingen.id', '=', 'veilingvoorkeur.vId')
->find_all();
$this->template->content = View::factory('veiling/veilingen')
->bind('veiling', $veiling);
It displays correctly, in the view i have;
echo '<div class="row">';
foreach($veiling as $ve)
{
echo $ve->provincie;
}
?>
</div>
it displays the provincie id; but i want to add a function to it; So it will be transformed to a province name. Normally i would create a functions.php file with a function getProvince($provinceId)
Do a mysql query to grab the pName value from Provinces and that is the job. But i'm new to kohana. Is there an option to turn the province id to province['pName'] during the ORM selection part, or do i have to search for another solution. Which i can't find :(
So. Please help me on the road again.
Thanx in advance.
Kind regards,
Kevin
Edit: 1:08
I've tried something, and it worked. I used ORM in the view file, for adding a function;
function provincie($id){
$provincie = ORM::factory('provincies', $id);
return $provincie->pName;
}
But i'm not glad with this way of solution, is there any other way? Or am i have to use it this way?
Check out Kohana's ORM relationships. If you use this, you could access the province name by simply calling:
$ve->provincie->name;
The relationship definition could look something like:
protected $_belongs_to = array(
'provincie' => array(
'model' => 'Provincie',
'foreign_key' => 'provincie_id'
),
);
Note that if you have a table column called provincie the relationship definition I give above will not work. You'd either have to change the table column to provincie_id or rename the relationship to e.g. provincie_model.
The output you describe when you do echo $ve->provincie; suggests that you store the ID in a column called provincie, thus the above applies to you.
I'd personally go for the first option as I prefer accessing IDs directly with an _id suffix and models without any suffix. But that's up to you.
Edit: If you use Kohanas ORM relationships you could even load the province with the initial query using with() e.g:
ORM::factory('veiling')->with('provincie')->find_all();
This could save you hundreds of extra queries.

Yii Framework - two relations via the same "through" table

Mine goal is to have possibility to search Documents via the Users Names and Surnames and also via Recrutation Year and Semester.
Documents are related only to Declarations in such a way that Document are connected to exatly one Declaration and Declaration can be connected to exatly One or none Documents.
Declarations are related to OutgoingStudent and Recrutation.
So when quering Documents I want to query also OutgoingStudent and Recrutations via the Declaration table.
My code for relations in Documents:
return array(
'declaration' => array(self::BELONGS_TO, 'Declaration', 'DeclarationID'),
'outgoingStudentUserIdUser' => array(self::HAS_ONE, 'OutgoingStudent', 'OutgoingStudent_User_idUser','through'=>'declaration',),
'Recrutation' => array(self::HAS_ONE, 'Recrutation', 'Recrutation_RecrutationID','through'=>'declaration'),
);
And now when in search() function I want to make a query ->with
'declaration','outgoingStudentUserIdUser' and 'Recrutation':
$criteria->with = array('declaration','Recrutation','outgoingStudentUserIdUser');
I'm getting this error:
CDbCommand nie zdołał wykonać instrukcji SQL: SQLSTATE[42000] [1066]
Not unique table/alias: 'declaration'. The SQL statement executed was:
SELECT COUNT(DISTINCT t.DeclarationID) FROM Documents t LEFT
OUTER JOIN Declarations declaration ON
(t.DeclarationID=declaration.idDeclarations) LEFT OUTER JOIN
Recrutation Recrutation ON
(declaration.Recrutation_RecrutationID=Recrutation.RecrutationID)
LEFT OUTER JOIN Declarations declaration ON
(t.DeclarationID=declaration.idDeclarations) LEFT OUTER JOIN
OutgoingStudent outgoingStudentUserIdUser ON
(declaration.OutgoingStudent_User_idUser=outgoingStudentUserIdUser.User_idUser)
When using only $criteria->with = array('declaration','Recrutation') or $criteria->with = array('declaration','outgoingStudentUserIdUser') there is no error only when using both.
So probably it should be done in some other way, but how?
I have so many things to tell you! Here they are:
I find your relations function declaration pretty messy, and I'm not sure if it is doing what you want it to do (in case it worked). Here are my suggestions to re-declare it:
First of all, 'outgoingStudentUserIdUser' looks like a terrible name for a relation. In the end, the relation will be to instances of outgoingStudentUser, not only to 'ids'. So allow me to name it just as outgoingStudentUser. Now, this is my code:
'outgoingStudentUser' => array(self::HAS_ONE, 'OutgoingStudent', array('idDocuments'=>'idOutgoingStudent'),'through'=>'declaration',),
where 'idDocuments' is Documents' model primary key, and idOutgoingStudent is OutgoingStudent's model primary key.
The second relation could be corrected in a very similar way:
'Recrutation' => array(self::HAS_ONE, 'Recrutation', array('idDocuments'=>'idRecrutation'),'through'=>'declaration'),
where 'idDocuments' is Documents' model primary key, and idRecrutation is Recrutation's model primary key.
You can find that this is the correct declaration here: http://www.yiiframework.com/doc/guide/1.1/en/database.arr#through-on-self
But that's not all. I have more to tell you! What you're doing with your code is senseless. 'with' is used to force eager loading on related objects. In the following code:
$criteria->with = array('declaration','Recrutation','outgoingStudentUserIdUser');
you're just specifying in $criteria that when you retrieve in DB an instance of Documents using this $criteria, it will also fetch the models linked to that instance by the relations passed as parameters to 'with'. That's eager loading. It is used to reduce the number of queries to database. Is like saying: "go to DB and get me this instance of Documents, but once you're there, bring to me once per all the instances of other tables related to this object".
Well, that's what you're declaring, but certainly that's not what you want to do. How I know? Because that declaration is useless inside a search() function. As you may see here: http://www.yiiframework.com/doc/api/1.1/CDbCriteria/#with-detail , 'with' is only useful in some functions, and search() is not one of them. Inside search(), eager loading is pointless, senseless and useless.
So I see myself forced to ask you what are you trying to do? You say: "Mine goal is to have possibility to search Documents via the Users Names and Surnames and also via Recrutation Year and Semester", but what do you mean by "search Documents via the Users Names and..."? Do you want something like this: $user->documents, to return all the documents associated with $user? I hope you could be more specific about that, but perhaps in another, more to-the-point, question.
You can also try this:
return array(
'declaration' => array(self::BELONGS_TO, 'Declaration', 'DeclarationID'),
'outgoingStudentUserIdUser' => array(self::HAS_ONE, 'OutgoingStudent', 'OutgoingStudent_User_idUser','through'=>'declaration',),
'Recrutation' => array(self::HAS_ONE, 'Recrutation', '', 'on'=>'declaration.id=Recrutation.Recrutation_RecrutationID'),
);

how to get CakePHP many-to-many result

I have a many-to-many relation between the models Image and Units using a images_units join table.
How can I translate this query to a cakePHP find()?
SELECT * FROM Image, Units, images_units WHERE images_units.unit_id = 29;
Right now I'm trying find() on Image->find('all', $params); with no luck.
Straight from the CakePHP Manual:
$this->Image->bindModel(array('hasOne' => array('ImagesUnit')));
$this->Image->find('all', array('fields' => array('Image.*'),'conditions' => array('ImagesUnit.unit_id' => 29)));
Of course, you will need to have the HABTM association defined in the model. See the whole section on HABTM for learning how to use it.
In your Image Model, add the following code:
$hasAndBelongsToMany = 'Unit';
The find() in your Images controller should look like this:
$this->Image->find('all', array('conditions'=>array('Unit.id'=>29)));
Still not quite sure this is what you're looking for but I think this is correct.

Select specific field of nested HABTM array

I have a tickets table, and a contacts table. A ticket can have many contacts.
I am paginating the tickets table, and want it to select specific fields including the first linked contact form the nested array. No matter what I try, I can't seem to figure this one out (or if it is even possible).
My code:
$this->paginate = array(<br>
'conditions' => array('status_id !=' => '3'),<br>
'limit'=>50,<br>
'fields'=>array('Ticket.title', 'Ticket.ticket_number', 'Priority.name', 'Status.name', 'Contact.0.full_name') <br>
);
(The Contact.0.full_name is causing it to fail. How can I make this work?)
So I can use this column with $this->Paginator->sort.
You cant call columns like this Contact.0.full_name CakePHP doesn't work like that. A valid field name is TableAlias.column_name
You cant use hasMany relation's children into sort operation.
To achieve similar functionality, You can drop hasMany and add hasOne to achieve the desired output. Because there's no schema changes in both relationship types.
$this->Ticket->unbindModel(array('hasMany' => array('Contact')));
$this->Ticket->bindModel(array('hasOne' => array('Contact')));
$this->paginate = array(
'conditions' => array('status_id !=' => '3'),
'limit'=>50,
'fields'=>array('Ticket.title', 'Ticket.ticket_number', 'Priority.name', 'Status.name', 'Contact.full_name')
);
Now you can use this column Contact.full_name for sorting purposes.

Categories