Cdbcriteria Join - Column Not Found - yii

I have the following SQL
SELECT entry_subject.id, entry_subject.subject_id
FROM entry_subject
INNER JOIN subject
ON entry_subject.subject_id = subject.id
WHERE subject.id = 71
I have arrived at the following which is an almost carbon copy of a number of examples, however I'm getting 1054 Unknown column 'EntrySubject.subject_id' in 'field list'.
$subjectEntries = new CDbCriteria();
$subjectEntries->alias = 'EntrySubject';
$subjectEntries->select = 'EntrySubject.id, EntrySubject.subject_id';
$subjectEntries->join = 'INNER JOIN Subject ON Subject.id = EntrySubject.subject_id';
$subjectEntries->condition = 'Subject.id=71';
$subjectEntriesModel=Subject::model()->findAll($subjectEntries);
My relationships are as follows
return array(
'entrySubject' => array(self::HAS_MANY, 'EntrySubject', 'subject_id'),
'phase' => array(self::BELONGS_TO, 'Phase', 'phase_id'),
'subjectType' => array(self::BELONGS_TO, 'SubjectType', 'subject_type'),
);
What am I doing wrong?
Many thanks

I assume you are trying to get subject entries for subject #71, then you should simply try the following :
$subject = Subject::model()->findByPk(71);
$subjectEntriesModel = $subject->entrySubject;
EDIT : you should, usually, avoid eager loading (as described in miog and Burhan Çetin answers) for a HAS_MANY relation

From soju's answer above
I assume you are trying to get subject entries for subject #71, then
you should simply try the following :
$subject = Subject::model()->findByPk(71);
$subjectEntriesModel = $subject->entrySubject;
I would suggest an addition to this.
$subject = Subject::model()->with('entrySubject')->findByPk(71);
$subjectEntriesModel = $subject->entrySubject;
->with('entrySubject') was added
This will allow yii to use one query instead if it is possible.

$query = Subject::model()->with('entrySubject')->findbypk($id);

Related

Findallbyattributes With Related Model

I'm trying to do a findAllByAttributes using a related model column as one of the criteria, but I keep getting a CDbException stating the column cannot be found.
Here's my model Relationship:
public function relations() {
return array(
'MetaData' => array(self::BELONGS_TO, 'ProjectMeta', 'wbse_or_io'),
);
}
And here's my attempted query:
$listing = ProjectIndex::model()->with('MetaData')
->findAllByAttributes(array(
'report_date'=>$reportDate,
'MetaData.cost_centre'=>$costCentre
)
);
From what I've read through Google/StackOverflow/these forums, I should be able to reference the cost_centre column in the MetaData relationship. But I keep getting the following error:
Table "tbl_project_index" does not have a column named "MetaData.cost_centre"
How do I reference the related table column?
Check this out
$listing = ProjectIndex::model()->with(
'MetaData'=>array(
'condition'=>'cost_centre = :cost_centre',
'params'=>array('cost_centre'=>$costCentre))
)
->findAllByAttributes(array('report_date'=>$reportDate));
The attributes in the attributes array cannot be for the related models. You can look at the source for findAllByAttributes for a better explanation. You can, however, pass the related attribute as a condition string or CDbCriteria array, in addition to Alex's answer.
$listing = ProjectIndex::model()->with('MetaData')->findAllByAttributes(
array('report_date'=>$reportDate),
'cost_centre = :cost_centre',
array(':cost_centre'=> $costCentre)
);
Or
$listing = ProjectIndex::model()->with('MetaData')->findAllByAttributes(
array('report_date'=>$reportDate),
array(
'condition' =>'cost_centre = :cost_centre',
'params'=>array(':cost_centre'=> $costCentre)
),
);

Yii with join using CDbCriteria and CActiveDataProvider with custom Search

Hi I am using Yii to create an Application
I have modified the search() in model and I have come up with a problem.
Users can log in as admins, managers, clients
When a user is logged as a manager, he can view clients only from the store they both belong. So far so good, I've managed to accomplish that.
Now the problem is when I try to prevent managers from viewing other managers from the same store (and therefore edit each other's accounts) in CGridView.
The relations
/**
* #return array relational rules.
*/
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(
'authitems' => array(self::MANY_MANY, 'Authassignment', 'authassignment(userid, itemname)'),
'additionalContacts' => array(self::HAS_MANY, 'AdditionalContact', 'Client_Id'),
'store' => array(self::BELONGS_TO, 'Store', 'Store_Id'),
'user' => array(self::BELONGS_TO, 'User', 'User_Id'),
'genericPoints' => array(self::HAS_MANY, 'GenericPoint', 'Client_Id'),
);
}
The Custom model search
public function searchCustom()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->addCondition('t.id !='.$this->id);
$criteria->compare('t.Created',$this->Created,true);
$criteria->compare('t.Updated',$this->Updated,true);
$criteria->compare('t.Discount',$this->Discount,true);
$criteria->compare('t.Discount_Type',$this->Discount_Type,true);
$criteria->addCondition('t.Store_Id ='.$this->Store_Id);
//$criteria->addCondition('t.id !='.$this->id);
//GET FIELDS FROM USER IN SEARCH
$criteria->with=array('user');
$criteria->compare('user.id',$this->User_Id,true);
$criteria->compare('user.First_Name',$this->First_Name,true);
$criteria->compare('user.Last_Name',$this->Last_Name,true);
$criteria->compare('user.Username',$this->Username,true);
$criteria->compare('user.Email',$this->Email,true);
$criteria->addCondition('user.Status = 1');
$criteria->with=array('authitems');
$criteria->compare('authitems.userid',$this->id,false);
$criteria->compare('authitems.itemname','client',false);
$criteria->together = true;
$criteria->order = 't.Created DESC';
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
And this the error I am getting
CDbCommand failed to execute the SQL statement: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'user.Status' in 'where clause'. The SQL statement executed was: SELECT COUNT(DISTINCT `t`.`id`) FROM `client` `t` LEFT OUTER JOIN `authassignment` `authitems_authitems` ON (`t`.`id`=`authitems_authitems`.`userid`) LEFT OUTER JOIN `authassignment` `authitems` ON (`authitems`.`itemname`=`authitems_authitems`.`itemname`) WHERE (((((t.id !=2) AND (t.Store_Id =1)) AND (user.Status = 1)) AND (authitems.userid=:ycp0)) AND (authitems.itemname=:ycp1))
I know it has something to do with the relations the gii set up for me but I can't pinpoint it.
Perhaps authitems' MANY_MANY relation stops the user relation from loading?
Instead of making another assignement for $criteria->with (which will override the previous one), you should simply try :
$criteria->with=array('user', 'authitems');

Doctrine query builder using inner join with conditions

I'd like to construct the following SQL using Doctrine's query builder:
select c.*
from customer c
join phone p
on p.customer_id = c.id
and p.phone = :phone
where c.username = :username
First I tried
$qb->select('c')
->innerJoin('c.phones', 'p', Join::ON, $qb->expr()->andx(
$qb->expr()->eq('p.customerId', 'c.id'),
$qb->expr()->eq('p.phone', ':phone')
))
->where('c.username = :username');
But I'm getting the following error
Error: expected end of string, got 'ON'
Then I tried
$qb->select('c')
->innerJoin('c.phones', 'p')
->where('c.username = :username')
->andWhere('p.phone = :phone');
which seems to be working. However, does anyone know what's wrong with the first attempt? I'd like to make the first one work since it resembles more closely to how SQL is structured.
Note: I know we can also write native mysql or dql with Doctrine, but I'd prefer query builder.
EDIT: Below is the entire code
namespace Cyan\CustomerBundle\Repository;
use Cyan\CustomerBundle\Entity\Customer;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
class CustomerRepository extends EntityRepository
{
public function findCustomerByPhone($username, $phone)
{
$qb = $this->createQueryBuilder('c');
$qb->select('c')
->innerJoin('c.phones', 'p', Join::ON, $qb->expr()->andx(
$qb->expr()->eq('p.customerId', 'c.id'),
$qb->expr()->eq('p.phone', ':phone')
))
->where('c.username = :username');
// $qb->select('c')
// ->innerJoin('c.phones', 'p')
// ->where('c.username = :username')
// ->andWhere('p.phone = :phone');
$qb->setParameters(array(
'username' => $username,
'phone' => $phone->getPhone(),
));
$query = $qb->getQuery();
return $query->getResult();
}
}
I'm going to answer my own question.
innerJoin should use the keyword "WITH" instead of "ON" (Doctrine's documentation [13.2.6. Helper methods] is inaccurate; [13.2.5. The Expr class] is correct)
no need to link foreign keys in join condition as they're already specified in the entity mapping.
Therefore, the following works for me
$qb->select('c')
->innerJoin('c.phones', 'p', 'WITH', 'p.phone = :phone')
->where('c.username = :username')
->setParameter('phone', $phone)
->setParameter('username', $username);
or
$qb->select('c')
->innerJoin('c.phones', 'p', Join::WITH, $qb->expr()->eq('p.phone', ':phone'))
->where('c.username = :username')
->setParameter('phone', $phone)
->setParameter('username', $username);;
You can explicitly have a join like this:
$qb->innerJoin('c.phones', 'p', Join::ON, 'c.id = p.customerId');
But you need to use the namespace of the class Join from doctrine:
use Doctrine\ORM\Query\Expr\Join;
Or if you prefere like that:
$qb->innerJoin('c.phones', 'p', Doctrine\ORM\Query\Expr\Join::ON, 'c.id = p.customerId');
Otherwise, Join class won't be detected and your script will crash...
Here the constructor of the innerJoin method:
public function innerJoin($join, $alias, $conditionType = null, $condition = null);
You can find other possibilities (not just join "ON", but also "WITH", etc...) here: http://docs.doctrine-project.org/en/2.0.x/reference/query-builder.html#the-expr-class
EDIT
Think it should be:
$qb->select('c')
->innerJoin('c.phones', 'p', Join::ON, 'c.id = p.customerId')
->where('c.username = :username')
->andWhere('p.phone = :phone');
$qb->setParameters(array(
'username' => $username,
'phone' => $phone->getPhone(),
));
Otherwise I think you are performing a mix of ON and WITH, perhaps the problem.

Yii update with join

I have a code
$command = Yii::app()->db->createCommand()
->update(
'queue q',
array('i.status_id' => $status_id)
)
->join('item i', 'q.item_id = i.item_id')
->where('IN', 'queue_id', $ids);
after I call $command->buildQuery() I get an error:
CDbCommand failed to execute the SQL statement: Invalid parameter number: parameter was not defined. The SQL statement executed was: UPDATE queue q SET i.status_id=:i.status_id
The impression is that it does not see the join and where commands.
What the problem?
Your code is valid with the newest Yii version. This MySQL-specific functionality has been added as of 1.1.14: https://github.com/yiisoft/yii/commit/ed49b77ca059c0895be17df5813ee1e83d4c916d.
The where clause should be in the update() function like this
Yii::app()->db->createCommand()
->update(
'queue q',
array('i.status_id' => $status_id),array('in', 'queue_id', $ids)
);
And regarding the JOIN part there is a open bug at https://github.com/yiisoft/yii/issues/124 (Im not sure. Correct me if Im wrong). Please let me know if there is a workaround.
You have to bind the parameters:
$command = Yii::app()->db->createCommand()
->update(
'queue q',
array('i.status_id' => ':status_id'),
array('in', 'queue_id', $ids),
array(':status_id' => $status_id),
)
->join('item i', 'q.item_id = i.item_id');
Having come across this problem a few times in my projects I have come-up with the following Yii work-around using CDbCriteria which is a little hacky, but gives the security of param count matching.
When applied to your example my code would be (guessing a little bit of your structure):
$ids = array(1,2,3,4,5);
$criteria = new CDbCriteria();
$criteria->addInCondition('i.queue_id',$ids);
$sql = '
UPDATE queue q
JOIN item i
ON q.item_id = i.item_id
SET i.status_id = :status
WHERE '.$criteria->condition;
$command = Yii::app()->db->createCommand($sql);
$command->bindValue('status',$status);
$command->bindValues($criteria->params);
$rows = $command->execute();

ActiveRecord Arel OR condition

How can you combine 2 different conditions using logical OR instead of AND?
NOTE: 2 conditions are generated as rails scopes and can't be easily changed into something like where("x or y") directly.
Simple example:
admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)
It's easy to apply AND condition (which for this particular case is meaningless):
(admins.merge authors).to_sql
#=> select ... from ... where kind = 'admin' AND kind = 'author'
But how can you produce the following query having 2 different Arel relations already available?
#=> select ... from ... where kind = 'admin' OR kind = 'author'
It seems (according to Arel readme):
The OR operator is not yet supported
But I hope it doesn't apply here and expect to write something like:
(admins.or authors).to_sql
ActiveRecord queries are ActiveRecord::Relation objects (which maddeningly do not support or), not Arel objects (which do).
[ UPDATE: as of Rails 5, "or" is supported in ActiveRecord::Relation; see https://stackoverflow.com/a/33248299/190135 ]
But luckily, their where method accepts ARel query objects. So if User < ActiveRecord::Base...
users = User.arel_table
query = User.where(users[:kind].eq('admin').or(users[:kind].eq('author')))
query.to_sql now shows the reassuring:
SELECT "users".* FROM "users" WHERE (("users"."kind" = 'admin' OR "users"."kind" = 'author'))
For clarity, you could extract some temporary partial-query variables:
users = User.arel_table
admin = users[:kind].eq('admin')
author = users[:kind].eq('author')
query = User.where(admin.or(author))
And naturally, once you have the query you can use query.all to execute the actual database call.
I'm a little late to the party, but here's the best suggestion I could come up with:
admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)
admins = admins.where_values.reduce(:and)
authors = authors.where_values.reduce(:and)
User.where(admins.or(authors)).to_sql
# => "SELECT \"users\".* FROM \"users\" WHERE ((\"users\".\"kind\" = 'admin' OR \"users\".\"kind\" = 'author'))"
As of Rails 5 we have ActiveRecord::Relation#or, allowing you to do this:
User.where(kind: :author).or(User.where(kind: :admin))
...which gets translated into the sql you'd expect:
>> puts User.where(kind: :author).or(User.where(kind: :admin)).to_sql
SELECT "users".* FROM "users" WHERE ("users"."kind" = 'author' OR "users"."kind" = 'admin')
From the actual arel page:
The OR operator works like this:
users.where(users[:name].eq('bob').or(users[:age].lt(25)))
I've hit the same problem looking for an activerecord alternative to mongoid's #any_of.
#jswanner answer is good, but will only work if the where parameters are a Hash :
> User.where( email: 'foo', first_name: 'bar' ).where_values.reduce( :and ).method( :or )
=> #<Method: Arel::Nodes::And(Arel::Nodes::Node)#or>
> User.where( "email = 'foo' and first_name = 'bar'" ).where_values.reduce( :and ).method( :or )
NameError: undefined method `or' for class `String'
To be able to use both strings and hashes, you can use this :
q1 = User.where( "email = 'foo'" )
q2 = User.where( email: 'bar' )
User.where( q1.arel.constraints.reduce( :and ).or( q2.arel.constraints.reduce( :and ) ) )
Indeed, that's ugly, and you don't want to use that on a daily basis. Here is some #any_of implementation I've made : https://gist.github.com/oelmekki/5396826
It let do that :
> q1 = User.where( email: 'foo1' ); true
=> true
> q2 = User.where( "email = 'bar1'" ); true
=> true
> User.any_of( q1, q2, { email: 'foo2' }, "email = 'bar2'" )
User Load (1.2ms) SELECT "users".* FROM "users" WHERE (((("users"."email" = 'foo1' OR (email = 'bar1')) OR "users"."email" = 'foo2') OR (email = 'bar2')))
Edit : since then, I've published a gem to help building OR queries.
Just make a scope for your OR condition:
scope :author_or_admin, where(['kind = ? OR kind = ?', 'Author', 'Admin'])
Using SmartTuple it's going to look something like this:
tup = SmartTuple.new(" OR ")
tup << {:kind => "admin"}
tup << {:kind => "author"}
User.where(tup.compile)
OR
User.where((SmartTuple.new(" OR ") + {:kind => "admin"} + {:kind => "author"}).compile)
You may think I'm biased, but I still consider traditional data structure operations being far more clear and convenient than method chaining in this particular case.
To extend jswanner answer (which is actually awesome solution and helped me) for googling people:
you can apply scope like this
scope :with_owner_ids_or_global, lambda{ |owner_class, *ids|
with_ids = where(owner_id: ids.flatten).where_values.reduce(:and)
with_glob = where(owner_id: nil).where_values.reduce(:and)
where(owner_type: owner_class.model_name).where(with_ids.or( with_glob ))
}
User.with_owner_ids_or_global(Developer, 1, 2)
# => ...WHERE `users`.`owner_type` = 'Developer' AND ((`users`.`owner_id` IN (1, 2) OR `users`.`owner_id` IS NULL))
What about this approach: http://guides.rubyonrails.org/active_record_querying.html#hash-conditions (and check 2.3.3)
admins_or_authors = User.where(:kind => [:admin, :author])
Unfortunately it is not supported natively, so we need to hack here.
And the hack looks like this, which is pretty inefficient SQL (hope DBAs are not looking at it :-) ):
admins = User.where(:kind => :admin)
authors = User.where(:kind => :author)
both = User.where("users.id in (#{admins.select(:id)}) OR users.id in (#{authors.select(:id)})")
both.to_sql # => where users.id in (select id from...) OR users.id in (select id from)
This generates subselets.
And a little better hack (from SQL perspective) looks like this:
admins_sql = admins.arel.where_sql.sub(/^WHERE/i,'')
authors_sql = authors.arel.where_sql.sub(/^WHERE/i,'')
both = User.where("(#{admins_sql}) OR (#{authors_sql})")
both.to_sql # => where <admins where conditions> OR <authors where conditions>
This generates proper OR condition, but obviously it only takes into account the WHERE part of the scopes.
I chose the 1st one until I'll see how it performs.
In any case, you must be pretty careful with it and watch the SQL generated.