Is it possible to make sub-queries in ActiveRecord in Yii?
i have a query like this:
select * from table1
where table1.field1 in (select table2.field2 from table2)
i'm currently using the fallowing code:
object1::model()->findAll(array('condition'=>'t.field1 in (select table2.field2 from table2)'))
[Edit]
i would like to know if there is a manner to construct the sub-query without using SQL, and without using joins.
Is there any solution ?
and thanks in advance.
First find doublets by db fields:
$model=new MyModel('search');
$model->unsetAttributes();
$criteria=new CDbCriteria();
$criteria->select='col1,col2,col3';
$criteria->group = 'col1,col2,col3';
$criteria->having = 'COUNT(col1) > 1 AND COUNT(col2) > 1 AND COUNT(col3) > 1';
Get the subquery:
$subQuery=$model->getCommandBuilder()->createFindCommand($model->getTableSchema(),$criteria)->getText();
Add the subquery condition:
$mainCriteria=new CDbCriteria();
$mainCriteria->condition=' (col1,col2,col3) in ('.$subQuery.') ';
$mainCriteria->order = 'col1,col2,col3';
How to use:
$result = MyModel::model()->findAll($mainCriteria);
Or:
$dataProvider = new CActiveDataProvider('MyModel', array(
'criteria'=>$mainCriteria,
));
Source: http://www.yiiframework.com/wiki/364/using-sub-query-for-doubletts/
No, there is not a way to programmatically construct a subquery using Yii's CDbCriteria and CActiveRecord. It doesn't look like the Query Builder has a way, either.
You can still do subqueries a few different ways, however:
$results = Object1::model()->findAll(array(
'condition'=>'t.field1 in (select table2.field2 from table2)')
);
You can also do a join (which will probably be faster, sub-queries can be slow):
$results = Object1::model()->findAll(array(
'join'=>'JOIN table2 ON t.field1 = table2.field2'
);
You can also do a direct SQL query with findAllBySql:
$results = Object1::model()->findAllBySql('
select * from table1 where table1.field1 in
(select table2.field2 from table2)'
);
You can, however, at least provide a nice AR style interface to these like so:
class MyModel extends CActiveRecord {
public function getResults() {
return Object1::model()->findAll(array(
'condition'=>'t.field1 in (select table2.field2 from table2)')
);
}
}
Called like so:
$model = new MyModel();
$results = $model->results;
One interesting alternative idea would be to create your subquery using the Query Builder's CDbCommand or something, and then just pass the resulting SQL query string into a CDbCritera addInCondition()? Not sure if this will work, but it might:
$sql = Yii::app()->db->createCommand()
->select('*')
->from('tbl_user')
->text;
$criteria->addInCondition('columnName',$sql);
You can always extend the base CDbCriteria class to process and build subqueries somehow as well. Might make a nice extension you could release! :)
I hope that helps!
I know this an old thread but maybe someone (like me) still needs an answer.
There is a small issues related to the previous answers. So, here is my enhancement:
$model=new SomeModel();
$criteria=new CDbCriteria();
$criteria->compare('attribute', $value);
$criteria->addCondition($condition);
// ... etc
$subQuery=$model->getCommandBuilder()->createFindCommand($model->getTableSchema(),$criteria)->getText();
$mainCriteria=new CDbCriteria();
$mainCriteria->addCondition($anotherCondition);
// ... etc
// NOW THIS IS IMPORTANT
$mainCriteria->params = array_merge($criteria->params, $mainCriteria->params);
// Now You can pass the criteria:
$result = OtherModel::model()->findAll($mainCriteria);
Related
So I've searched the internet for similar cases, and I just got lost from all contradicting answers, and unrelated scenarios. So I thought to put my case hoping to get some specific answers.
I am new to Laravel, and creating small application. In the application I have to search for offeres and show the result in a blade view. Since the query is complex, and output of the search does not belong to a specific Model, I just wish to keep it as a raw query.
I have placed the query in the controller, but I just don't feel it's the right place. Especially if I need to reuse the query in many places.
Here's the method from the OfferController:
public function search(Request $request)
{
$area = $request->area;
$size = $request->size;
$sql = "SELECT distinct product_name,product_offer.quantity, product_offer.price
FROM product
inner join brand on product.brand_id = brand.brand_id
inner join brand_area on brand_area.brand_id = brand.brand_id
inner join area on area.area_id = brand_area.area_id
inner join product_offer on product_offer.product_id = product.product_id
where area.area_id = :area
and product.size_id = :size ";
$params = array(
'area'=>$area,
'size'=>$size
);
$offers = DB::select( DB::raw($sql), $params);
return view('searchresult')->with($offers);
}
So in short: should I move the query to the model, create DAL class, or keep it here? Baring in mind the project is small scale.
in my opinion, if you are going to reuse it, create a service that will perform that query and gives you back a result, something like SearchService that looks like this:
<?php
class SearchService
{
public static function perform(array $params){
$sql = "SELECT distinct product_name,product_offer.quantity, product_offer.price
FROM product
inner join brand on product.brand_id = brand.brand_id
inner join brand_area on brand_area.brand_id = brand.brand_id
inner join area on area.area_id = brand_area.area_id
inner join product_offer on product_offer.product_id = product.product_id
where area.area_id = :area
and product.size_id = :size ";
return DB::select( DB::raw($sql), $params);
}
}
?>
And by doing so, you can just call
SearchService::perform([...]);
to get the results.
Obviously this is version1.0, you can improve it in a lot of ways, for example making it not static in order to make it testable, and also to allow getter and setter to exists, and a lot of other things, that might be usefull
You have a fair point saying it does not look right to place query in the controller. I would offer you to have a look at the concept of Laravel repository pattern:
https://dev.to/asperbrothers/laravel-repository-pattern-how-to-use-why-it-matters-1g9d
Also, I think you could use Laravel DB for this kind of query without the need to write it as a raw query. I do not think there is a huge need to have a raw query here. Laravel DB table(), select(), where() and other methods should be enough.
Actually, you could potentially write this using models and their relationships, but if the query is quite slow, it is better to use query builder for better efficiency.
EDIT:
also for some specific queries which do not belong to anything I remember seeing custom trait used, could also be a solution.
I'm writing my previous comment as an answer since I think its really what you are looking for:
I suggest you use a Trait to do this. The reason why is that it will keep your code clean and you'll be able to reuse this code later for other uses. A Trait is made for reusable code.
You will create a Trait:
<?php
namespace App\Traits; // Optional, but you could create a namespace for all your Traits if you need others
trait MyCustomTrait
{
public function perform(array $params) {
$sql = "SELECT distinct product_name,product_offer.quantity, product_offer.price
FROM product
inner join brand on product.brand_id = brand.brand_id
inner join brand_area on brand_area.brand_id = brand.brand_id
inner join area on area.area_id = brand_area.area_id
inner join product_offer on product_offer.product_id = product.product_id
where area.area_id = :area
and product.size_id = :size ";
return DB::select( DB::raw($sql), $params);
}
}
To use it, only write: use MyCustomTrait in the scope of your controller and call your function like this: $this->perform([...]).
Here's some links to learn more about traits:
https://www.w3schools.com/php/php_oop_traits.asp
https://www.develodesign.co.uk/news/laravel-traits-what-are-traits-and-how-to-create-a-laravel-trait/.
Hope it helps you!
I want to change this SQL query : (i'm using a bundle)
(Evenement is an entity and evenement_evenement is the result of self-referencing many to many of evenement)
SELECT *
FROM evenement e
natural JOIN evenement_evenement ee
WHERE e.id = ee.evenement_source
AND e.id = 3
Into DQL. For now I have this :
public function findAllEventAssociateByEvent($idEvent){
$qb = $this->createQueryBuilder('e');
$qb->add('select', 'e');
$qb->from('Bundle:Evenement', 'e2');
$qb->where('e = :evenement');
$qb->andWhere('e2 in e.evenements');
$qb->setParameter('evenement', $idEvent);
return $qb;
//select * from evenement e NATURAL join evenement_evenement ee where e.id = ee.evenement_source and e.id = $idEvent
}
And i have this :
$eventAssocies = $em->getRepository('Bundle:Evenement')->findAllEventAssociateByEvent($id)->getQuery()->getResult();
But it's not working, i have an error in my "andWhere", but I don't know why...
I think you misunderstood some stuff, I reckon you query should more look like this:
public function findAllEventAssociateByEvent($idEvent){
$qb = $this->createQueryBuilder('e')
->join('e.evenement_evenement', 'e2')
->where('e = :evenement')
->setParameter('evenement', $idEvent);
return $qb;
}
The join will do what you were trying to do with your AndWhere I reckon
You cannot use andWhere like that, you have to treat it like you treated your where clause. Declare a parameter, and later on, set it. From what I understand, you'd need to use a subquery for that e.evenements part. Whatever you expect to be in e.evenements part, extract it to a variable, e.g: $evenements and do the following:
$qb->andWhere('e2 IN (:evenements)')
->setParameter('evenements', $evenements, Doctrine\DBAL\Connection::PARAM_INT_ARRAY);
Assuming $evenements is an array. If it's a string, you can explode() it for example.
If you are looking for a quick solution, try this:
$qb = $this->createQueryBuilder('e')
->select('e.id')
->where('e.id = :evenement')
->setParameter('evenement', $idEvent);
$evenementsIds = $qb->getQuery()->getResult(); // This will get you an array of ID's
$qb2 = $this->createQueryBuilder('e2')
->where('e2.id IN (:evenements)')
->setParameter('evenements', $evenementsIds, Doctrine\DBAL\Connection::PARAM_INT_ARRAY);
$result = $qb2->getQuery()->getResult();
I got 2 tables User and Group; each object Group got an array members[] that points to some instances of User.
I need to build this same query with QueryBuilder :
SELECT (users instances) FROM User
WHERE (users instances) IN (SELECT Group.members from Group WHERE group.id = $someId)
How can I achieve that?
You have to make 2 querybuilders:
$qb2 = $this->em->createQueryBuilder('group')
->select('group.members')
->where('group.id = $someId');
$qb = $this->em->createQueryBuilder('user')
->select(user instances)
-where($qb->expr()->in('user instances', $qb2->getDQL());
it will give you the idea how it works. Of course you have to adjust this code to yours.
Improving #Eimsas Answer it could be:
$em = $this->getEntityManager();
$qb2 = $em->createQueryBuilder('group')
->select('group.members')
->where('group.id = $someId');
$qb = $em->createQueryBuilder('user');
$query = $qb->select(user instances)
->where($qb->expr()->in('user instances', $qb2->getDQL());
$result = $query->getQuery->getResult();
I want to get the user that wrote the most articles. I do so fine in two ways with ActiveRecord like the following:
$table = Articles::find()
->select('articles.*, COUNT(*) AS cnt')
->with('user','userDetails')
->groupBy('articles.user_id')
->orderBy(('cnt DESC'))
->limit(10)
->offset($offset)
->all();
and with a query like the following:
$query = (new Query())
->select('articles.user_id, COUNT(*) AS num_articles')
->from('articles')
->join('LEFT JOIN', 'user_details', 'user_details.user_id = articles.user_id')
->groupBy('articles.user_id')
->orderBy('num_articles DESC')
->limit(10)
->offset($offset)
->all();
The problem is that the ActiveRecord gives me further needed informations userDetails that I need. But I do not get the amount of articles of user that should be on cnt
With the Query I get the user_id and the amount of articles. But I do not get it working by joining with userDetails. All of these does not work: LEFT JOIN, RIGHT JOIN, INNER JOIN.
I am interested in resolving both for learning, but for concrete I need help with the ActiveRecord problem.
Okay well I solved it for the ActiveRecord. The ActiveRecords needs a public $var; in the Model. So to get the amount you have to add the mentioned public... to your Model so that in my case:
public $cnt; extends the ActiveRecord of Articles
now I can access it with the given Request in my Question. But this just solves the first point. I am still interested in the second way for more complex Queries.
I dont have much idea about active record but I think the below is something what you are looking for
select * from user_details where user_id in
(select A.user from
(select user_id as user, COUNT(*) AS num_articles
from articles group by user_id order by num_articles desc LIMIT 10)A
);
for second point you should include required column from joined table to select statement:
$query = (new Query())
->select('articles.user_id, COUNT(*) AS num_articles, user_details.username as username')
->from('articles')
->join('LEFT JOIN', 'user_details', 'user_details.user_id = articles.user_id')
->groupBy('articles.user_id')
->orderBy('num_articles DESC')
->limit(10)
->offset($offset)
->all();
I try to get this mysql query to work with Yii model but i can't.
SELECT COUNT( qhc.countries_id) AS counter, q.question, co.name
FROM questions AS q , countries as co, questions_has_countries AS qhc
WHERE qhc.questions_id = q.id
AND co.id = qhc.countries_id
GROUP BY question
HAVING counter = 2
So far i have this, but somehow thou it seems ok, it doesnt work :
$criteria = new CDbCriteria();
$criteria->select = 'question, COUNT(countries_id) as counter';
$criteria->with = array('countries', 'categories');
$criteria->addInCondition('countries.id' , $_POST['Questions']['countries']);
$criteria->group = 'question';
$criteria->having = ('counter = 1');
$model = Questions::model()->findAll($criteria)
Pls help, I'am pretty new to Yii framework.
Thanks.
Sql from the log :
SELECT `t`.`question` AS `t0_c1`,
COUNT(countries_id) as counter, `t`.`id` AS `t0_c0`, `countries`.`id` AS
`t1_c0`, `countries`.`name` AS `t1_c1`, `categories`.`id` AS `t2_c0`,
`categories`.`name` AS `t2_c1` FROM `questions` `t` LEFT OUTER JOIN
`questions_has_countries` `countries_countries` ON
(`t`.`id`=`countries_countries`.`questions_id`) LEFT OUTER JOIN `countries`
`countries` ON (`countries`.`id`=`countries_countries`.`countries_id`)
LEFT OUTER JOIN `questions_has_categories` `categories_categories` ON
(`t`.`id`=`categories_categories`.`questions_id`) LEFT OUTER JOIN
`categories` `categories` ON
(`categories`.`id`=`categories_categories`.`categories_id`) WHERE
(countries.id=:ycp0) GROUP BY question HAVING (counter = 2). Bound with
:ycp0='1'
You have done most of work. Now you need to call the $criteria into model. Just like this
$rows = MODEL ::model()->findAll($criteria);
Where MODEL is model class of table which you want to apply criteria on.
To learn more about this you can follow this CActiveRecord Class.
Try to set together in CDbCriteria
...
$criteria->together = true;
$model = Question::model()->findAll($criteria);
when you use "as counter", your model must have a property named "counter" or it will not load it into your model.
if you don't have a property named "counter", try using another one of your models property that you are not selecting right now : "as someColumn"
and use condition or addCondition or .. instead of having
cheers