I want to write a mysql query in Yii2 search model but when performing searching criteria it gives the errors on joins.
This is my search model.
class StudentRegistrationSearch extends StudentRegistration {
/**
* #inheritdoc
*/
public function rules() {
return [
[['id', 'student_id', 'recordstatus', 'addedbyuserid'], 'integer'],
[[ 'registration_date', 'dateadded', 'let'], 'safe'],
];
}
/**
* #inheritdoc
*/
public function scenarios() {
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params) {
$query = StudentRegistration::find()->where(['recordstatus' => 1]);
$query = <<<EOD
SELECT
students.student_name,
students.`id`,
students.`reg_no`,
reg.`registration_date`,
exam.`exam_year`,
exam.`exam_title`
FROM students
LEFT JOIN student_registration reg ON (reg.`student_id` = students.`id`)
LEFT JOIN student_reg_detail detail ON(detail.`student_register_id` = reg.`id`)
LEFT JOIN def_exams exam ON(exam.`id` = detail.reg_exam_id)
WHERE students.`recordstatus` = 1 AND reg.`recordstatus` = 1 AND detail.`recordstatus` = 1
ORDER BY exam.exam_year DESC, exam.exam_title,reg.registration_date,students.student_name; EOD;
$query = Yii::$app->db->createCommand($query);
$query = $query->queryAll();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
$query->andFilterWhere([
'id' => $this->id,
'student_id' => $this->student_id,
'registration_date' => $this->registration_date,
'recordstatus' => $this->recordstatus,
'dateadded' => $this->dateadded,
'addedbyuserid' => $this->addedbyuserid,
'let' => $this->let,
]);
$query->orderBy('student_id');
return $dataProvider;
}}
I want to show the data from multiple table in the single grid and then perform filter operation but simple query is not working.
Can you please help me someone.
Thanks in advance.
Try something like following
$query = (new yii\db\Query())
->from(['s' => 'students'])
->select(['s.student_name', 's.id', 's.reg_no', 'reg.registration_date', 'exam.exam_year', 'exam.exam_title'])
->leftJoin(['reg' => 'student_registration', 'stu.student_id = s.id'])
->leftJoin(['detail' => 'student_reg_detail', 'stu.student_id = s.id'])
->leftJoin(['exam' => 'def_exams ', 'exam.id = detail.reg_exam_id'])
->where(['s.recordstatus' => 1, 'reg.recordstatus' => 1, 'detail.recordstatus' => 1])
->orderBy('exam.exam_year DESC, exam.exam_title,reg.registration_date,students.student_name')
;
OR
$query = Students::find()
->from(['s' => Students::tablename()])
->select(['s.student_name', 's.id', 's.reg_no', 'reg.registration_date', 'exam.exam_year', 'exam.exam_title'])
->leftJoin(['reg' => 'student_registration', 'stu.student_id = s.id'])
->leftJoin(['detail' => 'student_reg_detail', 'stu.student_id = s.id'])
->leftJoin(['exam' => 'def_exams ', 'exam.id = detail.reg_exam_id'])
->where(['s.recordstatus' => 1, 'reg.recordstatus' => 1, 'detail.recordstatus' => 1])
->orderBy('exam.exam_year DESC, exam.exam_title,reg.registration_date,students.student_name')
;
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
Related
I'm working on a Laravel 9 project and have created a custom validation rule called ValidModelOwnership which should check that the field a user is trying to add is owned by a model based on some values passed to it.
I've written the rule, but when debugging and outputting $model->toSql() the id is empty?
What am I missing?
My rule:
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Log;
class ValidModelOwnership implements Rule
{
/**
* The model we're checking
*/
protected $model;
/**
* Array of ownership keys
*/
protected $ownershipKeys;
/**
* Create a new rule instance.
*
* #return void
*/
public function __construct($model, $ownershipKeys)
{
$this->model = $model;
$this->ownershipKeys = $ownershipKeys;
}
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
$model = $this->model::query();
$model = $model->where($this->ownershipKeys);
Log::debug($model->toSql());
if (!$model->exists()) {
return false;
}
return true;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
return "The :attribute field doesn't belong to you and/or your company.";
}
}
And my usage in my controller:
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store($company_id, $buyer_id, Request $request)
{
$this->authorize('create', BuyerTier::class);
$validator = Validator::make($request->all(), [
'name' => [
'required',
'string',
Rule::unique(BuyerTier::class)
->where('buyer_id', $buyer_id)
->where('company_id', $company_id)
],
'country_id' => [
'required',
'numeric',
new ValidModelOwnership(Country::class, [
['company_id', 80]
])
],
'product_id' => [
'required',
'numeric',
new ValidModelOwnership(Product::class, [
['company_id', 80]
])
],
'processing_class' => 'required|string',
'is_default' => [
'required',
'boolean',
new ValidDefaultModel(BuyerTier::class, $buyer_id)
],
'is_enabled' => 'required|boolean'
]);
if ($validator->fails()) {
return response()->json([
'message' => 'One or more fields has been missed or is invalid.',
'errors' => $validator->messages(),
], 400);
}
try {
$tier = new BuyerTier;
$tier->user_id = Auth::id();
$tier->company_id = $company_id;
$tier->buyer_id = $buyer_id;
$tier->country_id = $request->input('country_id');
$tier->product_id = $request->input('product_id');
$tier->name = trim($request->input('name'));
$tier->description = $request->input('description') ?? null;
$tier->processing_class = $request->input('processing_class');
$tier->is_default = $request->boolean('is_default');
$tier->is_enabled = $request->boolean('is_enabled');
$tier->save();
return response()->json([
'message' => 'Buyer tier has been created successfully',
'tier' => $tier
], 201);
} catch (\Exception $e) {
return response()->json([
'message' => $e->getMessage()
], 400);
}
}
I've hard-coded my id's to illustrate that even when set statically, it's not passed through:
[2023-01-19 09:40:59] local.DEBUG: select * from products where (company_id = ?) and products.deleted_at is null
Laravel (and most other frameworks) extract out variables when building SQL queries to prevent SQL injection.
So the following eloquent query:
User::where('name', 'Larry');
will become:
SELECT * FROM `users` WHERE `name` = ?
and it will also pass an array of bindings: ['Larry']. When SQL processes the query it replaces replaces the ? with the values in the bindings.
So if you want to see the full query you need to log the SQL and the bindings:
Log::debug($model->toSql());
Log::debug($model->getBindings());
I have two gridview tables currently, one shows all the data and the other i want it to show only data where id = 2. Is it possible to filter only one table without affecting the other? I know I can filter from the search model but that will affect all tables and i want it to affect only one.
Can i have two dataprovider?
This is the code in my search model:
class JobPlanningSearch extends JobPlanning
{
/**
* #inheritdoc
*/
public function rules()
{
return [
[['id', 'priority', 'employer_id', 'client_id', 'status', 'activity'], 'integer'],
[['job_description', 'impediment', 'date', 'estimated_time', 'due_date'], 'safe'],
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$query = JobPlanning::find();
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
// grid filtering conditions
$query->andFilterWhere([
'id' => $this->id,
'priority' => $this->priority,
'client_id' => $this->client_id,
'employer_id' => $this->employer_id,
'estimated_time' => $this->estimated_time,
'status' => $this->status,
'activity' => $this->activity,
//'actual' => $this->actual,
//'actual' => 1,
]);
$query->andFilterWhere(['like', 'job_description', $this->job_description]);
$query->andFilterWhere(['like', 'activity', $this->activity]);
return $dataProvider;
}
You need two dataproviders, like this:
$searchModelOne = new JobPlanningSearch();
$dataProviderOne = $searchModelOne->search(Yii::$app->request->queryParams);
$dataProviderOne->pagination->pageParam = 'dp-one-page'; //set page param for first dataprovider
$searchModelTwo = new JobPlanningSearch();
searchModelTwo->id = 2; // set id = 2 in second dataprovider
$dataProviderTwo = $searchModelTwo->search(Yii::$app->request->queryParams);
$dataProviderTwo->pagination->pageParam = 'dp-two-page'; //set page param for second dataprovider
You need to use two searchModels, one for each GridView and of course two data providers.
Also you need to change the formName attribute of one of the searchModels to avoid filtering to affects both grids.
And the in the controller, when passing parameters to the search() method of the modified searchModel, pass the params in the new name you assigned to formName in this searchModel.
You will have to manipulate the DataProvider in the controller.
$searchModel = new JobPlanningSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$dataProvider->query->andWhere(['=','id',2]);
remove it from the search function in JobPlanningSearch.
$query->andFilterWhere([
/* 'id' => $this->id, */ // you must remove it from here
'priority' => $this->priority,
'client_id' => $this->client_id,
'employer_id' => $this->employer_id,
'estimated_time' => $this->estimated_time,
...
]);
In this case, the ID will always be 2.
But if you want to have a default value (the first time) and allow the user to change it, you should skip step 2. And add a condition in the controller:
...
if(count(Yii::$app->request->queryParams) == 0){
$dataProvider->query->andWhere(['=','id',2]);
}
This is just an example, you should check that the ID field has not been sent in the queryparams.
I am working on Yii2. I have a controller in which I am doing the following.
/**
* #param $id
* #return string|\yii\web\Response
* #throws NotFoundHttpException
* #throws \Exception
* #throws \yii\db\Exception
* #throws \yii\db\StaleObjectException
*/
public function actionViewcreated($id)// passed the id of my model which is created in the previous step
{
$params = "";
//print_r('hi');
$model= $this->findModel($id); // this will find my model/record based on the id
$sub_div = $model->sub_div;
$meter_type = $model->meter_type;
$query = /** #lang text */
"SELECT DISTINCT m.`id` AS meter_id, ins.`meter_msn` AS Meter_Serial_Number, ins.`meter_type` AS Meter_Type, sd.`sub_div_code` AS Sub_Division_Code,sd.`name` AS Sub_Division_Name
FROM `installations` ins
INNER JOIN `meters` m ON ins.`meter_msn` = m.`meter_msn`
INNER JOIN `meter_acceptance_header` map ON ins.`meter_type` = map.`meter_type`
INNER JOIN `survey` sur ON ins.`ref_no` = sur.`ref_no`
INNER JOIN `survey_hesco_subdivision` sd ON sur.`sub_division` = sd.`sub_div_code`
WHERE ins.`meter_type` = '$meter_type'
AND sd.`sub_div_code` = '$sub_div'
AND m.`id` NOT IN (SELECT DISTINCT md.`meter_id` FROM
`meter_acceptance_details` md WHERE md.`flag` IN (1))";
$session = Yii::$app->session;
$session->set('my_sql', $query);
$sqlCount= /** #lang text */
"SELECT COUNT(DISTINCT m.`id`)
FROM `installations` ins
INNER JOIN `meters` m ON ins.`meter_msn` = m.`meter_msn`
INNER JOIN `meter_acceptance_header` map ON ins.`meter_type` = map.`meter_type`
INNER JOIN `survey` sur ON ins.`ref_no` = sur.`ref_no`
INNER JOIN `survey_hesco_subdivision` sd ON sur.`sub_division` = sd.`sub_div_code`
WHERE ins.`meter_type` = '$meter_type'
AND sd.`sub_div_code` = '$sub_div'
AND m.`id` NOT IN (SELECT DISTINCT md.`meter_id` FROM `meter_acceptance_details` md WHERE md.`flag` IN (1))";
$params = Yii::$app->request->queryParams;
//print_r(isset($params->Meter_Serial_Number));
if(isset($params['Meter_Serial_Number']) && $params['Meter_Serial_Number']!==''){
$query.="AND WHERE (ins.`meter_msn`='".$params['Meter_Serial_Number']."')";
$sqlCount="AND WHERE (ins.`meter_msn`='".$params['Meter_Serial_Number']."')";
}
if(isset($params['Sub_Division_Name'])&&$params['Sub_Division_Name']!==''){
$query.="AND WHERE (sd.`name`='".$params['Sub_Division_Name']."')";
$sqlCount.="AND WHERE (sd.`name`='".$params['Sub_Division_Name']."')";
}
//print_r($query);
$count = Yii::$app->db->createCommand($sqlCount)->queryScalar();
$session = Yii::$app->session;
$session->set('total', $count);
if($count <= 0)
{
$this->findModel($id)->delete();
\Yii::$app->getSession()->setFlash('errors', '
<div class="alert alert-error alert-dismissable">
<button aria-hidden="true" data-dismiss="alert" class="close"
type="button">×</button>
<strong>There are no meters installed against the selected Sub Division!!!! </strong>Acceptance is not Created</div>');
return $this->redirect(['index', 'id' => $model->id]);
}
else
{
$dataProvider = new SqlDataProvider([
'sql' => $query,
'totalCount' => $count,
'pagination' => false,
]);
return $this->render('viewcreated', [
'dataProvider' => $dataProvider,
'model' =>$model,
'id' => $model->id
]);
}
}
View
$this->title = $model->id;
$this->title = 'Meter Acceptance Form';
$this->params['breadcrumbs'][] = $this->title;
.
.
.
<?php Pjax::begin(); ?>
<?= DetailView::widget([
'model' => $model,
'attributes' => [
[
'label'=>'Serial #',
'value' => function($d)
{
return $d->id;
}
],
[
'label' => 'Meter Type',
'value' => function ($d) {
if(is_object($d))
return $d->meter_type;
return ' - ';
},
],
'sub_div',
[
'label' => 'Sub Division Name',
'value' => function ($d) {
if(is_object($d))
return $d->subDiv->name;
return '-';
},
],
[
'label' => 'Prepared By',
'value' => function ($d) {
if(is_object($d))
return $d->prepared->name;
},
],
'prepared_at',
'status',
],
]) ?>
.
.
.
<?php Pjax::end(); ?>
.
.
.
Submit
When I press the Submit button I am getting the following error
PHP Notice 'yii\base\ErrorException' with message 'Undefined variable: model'
in
E:\xampp\htdocs\inventory-web\backend\views\meteracceptanceheader\viewcreated.php:17
And line 17 is $this->title = $model->id;
How can I get rid of this issue?
Update 1
By doing print_r($model); exit(); I got the following result
common\models\MeterAcceptanceHeader Object ( [_attributes:yii\db\BaseActiveRecord:private] => Array ( [id] => 1 [sub_div] => 37111 [meter_type] => L.T.TOU [prepared_by] => 12 [prepared_at] => 2018-08-03 08:39:22 [updated_at] => [status] => Prepared ) [_oldAttributes:yii\db\BaseActiveRecord:private] => Array ( [id] => 1 [sub_div] => 37111 [meter_type] => L.T.TOU [prepared_by] => 12 [prepared_at] => 2018-08-03 08:39:22 [updated_at] => [status] => Prepared ) [_related:yii\db\BaseActiveRecord:private] => Array ( ) [_errors:yii\base\Model:private] => [_validators:yii\base\Model:private] => [_scenario:yii\base\Model:private] => default [_events:yii\base\Component:private] => Array ( ) [_behaviors:yii\base\Component:private] => Array ( ) )
Update 2:
View Process Controller
/**
* #param $id
* #return \yii\web\Response
* #throws NotFoundHttpException
* #throws \Exception
* #throws \yii\db\StaleObjectException
*/
public function actionViewprocess($id)
{
$model = $this->findModel($id);
$accpt_id = $model->id;
$meter_type = $model->meter_type;
$ogp_sub_div = $model->sub_div;
if(Yii::$app->request->isAjax && Yii::$app->request->post())
{
$data = explode(',',$_POST['data']);
foreach($data as $value)
{
$m = new MeterAcceptanceDetails;
$m -> load(Yii::$app->request->post());
$m->accpt_id = $accpt_id;
$m->meter_type = $meter_type;
$m->created_at = date('Y-m-d H:i:s');
$m->meter_id = $value;
$m->meter_msn = \common\models\Meters::idTomsn($value);
$m->flag = 1;// 1 means created
$m->ogp_sub_div = $ogp_sub_div;
if($m->save())
{
$model->status = MeterAcceptanceHeader::$status_titles[1];
$model->update();
}
else{
$this->renderAjax('viewcreated');
}
}
}
else{
$this->renderAjax('viewcreated');
}
$query = /** #lang text */
"SELECT DISTINCT a.`id` AS Accpt_Id,u.`name` AS Prepared_By,b.`meter_msn` AS Meter_Serial_Number, b.`meter_type` AS Meter_Type,
sd.`name` AS Sub_Div_Name,DATE(b.`created_at`) AS 'Date' FROM
`meter_acceptance_header` a
INNER JOIN `meter_acceptance_details` b ON a.`id` = b.`accpt_id`
INNER JOIN `survey_hesco_subdivision` sd ON b.`ogp_sub_div` = sd.`sub_div_code`
INNER JOIN `user` u ON a.`prepared_by` = u.`id`
WHERE b.`accpt_id` =$accpt_id AND b.`meter_type` = '$meter_type'";
return $this->redirect(Url::toRoute(['meteracceptanceheader/viewprocess','id' => $model->id, 'model' => $this->findModel($id)]));
}
View Process View
$this->title = $model->id;
$this->title = 'Meter Acceptance';
$this->params['breadcrumbs'][] = $this->title;
<section class="content-header">
<h1>Meter Acceptance</h1>
</section>
<section class="content">
<div class="box">
<div class="box-body">
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
[
'label'=>'Serial #',
'value' => 'Accpt_Id'
],
'Date',
'Prepared_By',
'Meter_Serial_Number',
'Meter_Type',
'Sub_Div_Name',
],
]); ?>
</div>
</div>
</section>
Any help would be highly appreciated.
You are using an anchor link to submit to the viewprocess
Submit
which resolves to a GET request and then in the actionViewprocess you are checking
if(Yii::$app->request->isAjax && Yii::$app->request->post())
which resolves to false and the control goes to the else part where you call
$this->renderAjax('viewcreated')
, and here is the main problem you are not passing the $model with the view which is required and hence gives you error on the very first line
$this->title = $model->id;
which is in the exception too, you should either pass all the required vars for the view if you are trying to render it from another action or redirect to that action rather than using renderAjax()
Most likely your model doesn't include phodoc comment which includes the field descriptions. Ie. on top of your model class, you should have a comment like this:
/**
* This is the model class for table "applications".
*
* #property integer $id
* #property integer $someotherfield
*/
NOTE: you have posted controller code, but the functionality there would belong to model as per MVC principles. If this comment doesn't help, please post also your model code.
I would like to get data of a user and his roles but it returns an error of:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'auth_item.user_id' in 'on clause'
The SQL being executed was: SELECT COUNT(*) FROM `tblusers` LEFT JOIN `auth_item` ON `tblusers`.`id` = `auth_item`.`user_id`
I have tried:
Search model
$query = User::find()->joinWith('role');
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort'=> ['defaultOrder' => ['id'=>SORT_ASC]],
'pagination' => ['pageSize' => $pageSize]
]);
return $dataProvider;
Relationship on the user model
public function getRole()
{
// User has_one Role via Role.user_id -> id
return $this->hasOne(Role::className(), ['user_id' => 'id']);
}
Relationship on the role model
public function getUser()
{
return $this->hasMany(User::className(), ['id' => 'user_id']);
}
This is the full search:
public function search($params, $pageSize = 10)
{
$query = User::find()->joinWith('role');
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort'=> ['defaultOrder' => ['id'=>SORT_ASC]],
'pagination' => ['pageSize' => $pageSize]
]);
return $dataProvider;
die();
// if user is not 'theCreator' ( You ), do not show him users with this role
// if user is not 'theCreator' ( You ), do not show him users with this role
if (Yii::$app->user->can('theCreator')) {
$query->where(['!=', 'item_name', 'theCreator']);
}
$dataProvider = new ActiveDataProvider([
'query' => $query,
'sort'=> ['defaultOrder' => ['id'=>SORT_ASC]],
'pagination' => ['pageSize' => $pageSize]
]);
// make item_name (Role) sortable
$dataProvider->sort->attributes['item_name'] = [
'asc' => ['item_name' => SORT_ASC],
'desc' => ['item_name' => SORT_DESC],
];
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$query->andFilterWhere([
'id' => $this->id,
'status' => $this->status,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]);
$query->andFilterWhere(['like', 'username', $this->username])
->andFilterWhere(['like', 'email', $this->email])
->andFilterWhere(['like', 'item_name', $this->item_name]);
return $dataProvider;
}
This is the authitem model
<?php
namespace app\rbac;
use yii\db\ActiveRecord;
use Yii;
class AuthItem extends ActiveRecord
{ public static function tableName()
{
return '{{%auth_item}}';
}
public static function getRoles()
{
if (Yii::$app->user->can('theCreator'))
{
return static::find()->select('name')->where(['type' => 1])->all();
}
else
{
return static::find()->select('name')
->where(['type' => 1])
->andWhere(['!=', 'name', 'theCreator'])
->all();
}
}
}
THIS IS THE CODE UPDATE:
On this line
$query = User::find()->joinWith('role');
it actually relates to auth assignment as below:
<?php
namespace app\rbac;
use app\models\User;
use yii\db\ActiveRecord;
use Yii;
class Role extends ActiveRecord
{
public static function tableName()
{
return '{{%auth_assignment}}';
}
public function rules()
{
return [
[['item_name'], 'required'],
[['item_name'], 'string', 'max' => 64],
];
}
public function attributeLabels()
{
return [
'item_name' => Yii::t('app', 'Role'),
];
}
public function getUser()
{
// Role has_many User via User.id -> user_id
return $this->hasMany(User::className(), ['id' => 'user_id']);
}
}
Why does it return that error?
If you are using the default rbac models/module from yii2 could be you are relating the wrong model/table .. because the auth_itme model don't contain user_id column
/**
* This is the model class for table "auth_item".
*
* #property string $name
* #property integer $type
* #property string $description
* #property string $rule_name
* #property string $data
* #property integer $created_at
* #property integer $updated_at
Could be instead that you want relate the auth_assignment table which contain relation between auth_item and user
/**
* This is the model class for table "auth_assignment".
*
* #property string $item_name
* #property string $user_id
* #property integer $created_at
*
[EDITED 2]
I'm having hard time to sort by the 'topicCount' which is defined as a relational getter on a model 'Tag'.
A Topic can have a lots of Tag, and wish to sort the Tags by how many Topics containing that Tag.
In my models/Tag.php:
public function getTopicCount()
{
return TopicTag::find()->where(['tag_id' => $this->id])->count();
}
And in my views/tag/index.php:
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
'id',
'name',
[
'attribute'=>'topicCount',
'value' => 'topicCount',
],
'created_at',
['class' => 'yii\grid\ActionColumn','template' => '{view}',],
],
]); ?>
And in my controllers/TagController.php:
public function actionIndex()
{
$dataProvider = new ActiveDataProvider([
'query' => Tag::find(),
'sort'=> [
'defaultOrder' => ['id'=>SORT_DESC],
'attributes' => ['id','topicCount'],
],
'pagination' => [
'pageSize' => 100,
],
]);
return $this->render('index', [
'dataProvider' => $dataProvider,
]);
}
And in my models/TagSearch.php:
<?php
namespace common\models;
use Yii;
/**
* This is the model class for table "tags".
*
* #property integer $id
* #property string $name
* #property string $created_at
* #property string $updated_at
*/
class TagSearch extends Tag
{
public $topicCount;
/**
* #inheritdoc
*/
public function rules()
{
return [
[['topicCount'], 'safe']
];
}
public function search($params)
{
// create ActiveQuery
$query = Tag::find();
$query->joinWith(['topicCount']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$dataProvider->sort->attributes['topicCount'] = [
'asc' => ['topicCount' => SORT_ASC],
'desc' => ['topicCount' => SORT_DESC],
];
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$query->andFilterWhere([
//... other searched attributes here
])
->andFilterWhere(['=', 'topicCount', $this->topicCount]);
return $dataProvider;
}
}
And in the index view I can see the correct topicCount:
but on clicking the topicCount column I get the error:
exception 'PDOException' with message 'SQLSTATE[42703]: Undefined column: 7 ERROR: column "topicCount" does not exist
LINE 1: SELECT * FROM "tags" ORDER BY "topicCount" LIMIT 100
Thanks for any guidance..!
[EDIT]
Following Lucas' advice, I've set my dataProvider query in my $dataProvider like this:
'query' => $query->select(['tags.*','(select count(topic_tags.id) from topic_tags where topic_tags.tag_id=tags.id) topicCount'])->groupBy('tags.id'),
and I got error:
exception 'PDOException' with message 'SQLSTATE[42P01]: Undefined table: 7 ERROR: missing FROM-clause entry for table "tags"
so I reformulated like this:
'query' => $query->from('tags')->leftJoin('topic_tags','topic_tags.tag_id = tags.id')->select(['tags.*','(select count(topic_tags.id) from topic_tags where topic_tags.tag_id=tags.id) topicCount'])->groupBy('tags.id'),
and now I get the result:
apparently the topicCount column is not set, so when I try to sort by it, it returns the error:
exception 'PDOException' with message 'SQLSTATE[42703]: Undefined column: 7 ERROR: column "topicCount" does not exist
but when I try the SQL directly on the DB, it works fine:
so I suppose the problem is in the way Yii handles the alias 'topicCount'?
2nd EDIT
Still the same result without the topicCount set in the Grid view.
I show my TagSearch model, TagController and views/tag/index view file below:
TagSearch
<?php
namespace common\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use common\models\Tag;
/**
* TagSearch represents the model behind the search form about `common\models\Tag`.
*/
class TagSearch extends Tag
{
public $topicCount;
/**
* #inheritdoc
*/
public function rules()
{
return [
[['id', 'topicCount'], 'integer'],
[['name', 'created_at', 'updated_at', 'topicCount'], 'safe'],
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$query = Tag::find();
$dataProvider = new ActiveDataProvider([
'query' => $query->from("tags")->select(["tags.*","(select count(topic_tags.id) from topic_tags where topic_tags.tag_id=tags.id) topicCount"])->groupBy("tags.id"),
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
$query->where('0=1');
return $dataProvider;
}
$query->andFilterWhere([
'id' => $this->id,
'topicCount' => $this->topicCount,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
]);
$query->andFilterWhere(['like', 'name', $this->name]);
return $dataProvider;
}
}
Tag model
<?php
namespace common\models;
use Yii;
/**
* This is the model class for table "tags".
*
* #property integer $id
* #property integer $topicCount
* #property string $name
* #property string $created_at
* #property string $updated_at
*/
class Tag extends \yii\db\ActiveRecord
{
public $topicCount;
/**
* #inheritdoc
*/
public static function tableName()
{
return 'tags';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['topicCount'], 'integer'],
[['name'], 'string'],
[['created_at', 'updated_at'], 'required'],
[['created_at', 'updated_at'], 'safe']
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'name' => 'Name',
'topicCount' => 'TC',
'created_at' => 'Created At',
'updated_at' => 'Updated At',
];
}
}
TagController
public function actionIndex()
{
$searchModel = new TagSearch();
$myModels = $searchModel->search([]);
return $this->render('index', [
'dataProvider' => $myModels,
]);
}
tags/index
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
'id',
'name',
'topicCount',
'created_at',
'updated_at',
['class' => 'yii\grid\ActionColumn','template' => '{view}',],
],
]); ?>
What am I missing?
So resolved following this wiki:
Since in my case I don't use SUM('amount'), I changed to the following and works perfectly:
Tag model:
public function getTopicCount()
{
return $this->hasMany(TopicTag::className(), ["tag_id" => "id"])->count();
}
TagSearch model:
$query = Tag::find();
$subQuery = TopicTag::find()->select('tag_id, COUNT(tag_id) as topic_count')->groupBy('tag_id');
$query->leftJoin(["topicSum" => $subQuery], '"topicSum".tag_id = id');
Just encountered a problem with the generated SQL:
exception 'PDOException' with message 'SQLSTATE[42P01]: Undefined table: 7 ERROR: missing FROM-clause entry for table "topicsum"
This might be a Postgres-specific issue, had to arrange the code so that the generated SQL becomes like this:
SELECT COUNT(*) FROM "tags"
LEFT JOIN (SELECT "tag_id", COUNT(*) as topic_count FROM "topic_tags" GROUP BY "tag_id") "topicSum"
ON "topicSum".tag_id = id
note the double-quotation in "topicSum".tag_id part.
Hope this might be of help for someone using Postgres on Yii2.
You should alter your query to group and select the count instead of working with relations.
$query->groupBy('tags.id')->select(['tags.*','(select count(topic_tag.id) from topic_tag where topic_tag.tag.id=tags.id) topicCount']);
This will add topicCount as a result object in your query, which will make it behave like an ordinary column.
Also as a side note, for a method to act a relation in Yii2, it must return an ActiveQuery object. Your getTopicCount() is returning the count as an int, instead of the query, therefore Yii2 will not treat it like a relation.
Based on this Wiki and #arogachev's answer. I put select property to get tags count
public function search($params)
{
$query = SomeModels::find()
->select('subQueryName.field_count, someModels.*');
// ....
so it will give SQL like this SELECT subQuery.field_count, someModels.* ...
at view (grid),
[
'attribute'=> 'field_count',
],
Thank you #arogachev , you saved me :)
light solution is just reate view in PostgreSQL
and generate model via gii generator using as model and order & find work.
For update & delete use table model for search & index use view model.
For example
for actions update & delete use Tag model
for actions index & view use TagView model.