Yii CActiveDataProvider generate SQL query - php

CActiveDataProvider generating auto query to count total item count:
SELECT COUNT(DISTINCT `t`.`id`) FROM `transaction` `t` LEFT OUTER JOIN `partner` `partner` ON (`t`.`partner_id`=`partner`.`id`)
This query is slow, because contain join, how I can set manualy total count, and disable this auto query ?

You may manualy set total item count for CActiveDataProvider to prevent auto calculation.
class Model extends CActiveRecord {
public function search(){
$criteria = new CDbCriteria;
// your criteria here
$data_provider = new CActiveDataProvider($this, array('criteria'=>$criteria));
// replace $this->count( $criteria ) with your own condition or another criteria
$data_provider->setTotalItemCount( $this->count( $criteria ) );
return $data_provider;
}
}

Try set use CActiveDataProvider->countCriteria.
http://www.yiiframework.com/doc/api/1.1/CActiveDataProvider#countCriteria-detail

Related

Eloquent - global condition on joining table

I have multiple (related) eloquent models, and most (not all) of those has field tenant_id.
Since tenant condition must always be present on every query and every join, I don't want to leave this to programmer each time, but want to add a global join conditions for specific models.
For global where conditions I already have it handled in own abstract model:
public static function boot()
{
if (in_array(HasTenant::class, class_uses(static::class))) { // check if has trait
static::addGlobalScope('tenant', function (Builder $builder) {
$tenantId = 1; // this is dynamic
$builder->where($builder->getModel()->getTable() . '.tenant_id', $tenantId);
});
}
}
Then, for example, a have score table, joined with customer - both tables have tenant_id field.
Query code:
return (new ScoreDatabaseModel())
->with('driver')
Code for joining driver table:
public function driver()
{
return $this->belongsTo(DriverDatabaseModel::class, 'driver_id');
}
Which produces SQL:
select *
from `score`
left join `driver` on `driver`.`id` = `score`.`driver_id`
where `score`.`tenant_id` = 1
What I would like is to add condition to every join whose model has tenant_id field (denoted by HasTenant trait), to add tenant_id condition and produce SQL like:
select *
from `score`
left join `driver` on `driver`.id = `score`.`driver_id` AND `driver`.`tenant_id` = 1
where `score`.`tenant_id` = 1

Yii2 hasMany custom condition

I have sql condidtion SELECT * FROM (SELECT * FROM Prices WHERE aliasId = :aliasId order by id desc) p1 group by p1.currency and I am trying to use it in hasMany statement.
$q = $this->hasMany(Prices::className(), ['aliasId' => 'id']);
$db = \Yii::$app->db;
$query = $db
->createCommand('SELECT * FROM (SELECT * FROM Prices WHERE aliasId = :aliasId order by id desc) p1 group by p1.currency')
->bindValue(':aliasId', $this->id);
$query->prepare(true);
$q->sql = $query->getRawSql();
return $q;
But $this->id is empty when hasMany calling. Is there any way to bind custom query and link array there?
UPDATE.
I know that the reason of $this->id is empty, because I'm using Prices::find()>with('prices') in my Controller, so Yii creates query for all prices list. hasMany just adds addWhere('in', $key, $value) in empty query from $link parameter, I'm trying to override his query, but I can't.
$this->id is empty for new PriceAlias instances, and it's filled only after the model is saved in db - you are getting an empty value most likely because getPrices() is called before the model is saved in db.
You can test if $this->id != null or $this->isNewRecord == false before building the custom command, otherwise return null, an empty array or as required.
UPDATE 1: not sure I fully understand your update,
Prices::find()>with('prices') does create a WHERE ... IN (...) query, but
hasMany does not add an addWhere rule, it creates a relation for the ActiveRecord class. In your case:
$this->hasMany(Prices::className(), ['aliasId' => 'id'])
// generates: SELECT * FROM `prices` WHERE `aliasId` = :id
And the query is executed only when you specifically call getPrices() for an object.
So your problem is? after $q->sql = $query->getRawSql(); statement, $q->sql is not SELECT * FROM (SELECT * FROM Prices WHERE aliasId = :aliasId order by id desc) p1 group by p1.currency ?
UPDATE 2: I understand now. I can't think of any way of using Prices::find()->with() on relations with custom sql, at least not as the one you would like to use.
I can only suggest to find an alternative to find()->with() in your controller if you need to keep the custom query.
From official doc:
$subQuery = (new Query())->select('id')->from('user')->where('status=1');
// SELECT * FROM (SELECT `id` FROM `user` WHERE status=1) u
$query->from(['u' => $subQuery]);
In your case it should be something like this:
$subQuery = (new Query())->select('*')->from('Prices')->where('aliasId = :aliasId', ['aliasId'=>$aliasId])->orderBy('id');
$query->from(['p1' => $subQuery])->groupBy('p1.currency');

Yii CListView Search with Subqueries

I am using a CListView to display my data on a page. I have a textbox for searching keywords in the query.
In my query where I build my CActiveDataProvider I have some subqueries. For Example:
$criteria = new CDbCriteria;
$criteria->select = array('
Lessons.id,
Lessons.name,
(SELECT
COALESCE(CONCAT(u.first_name, " ", u.last_name), last_name, first_name)
FROM
users AS u
WHERE
u.user_token = Lessons.instructor_id
) as instructor_name
');
My model for the above query does have a class variable called $instructor_name.
When I enter data into the textbox I then run this piece of code to join another table for searching.
if ( !empty($query) ) {
$criteria->with = array('packages');
$criteria->compare( 'packages.contents', $query, true);
$criteria->together = true;
}
The results when running a search query do not return the instructor_name data from the subquery.
Any ideas on what is happening here to prevent my subquery data from loading? Thank you in advance.
To do this in a more CActiveRecord way (and get the results your looking for), try the following:
1 - Add instructor relation to Lesson model that references the User table (so now you will be able to do $lesson->instructor)
'instructor'=>array(self::BELONGS_TO, 'User', 'instructor_id'),
2 - Add a column/expression into the select clause in your Lesson::search() method that represents the concatenated instructor_name - something like:
$criteria->select = array('
Lessons.id,
Lessons.name,
COALESCE(CONCAT(instructor.first_name, " ", instructor.last_name), last_name, first_name) AS instructor_name
...etc
3 - Add instructor to the "with" part of the search criteria so that the instructor info is joined into the query
$criteria->with = array('packages', 'instructor');

How to use CDbCriteria in Yii to apply SQL filters on Model

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

Doing Join query using CDBCriteria

I am trying to do a Join query using CDBCriteria in Yii framework. The issue is the join query works successfully but it does not display the columns from other tables.
I am doing in the following way
$criteria = new CDbCriteria;
$criteria->order = 't.id desc';
$criteria->select = '*';
$criteria->join = ' INNER JOIN table2 INNER JOIN table3 INNER JOIN table4';
When i run this, I can see only the mail table1 columns displayed. Other columns are not shown.
In my model class, I have the relation has
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(
'user' => array(self::BELONGS_TO, 'DmPhoneUser', 'user_id'),
'command' => array(self::BELONGS_TO, 'DmOtaCommand', 'command_id'),
'partner' => array(self::BELONGS_TO, 'DmPartner', 'partner_id'),
);
}
********************************************************
public function actionGetHistory($start, $per_page)
{
$partner_id = '10';
$criteria = new CDbCriteria;
$criteria->order = 't.history_id desc';
$criteria->select = 't.*, table2.*';
$criteria->join = ' INNER JOIN table2 ON t.command_id = table2.command_id';
$result = Table_1_class::model()->with('command')->findAll();
$history_data = CJSON::encode($result);
echo $history_data;
}
here command_id is common in table1 and table2.
This is how I am using the criteria code.
As I said, Yii's Active Record implementation will only use columns which are defined in the table itself or the tables you are linking to through with, not arbitrary columns you return in the resultset.
Solution 1: Define a relation to table2, add that relation to with, and get rid of join. Then you'll probably need to convert each returned object to an array - CJSON::encode will not handle a model with relations well.
Solution 2: Use Yii::app()->db->createCommand("SQL query")->queryAll(); instead of Active Record. This will produce an array of arrays, which CJSON::encode will have no problem with.

Categories