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

Pax0r picture Pax0r · Dec 31, 2011 · Viewed 7.6k times · Source

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?

Answer

Alfredo Castaneda Garcia picture Alfredo Castaneda Garcia · Jan 1, 2012

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.