Related
I'm starting in the world Yii framework in version 1.1 and it's impossible for me to visualize the records brought from a pure query. It's been days since I can't solve it, it's probably something simple but with my poor knowledge I haven't been able to solve it yet. I show you my code to see if you can help me.
The relationships of the TblRecibo model, which is where I want to make the query are:
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(
'tblEstadoreciboHasTblRecibos' => array(self::HAS_MANY, 'TblEstadoreciboHasTblRecibo', 'idRecibo'),
'tblItemrecibowebs' => array(self::HAS_MANY, 'TblItemreciboweb', 'idRecibo'),
'idCalendario0' => array(self::BELONGS_TO, 'TblCalendario', 'idCalendario'),
'idDomicilioApertura0' => array(self::BELONGS_TO, 'TblDomicilio', 'idDomicilioApertura'),
'idDomicilio0' => array(self::BELONGS_TO, 'TblDomicilio', 'idDomicilio'),
'idPaquete0' => array(self::BELONGS_TO, 'TblPaquete', 'idPaquete'),
);
}
My model:
public function search()
{
$criteria = new CDbCriteria;
$criteria->alias = 'r';
$criteria->select = 'r.idRecibo,d.matricula,d.federado,e.nombre,d.calle,r.cantidad,cal.periodo,est.ult_modif,os.nombre ';
$criteria->join ='LEFT JOIN tbl_calendario as cal on r.idcalendario=cal.idcalendario '
.'LEFT JOIN tbl_estadorecibo_has_tbl_recibo as est on r.idrecibo=est.idrecibo '
.'LEFT JOIN tbl_domicilio as d ON r.iddomicilioapertura=d.idDomicilio '
.'LEFT JOIN tbl_entidad as e ON d.idEntidad=e.idEntidad '
.'LEFT JOIN tbl_itemreciboweb as rweb on r.idrecibo=rweb.idrecibo '
.'LEFT JOIN tbl_ooss as os on rweb.idooss=os.idooss';
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
'pagination' => array('pageSize' => Yii::app()->user->getState('pageSize', Yii::app()->params['defaultPageSize']),
),
));
}
My controller:
public function actionIndex()
{
$model = new TblRecibo('search');
$model->unsetAttributes(); // clear any default values
if (isset($_GET['TblRecibo']))
$model->attributes = $_GET['TblRecibo'];
$this->render('admin', array(
'model' => $model,
));
}
My view:
<?php
$this->widget('bootstrap.widgets.TbGridView', array(
'type'=>'bordered striped',
'id' => 'tbl-recibo-grid',
'dataProvider' => $model->search(),
'htmlOptions' =>array('style' => 'font-size: 16px;font-weight: normal'),
'columns' => array(
'idrecibo',
'dom.matricula',
'dom.federado',
'ent.nombre',
'dom.calle',
'rec.cantidad',
'cal.periodo',
'est.ult_modif',
'os.nombre',
),
));
?>
When executing I have no error just that the GridView does not show the results of the queries. I know it should not be anything complicated because, debugging the values are the issue is that I do not understand how to pass them to the Vista.
With select like r.idRecibo,d.matricula,d.federado, ... result will be returned with keys like idRecibo, matricula, federado etc. Tho handle such query you need to add all fields from joined tables to your model:
TblRecibo extends CActiveRecord {
public $matricula;
public $federado;
// ...
}
And then in grid view access them by:
'columns' => array(
'idRecibo',
'matricula',
'federado',
// ...
),
But for such custom queries it may be easier to use CSqlDataProvider instead of CActiveDataProvider - you will not need to add properties to model only for this search - result will be returned as array:
public function search() {
$from = 'FROM tbl_recibo as r '
. 'LEFT JOIN tbl_calendario as cal on r.idcalendario=cal.idcalendario '
. 'LEFT JOIN tbl_estadorecibo_has_tbl_recibo as est on r.idrecibo=est.idrecibo '
. 'LEFT JOIN tbl_domicilio as d ON r.iddomicilioapertura=d.idDomicilio '
. 'LEFT JOIN tbl_entidad as e ON d.idEntidad=e.idEntidad '
. 'LEFT JOIN tbl_itemreciboweb as rweb on r.idrecibo=rweb.idrecibo '
. 'LEFT JOIN tbl_ooss as os on rweb.idooss=os.idooss';
$count = Yii::app()->db->createCommand('SELECT COUNT(*) ' . $from)->queryScalar();
$sql = 'SELECT r.idRecibo,d.matricula,d.federado,e.nombre,d.calle,r.cantidad,cal.periodo,est.ult_modif,os.nombre ' . $from;
return new CSqlDataProvider($sql, array(
'totalItemCount' => $count,
'pagination' => array(
'pageSize' => Yii::app()->user->getState('pageSize', Yii::app()->params['defaultPageSize']),
),
));
}
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 have table named books and want to display "related books" in books view.
books table
`book_id`
`isbn`
`book_name`
`author_name`
`Date`
`number`
`language`
`Translator`
`abstract`
`picture`
`created_at`
controller code
public function actionView($id)
{
$model=$this->findModel($id);
$dataProvider=$this->related($model);
return $this->render('view', [
'model' => $model,
'dataProvider' => $dataProvider,
]);
}
protected function related($model)
{
$query = Books::find();
$query->select('book_id,picture, book_name,author_name,(( author_name LIKE \'%'.$model->author_name.'%\')+( book_name LIKE \'%'.$model->book_name.'%\')) as total')
->orFilterwhere(['or like','author_name' , explode(' ',$model->author_name)])
->orFilterwhere(['or like','book_name' , explode(' ',$model->book_name)])
->orderBy('total')
->limit(25);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
problom :
book_name LIKE \'%'.$model->book_name.'%\' did not accept array.
something like LIKE \'%'.explode(' ',$model->book_name).'%\'
its must be an array to work correctly.
how can i use array in select condition .
or show me some other way to get best match result on top.
tnx
First If you want Total no need to write where condition in select
$query->select('book_id,picture, book_name,author_name,(book_name+author_name) as total');
$model->author_name And $model->book_name is Array So you can do as below.
protected function related($model)
{
$query = Books::find();
$query->select('book_id,picture, book_name,author_name,(book_name+author_name) as total');
$author_names= explode(' ',$model->author_name);
foreach($author_names as $author_name)
{
$query->orFilterwhere(['like','author_name' , $author_name]);
}
$book_names=explode(' ',$model->book_name);
foreach($book_names as $book_name)
{
$query->orFilterwhere(['like','book_name' , $book_name]);
}
->orderBy('total')
->limit(25);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
}
You need to use MySQL match() against() feature (or substitute if you're using different DB).
$query = Books::find();
$query->select([
'book_id,picture', 'book_name', 'author_name',
new Expression('MATCH (author_name) AGAINST (:author_name) + MATCH (book_name) AGAINST (:book_name) AS total', [
'author_name' => $this->author_name,
'book_name' => $this->book_name,
]),
]);
if (!empty($this->author_name)) {
$query->andWhere(new Expression('MATCH (author_name) AGAINST (:author_name)', [
'author_name' => $this->author_name,
]));
}
if (!empty($this->book_name)) {
$query->andWhere(new Expression('MATCH (book_name) AGAINST (:book_name)', [
'book_name' => $this->book_name,
]));
}
$query->orderBy('total DESC')
->limit(25);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
TL;DR
I have a query that works in RAW SQL but i have had little success recreating it with query builder or active record.
I am working on a web application based off of the yii2 advanced application template. I have written a database query and implemented it with findbysql() returning the correct records but am having trouble translating this into active record.
I originally wanted to allow the user to modify (filter) the results by means of a search form(user & date), however i have since realized that implementing filters on the gridview with active records would be smoother.
I have gotten simple queries to work however am unsure of how to implement one with this many joins. Many examples used sub queries but my attempts failed to return any records at all. I figured before I attempt filters i need to transcribe this query first.
videoController.php
public function actionIndex()
{
$sql = 'SELECT videos.idvideo, videos.filelocation, events.event_type, events.event_timestamp
FROM (((ispy.videos videos
INNER JOIN ispy.cameras cameras
ON (videos.cameras_idcameras = cameras.idcameras))
INNER JOIN ispy.host_machines host_machines
ON (cameras.host_machines_idhost_machines =
host_machines.idhost_machines))
INNER JOIN ispy.events events
ON (events.host_machines_idhost_machines =
host_machines.idhost_machines))
INNER JOIN ispy.staff staff
ON (events.staff_idreceptionist = staff.idreceptionist)
WHERE (staff.idreceptionist = 182)
AND (events.event_type IN (23, 24))
AND (events.event_timestamp BETWEEN videos.start_time
AND videos.end_time)';
$query = Videos::findBySql($sql);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
return $this->render('index', [
'dataProvider' => $dataProvider,
]);
}
Failed Attempt
public function actionIndex()
{
$query = Videos::find()
->innerJoin('cameras', 'videos.cameras_idcameras = cameras.idcameras')
->innerJoin('host_machines', 'cameras.host_machines_idhost_machines = host_machines.idhost_machines')
->innerJoin('events', 'events.host_machines_idhost_machines = host_machines.idhost_machines')
->innerJoin('staff', 'events.staff_idreceptionist = staff.idreceptionist')
->where('staff.idreceptionist = 182')
->andWhere(['events.event_type' => [23,24]])
->andwhere(['between', 'events.event_timestamp', 'videos.start_time', 'videos.end_time']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
return $this->render('index', [
'dataProvider' => $dataProvider,
]);
}
Portion of View
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'idvideo',
'event_type',
'event_timestamp',
'filelocation',
//['class' => 'yii\grid\ActionColumn'],
],
]); ?>
Please let me know if i need to be more specific or include any additional information.
Thanks ahead
i will assume, based on the question you asked here you liked in comments that you provided the entire query
(no other fields, that you took out just to show sample code)
therefore, if you only need only the fields specified in SELECT statement, you can optimize your query quite a bit:
first off, you're joining with host_machines only to link cameras and events, but have the same key host_machines_idhost_machines on both, so that's not needed, you can directly:
INNER JOIN events events
ON (events.host_machines_idhost_machines =
cameras.host_machines_idhost_machines))
secondly, the join with ispy.staff, the only used field is idreceptionist in WHERE clause, that field exists in events as well so we can drop it completly
the final query here:
SELECT videos.idvideo, videos.filelocation, events.event_type, events.event_timestamp
FROM videos videos
INNER JOIN cameras cameras
ON videos.cameras_idcameras = cameras.idcameras
INNER JOIN events events
ON events.host_machines_idhost_machines =
cameras.host_machines_idhost_machines
WHERE (events.staff_idreceptionist = 182)
AND (events.event_type IN (23, 24))
AND (events.event_timestamp BETWEEN videos.start_time
AND videos.end_time)
should output the same records as the one in your question, without any identitcal rows
some video duplicates will still exists due to one to many relation between cameras and events
now to the yii side of things,
you have to define some relations on the Videos model
// this is pretty straight forward, `videos`.`cameras_idcameras` links to a
// single camera (one-to-one)
public function getCamera(){
return $this->hasOne(Camera::className(), ['idcameras' => 'cameras_idcameras']);
}
// link the events table using `cameras` as a pivot table (one-to-many)
public function getEvents(){
return $this->hasMany(Event::className(), [
// host machine of event => host machine of camera (from via call)
'host_machines_idhost_machines' => 'host_machines_idhost_machines'
])->via('camera');
}
the VideoController and the search function itself
public function actionIndex() {
// this will be the query used to create the ActiveDataProvider
$query =Video::find()
->joinWith(['camera', 'events'], true, 'INNER JOIN')
->where(['event_type' => [23, 24], 'staff_idreceptionist' => 182])
->andWhere('event_timestamp BETWEEN videos.start_time AND videos.end_time');
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
return $this->render('index', [
'dataProvider' => $dataProvider,
]);
}
yii will treat each video as a single record (based on pk), that means that all video duplicates are
removed. you will have single videos, each with multiple events so you wont be able to use 'event_type'
and 'event_timestamp' in the view but you can declare some getters inside Video model to show that info:
public function getEventTypes(){
return implode(', ', ArrayHelper::getColumn($this->events, 'event_type'));
}
public function getEventTimestamps(){
return implode(', ', ArrayHelper::getColumn($this->events, 'event_timestamp'));
}
and the view use:
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'idvideo',
'eventTypes',
'eventTimestamps',
'filelocation',
//['class' => 'yii\grid\ActionColumn'],
],
]); ?>
edit:
if you want to keep the video duplicates, declare the two columns from events inside Video model
public $event_type, $event_timestamp;
keep the original GridView setup, and add a select and indexBy this to the query inside VideoController:
$q = Video::find()
// spcify fields
->addSelect(['videos.idvideo', 'videos.filelocation', 'events.event_type', 'events.event_timestamp'])
->joinWith(['camera', 'events'], true, 'INNER JOIN')
->where(['event_type' => [23, 24], 'staff_idreceptionist' => 182])
->andWhere('event_timestamp BETWEEN videos.start_time AND videos.end_time')
// force yii to treat each row as distinct
->indexBy(function () {
static $count;
return ($count++);
});
update
a direct staff relation to Video is currently somewhat problematic since that is more than one table away from it.
there's an issue about it here
however, you add the staff table by linking it to the Event model,
public function getStaff() {
return $this->hasOne(Staff::className(), ['idreceptionist' => 'staff_idreceptionist']);
}
that will allow you to query like this:
->joinWith(['camera', 'events', 'events.staff'], true, 'INNER JOIN')
Filtering will require some small updates on the controller, view and a SarchModel
here's a minimal implementation:
class VideoSearch extends Video
{
public $eventType;
public $eventTimestamp;
public $username;
public function rules() {
return array_merge(parent::rules(), [
[['eventType', 'eventTimestamp', 'username'], 'safe']
]);
}
public function search($params) {
// add/adjust only conditions that ALWAYS apply here:
$q = parent::find()
->joinWith(['camera', 'events', 'events.staff'], true, 'INNER JOIN')
->where([
'event_type' => [23, 24],
// 'staff_idreceptionist' => 182
// im guessing this would be the username we want to filter by
])
->andWhere('event_timestamp BETWEEN videos.start_time AND videos.end_time');
$dataProvider = new ActiveDataProvider(['query' => $q]);
if (!$this->validate())
return $dataProvider;
$this->load($params);
$q->andFilterWhere([
'idvideo' => $this->idvideo,
'events.event_type' => $this->eventType,
'events.event_timestamp' => $this->eventTimestamp,
'staff.username' => $this->username,
]);
return $dataProvider;
}
}
controller:
public function actionIndex() {
$searchModel = new VideoSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('test', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
and the view
use yii\grid\GridView;
use yii\helpers\ArrayHelper;
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'idvideo',
'filelocation',
[
'attribute' => 'eventType', // from VideoSearch::$eventType (this is the one you filter by)
'value' => 'eventTypes' // from Video::getEventTypes() that i suggested yesterday
// in hindsight, this could have been named better, like Video::formatEventTypes or smth
],
[
'attribute' => 'eventTimestamp',
'value' => 'eventTimestamps'
],
[
'attribute' => 'username',
'value' => function($video){
return implode(', ', ArrayHelper::map($video->events, 'idevent', 'staff.username'));
}
],
//['class' => 'yii\grid\ActionColumn'],
],
]);
My recommendation would be to have 2 queries. The first one to get the ids of the videos that fit your search, the second query theone that uses those ids and feeds your $dataProvider.
use yii\helpers\ArrayHelper;
...
public function actionIndex()
{
// This is basically the same query you had before
$searchResults = Videos::find()
// change 'id' for the name of your primary key
->select('id')
// we don't really need ActiveRecord instances, better use array
->asArray()
->innerJoin('cameras', 'videos.cameras_idcameras = cameras.idcameras')
->innerJoin('host_machines', 'cameras.host_machines_idhost_machines = host_machines.idhost_machines')
->innerJoin('events', 'events.host_machines_idhost_machines = host_machines.idhost_machines')
->innerJoin('staff', 'events.staff_idreceptionist = staff.idreceptionist')
->where('staff.idreceptionist = 182')
->andWhere(['events.event_type' => [23,24]])
->andwhere(['between', 'events.event_timestamp', 'videos.start_time', 'videos.end_time'])
// query the results
->all();
// this will be the query used to create the ActiveDataProvider
$query = Videos::find()
// and we use the results of the previous query to filter this one
->where(['id' => ArrayHelper::getColumn($searchResults, 'id')]);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
return $this->render('index', [
'dataProvider' => $dataProvider,
]);
}
Please look the picture below.
As you can see total is 215340 in the summary column. What I want is that in the next page it should start summing those numbers on this number.
Here is a code.
GridView::widget([
'dataProvider' => $dataProvider,
// 'filterModel' => $searchModel,
'columns' => [
[
'class' => 'kartik\grid\SerialColumn'],
// 'id',
[
'attribute' => 'customer_id',
'value' => function ($data) {
return $data->customerDetail->account_no;
}
],
[
'attribute' => 'naration',
'pageSummary' => 'Total',
],
[
'attribute' => 'transaction_type',
'label' => 'Deposits',
'pageSummary' => true,
'pageSummaryOptions' => ['id' => 'total_sum'],
'value' => function ($data) {
if($data->transaction_type==10)
return $data->amount;
else
return 0;
}
],
[
'attribute' => 'transaction_type',
'label' => 'Withdrawals',
'pageSummary' => true,
'value' => function ($data) {
if($data->transaction_type==11)
return $data->amount;
else
return 0;
}
],
[
'class' => 'backend\components\TotalColumn',
'label' => 'Balance',
'attribute' =>'amount',
],
],
// This line displays sum of the columns.
'showPageSummary' => true,
]);
?>
I was thinking about sending this page's sum to the next page using $_GET but the problem is when user going to previous pages i.e. from page 3 to 2. Do you any widget or something for this problem?
My idea is using mysql to select sum of all records in the current column and add this value with the total value of the previous page, I need to know offset and limit of the current page for this query, I can get those values from data provider, to achieve this I have to override the function \yii\grid\Column::renderFooterCellContent().
Extend \yii\grid\DataColumn class
namespace frontend\components;
use yii\helpers\ArrayHelper;
class DataColumn extends \yii\grid\DataColumn
{
protected function renderFooterCellContent()
{
if ($this->footer instanceof \Closure) {
return call_user_func($this->footer, ArrayHelper::getValue($this, 'grid.dataProvider.pagination'));
} else {
return parent::renderFooterCellContent();
}
}
}
Your view file
use frontend\models\Client;
use yii\db\Expression;
use yii\db\Query;
use yii\grid\GridView;
use yii\data\Pagination;
use frontend\components\DataColumn;
echo GridView::widget([
'dataProvider' => $dataProvider,
'dataColumnClass' => DataColumn::className(),
'showFooter' => true,
'columns' => [
//['class' => 'yii\grid\SerialColumn'],
'id',
[
'attribute' => 'num_users',
'footer' => function ($pagination) {
if ($pagination instanceof Pagination) {
$offset = $pagination->offset;
$limit = $pagination->limit;
$cquery = (new Query())
->select(['sub_users' => new Expression('SUM(num_users)')])
->from([
'cpage' => (new Query())
->select('num_users')
->offset($offset)
->limit($limit)
->from(Client::tableName())
]);
//negative offset is illegal
if ($offset > 0) {
$pquery = (new Query())
->select(['sub_users' => new Expression('SUM(num_users)')])
->from([
'ppage' => (new Query())
->select('num_users')
->offset(0)
->limit($offset)
->from(Client::tableName())
]);
$cquery->union($pquery, true);
}
return (new Query())->from(['subt' => $cquery])->sum('sub_users');
}
}
],
]
]);
Raw SQL query
SELECT SUM(sub_sum_users) FROM (
SELECT SUM(num_users) as sub_sum_users FROM (
SELECT num_users FROM `clients` LIMIT 20 OFFSET 120
) cpage
UNION ALL
SELECT SUM(num_users) as sub_sum_users FROM (
SELECT num_users FROM `clients` LIMIT 20 OFFSET 100
) ppage
) sublt
Note:
You could consider to use ActiveRecord, my girdview has a lot of join queries so it's very heavy for me
$offset = $pagination->offset;
$limit = $pagination->limit;
$cquery = Client::find()
->select(['sub_users' => new Expression('SUM(num_users)')])
->from([
'cpage' => Client::find()->select('num_users')->offset($offset)->limit($limit)
]);
//negative offset is illegal
if ($offset > 0) {
$pquery = $cquery = Client::find()
->select(['sub_users' => new Expression('SUM(num_users)')])
->from([
'ppage' => Client::find()->select('num_users')->offset(0)->limit($offset)
]);
$cquery->union($pquery, true);
}
return (new Query())->from(['subt' => $cquery])->sum('sub_users');