Yii2 ActiveDataProvider with leftJoin in query doesnt return the pagination pagesize items - yii2

Am trying to return users with paycheck and loggedout at a certain time and return 10 items per each page but it fails to work
I have tried with the following code
$filters = $arr["filters"];
$timev = [strtotime($filters["duration_from"]), strtotime($filters["duration_to"])]
$query = Users::find()
->leftJoin('tbl_truck_history', 'tbl_paychecks.user_id = users.id')
->leftJoin('tbl_security_login', 'tbl_security_login.user_id = users.id')
->where(["users.department"=>3])
->andWhere(['between', 'tbl_security_login.time_out', min($timev), max($timev)]);
$data = new ActiveDataProvider([
'query' => $query,
'pagination' =>[
'pageSize' => 10, //here per_page is
'page' => $arr['page']
],
]);
return ["data" => $data->getModels(),"totalRecords" => (int)$query->count()]
When i check on $data->getModels() it returns only 3 items in the array. What am i missing out

The problem:
Your ids are not unique (which will be used as a key). Primary key will appear multiple times in your result, and it will treated as a same row.
Overcome this problem:
You will need to "group by" your records. Maybe use users.id for this.
$query = Users::find()
...
->groupBy(['users.id']);
If group by is not an option for you, you will need to specify the key for the lines
class ActiveDataProvider extends BaseDataProvider
...
/**
* #var string|callable|null the column that is used as the key of the data models.
* This can be either a column name, or a callable that returns the key value of a given data model.
*
* If this is not set, the following rules will be used to determine the keys of the data models:
*
* - If [[query]] is an [[\yii\db\ActiveQuery]] instance, the primary keys of [[\yii\db\ActiveQuery::modelClass]] will be used.
* - Otherwise, the keys of the [[models]] array will be used.
*
* #see getKeys()
*/
public $key;
in your case:
$data = new ActiveDataProvider([
'query' => $query,
'pagination' =>[
'pageSize' => 10, //here per_page is
'page' => $arr['page']
],
'key' => static function($model) {return [new unique key] }
]);

Related

Yii2 sort by with calculations in an ActiveDataProviderRecord

I would like to add a groupby with cacluations in yii2 ActiveDataProvider.
SO currently i have the following fields in my database
tbl_products
id, products, pts_log,pts_chum
SO i would like to group my data with the formula
pts_log * pts_chum / 100
So i have the following in my controller
ActiveDataProvider([
'query' => $query,
'sort' => ['defaultOrder' => ['(pts_log * pts_chum / 100)' => SORT_DESC]],
'pagination' => [
'pageSize' => $this->paginator['perPage']/2,
'page' => $this->paginator['page']
],
]);
But am now getting an error
undefined (pts_log * pts_chum / 100)
This works with one item key like pts_log. What do i add to make sorting work with a formulae.
You have to add an alias to the field the query is to be grouped by and use the alias in sort.
$query = (new \yii\db\Query())
->select ('count(*) as c, ((pts_log * pts_chum / 100) as calc_field')
->from('tbl_products')
->groupBy('calc_field');
$dataProvider = new \yii\data\ActiveDataProvider([
'query' => $query,
'sort' => ['defaultOrder' => ['calc_field' => SORT_DESC]],
]);
Use simple the desired aggregates instead of count(*).
In this case You getting an error undefined (pts_log * pts_chum / 100) because it is not column name and not valid expression.
You need create new variable in ActiveRecord model and and fill it with data from the calculated expression.
Then this variable name use in sort 'sort' => ['defaultOrder' => ['expr_item' => SORT_DESC]]
Look to http://webtips.krajee.com/filter-sort-summary-data-gridview-yii-2-0/ A similar option is considered here.
Or prepare $query like:
$tn = TblProdukts::tableName();
$query = TblProdukts::find()->select([
TblProdukts::tableName().'.*',
"(".$tn.".id*".$tn.".rank/100) AS expr"
]);
In Active Record class create variable $expr;
If needed add it to safe rules:
public function rules()
{
return [
[[
'expr'// expression
],
'safe'],
];
}

how to use relation table when using sqldataprovider

please help I dont know how to get relation table when using sqldataprovider. Anyone understand how to use relation model?
$model = new Finalresult();
$searchModel = new FinalresultSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$dataProvider = new SqlDataProvider([
'sql' => 'SELECT finalresult.bib,
finalresult.series_id,
finalresult.category_id,
GROUP_CONCAT(DISTINCT finalresult.point ORDER BY series.serie_seri_no DESC) AS seriPoint
FROM finalresult, series GROUP BY finalresult.bib',
'key' => 'bib',
]);
I'm trying to get relation table:
'attribute'=>'category_id',
'width'=>'300px',
'value'=>function ($model, $key, $index, $widget) {
return $model->category->category_name;
},
then getting trying to non-object
You can't use relations with SqlDataProvider, because each single result will be presented as array, for example:
[
'id' => 1,
'name' => 'Some name',
'category_id' => 1,
],
For example, you can access category_id as `$model['category_id'].
SqlDataProvider is for very very complex queries, your query can easily be written as ActiveQuery and you can use ActiveDataProvider and get all advantages of that (relations, etc.).
You can find category by id, but it will be lazily loaded that means amount of queries is multiplied by number of rows.
With ActiveDataProvider and relations you can use eager loading and reduce amount of queries. Read more in official docs.
Grid Columns example in documentation
try to change "value" to
'value'=> function($data) {
return $data['category']['category_name'];
}

Yii2 Active record query multiple where clause from array

Yii2's index page's default data provider is like following:
$dataProvider = new ActiveDataProvider([
'query' => ModelName::find(),
]);
Now, I've got an array like $arr = [1, 2, 4, 6];
I want to add a where clause like:
WHERE parentId=1 OR parentId=2 OR parentId=4 OR parentId=6
How can I do that?
Can be done like this:
$query = ModelName::find()->where(['parentId' => $arr]);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
When you pass an array to where, Yii automatically transforms it into IN condition.
So generated SQL conditional part will be WHERE parentId IN (1, 2, 4, 6);.
It's equivalent to your mentioned condition with OR.

Yii 2 Gridview - Search Query generates ambiguous fields

I am generating related records search query for Gridview use
I get this error :
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'dbowner' in where clause is ambiguous
The SQL being executed was: SELECT COUNT(*) FROM tbl_iolcalculation LEFT JOIN tbl_iolcalculation patient ON tbl_iolcalculation.patient_id = patient.id WHERE (dbowner=1) AND (dbowner=1)
I have two related models 1) iolcalculation and patient - each iolcalculation has one patient (iolcalculation.patient_id -> patient.id)
The relevant code in my model IolCalculationSearch is :
public function search($params)
{
$query = IolCalculation::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$dataProvider->sort->attributes['patient.lastname'] = [
'asc' => ['patient.lastname' => SORT_ASC],
'desc' => ['patient.lastname' => SORT_DESC],
];
$query->joinWith(['patient'=> function($query) { $query->from(['patient'=>'tbl_iolcalculation']); } ]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$query->andFilterWhere([
'id' => $this->id,
'patient_id' => $this->patient_id,
'preop_id' => $this->preop_id,
'calculation_date' => $this->calculation_date,
'iol_calculated' => $this->iol_calculated,
The reason this error is generated is that each model has an override to the default Where clause as follows, the reason being that multiple users data needs to be segregated from other users, by the field dbowner:
public static function defaultWhere($query) {
parent::init();
$session = new Session();
$session->open();
$query->andWhere(['t.dbowner' => $session['dbowner']]);
}
this is defined in a base model extending ActiveRecord, and then all working models extend this base model
How Can I resolve this ambiguous reference in the MySQL code?
Thanks in advance
$query->andFilterWhere([
// previous filters
self::tableName() . '.structure_id' => $this->structure_id,
// next filters
]);
I think, that you are searching for table aliases.
(https://github.com/yiisoft/yii2/issues/2377)
Like this, of course you have to change the rest of your code:
$query->joinWith(['patient'=> function($query) { $query->from(['patient p2'=>'tbl_iolcalculation']); } ]);
The only way I can get this to work is to override the default scope find I had set up for most models, so that it includes the actual table name as follows - in my model definition:
public static function find() {
$session = new Session();
$session->open();
return parent::find()->where(['tbl_iolcalculation.dbowner'=> $session['dbowner']]);
}
There may be a more elegant way using aliases, so any advice would be appreciated - would be nice to add aliases to where clauses, and I saw that they are working on this....

CakePHP - trying to order posts by relevance to tags shared with main post and date created

I'm trying to display eight of the most relevant posts, in order of relevance, based on tags shared with the current post being viewed and the date created...
Models:
Tag habtm Post
Post habtm Tag
DB:
posts(id, slug, ...)
tags(id, tag, ...)
posts_tags(post_id, tag_id)
Within controller action:
$post = $this->Post->find('first', array('conditions' => array('slug' => $slug)));
$this->set('post', $post);
$tags = $post['Tag'];
$relOrd = '';
foreach($tags as $tag){
$tagId = $tag['id'];
$relOrd .= " + (CASE WHEN PostsTag.tag_id = ".$tagId." THEN 1 ELSE 0 END)";
}
$relOrd = '(' . substr($relOrd, 3) . ') AS Relevance';
$morePosts = $this->Post->find('all', array(
'joins' => array(
array(
'table' => 'posts_tags',
'alias' => 'PostsTag',
'type' => 'LEFT',
'conditions' => array(
'PostsTag.post_id = Post.id',
)
)
),
'group' => 'Post.id',
'fields' => array($relOrd, 'Post.*'),
'order' => array('Relevance' => 'DESC', 'Post.created' => 'DESC'),
'limit' => 8,
));
$this->log($morePosts);
$this->set('morePosts', $morePosts);
It's almost working, although the relevance value is being treated as if each post has only one tag (being only 0 or 1). So it seems that the relevance value for each post is taking either 0 or 1 depending on the posts LAST tag rather than being accumulative based on ALL tags.
First of all, I'd take all of the logic out of the controller. Consider this:
$post = $this->Post->find('first', array('conditions' => array('slug' => $slug)));
$this->set('post', $post);
$this->set('morePosts', $this->Post->findRelevant($post));
Now your controller is easy to read and does it's job. You essentially start by describing what data you want by naming an imaginary model function, and then you write the model code to fulfill that request.
So here's a stab at the model code:
var $actsAs = array('Containable');
function findRelevant($post, $limit = 8) {
// create an array of ids of the tags from this post
$tags = array();
foreach($post['Tag'] as $num => $tag) {
$tags[$tag['id']] = $tag['id'];
}
// find other posts that have any of those tags
$relevant = $this->find('all', array(
'conditions' => array('Post.id <>' => $post['Post']['id']),
'order' => 'Post.created desc',
'contain' => array('Tag' => array('conditions' => array(
'Tag.id' => $tags
))),
));
// count the number of tags of each post and call it relevance
// (this number is essentially the number of tags in common
// with the original post because we used contain to get only
// the tags from the original post)
foreach($relevant as &$p) {
$p['Post']['relevance'] = count($p['Tag']);
}
// sort by relevance
$relevant = Set::sort($relevant, '{n}.Post.relevance', 'desc');
// limit the number of posts returned (defaults to 8)
return array_splice($relevant, 0, $limit);
}
Obviously it would be great to use the database logic to fetch the records (as you are attempting to do) so that it's as fast as possible and so that you minimize the amount of data that you retrieve, but I can't see how to do that for what you're trying to achieve.
This method should work fine and is not database specific. :)