yii2: how to calculate the sum of a groupby query - php

i am trying to calculate the sum of a groupby query and send that value to the view in yii2. my current code gets and displays the correct grouping but the sum is not working.
here is the controller code:
public function actionIndex()
{
if( Yii::$app->request->post('search') )
{
$from = Yii::$app->request->post('from');
$to = Yii::$app->request->post('to');
switch( Yii::$app->request->post('activity') )
{
case 'bills':
$searchModel = new Bill();
$query = $searchModel::find();
$query->where(['BETWEEN', 'teis_bill_purchase_date', $from, $to]);
// The problem is in the below sum
$query->joinWith('inventory');
$query->groupBy('teis_inventory_id');
$query->sum('teis_bill_override_cbm');
$query->all();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
'show_results' => 1
]);
break;
}
}
return $this->render('index');
}
i have different types of inventory, and each inventory has multiple bills. i am trying to create a report the gets the number of bills between a specific date range ($from, $to), groups them by the inventory type, and sum up the values of each type and display those values for that type.
here is my view code:
if( isset($show_results) )
{
print(GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
[
'attribute'=>'teis_bill_id',
'value'=>'teis_bill_id',
],
'inventory.teis_inventory_type',
'teis_bill_pieces',
'teis_bill_override_cbm',
'teis_bill_sale_price',
'teis_bill_profit',
['class' => 'yii\grid\ActionColumn'],
],
]));
}
i have included the code for the view since i dont know how to display that sum once I get it!
any help would be appreciated.
thank you!

I think the problem is related to the aliasing of the sum('teis_bill_override_cbm') fields
try select your fields and for the sum use sum('teis_bill_override_cbm') as teis_bill_override_cbm
$searchModel = new Bill();
$query = $searchModel::find();
$query->select('teis_bill_id, inventory.teis_inventory_type,
sum(teis_bill_override_cbm ) as teis_bill_override_cbm,
teis_bill_pieces, teis_bill_sale_price, teis_bill_profit');
$query->where(['BETWEEN', 'teis_bill_purchase_date', $from, $to]);
// The problem is in the below sum
$query->joinWith('inventory');
$query->groupBy('teis_inventory_id');
//$query->sum('teis_bill_override_cbm'); already calculated
$query->all();

The problem here is that $query->all() returns the result as an array, but the result is not saved in any variable.
Try $var = $query->all(); instead, for the result of the executed statement to be saved.

Related

How to add grand total to the bottom of yii2 grid view

I have a grid view which is like this. The columns are calculated with the help of MySQL query and displayed accordingly.
<?php
$subquery = Adanalytics::find()->
select('id,ad_id,date_event,max(cpc) cpclick,max(cpv) cpview,max(impression) impression,max(view) view,max(clicks) clicks,visitor_ip,publisher_id')->
from('ad_analytics')->
where(['publisher_id' => Yii::$app->user->identity->id ])->
groupBy('ad_id,date_event,visitor_ip');
$query=Adanalytics::find()->
select('ad_id,date_event,sum(cpclick) total_click_cost,sum(cpview) total_view_cost,sum(impression) total_impression,sum(view) total_views,sum(clicks) total_clicks,publisher_id')->
from(['t'=>$subquery])->
groupBy('t.ad_id,t.date_event');
?>
<?= GridView::widget([
'dataProvider'=>new ActiveDataProvider([
'query' => $query,
]),
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'ad_id',
'total_impression',
'total_views',
'total_clicks',
'total_click_cost',
'total_view_cost',
'date_event',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
I want to show a grand total column at the bottom of this grid view.
For example
'grand_total_impression',
'grand_total_views', //This is the sum of all displayed views
'grand_total_clicks',//This is the sum of all displayed clicks
'grand_total_click_cost',//THis is the sum of all displayed cost
'grand_total_view_cost',//This is the sum of all displayed view cost
To do that I have code in my controller which is like this.
public function actionIndex()
{
$searchModel = new Adanalytics2Search();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$grandTotal = [];
foreach ($dataProvider->getModels() as $value) {
// var_dump($value);
// exit();
$grandTotal['total_click_cost'] += $value['total_click_cost'];
$grandTotal['total_view_cost'] += $value['total_view_cost'];
}
//var_dump($dataProvider);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
'grandTotal' => $grandTotal,
]);
}
But it is producing the error in at this way.
Undefined index: total_click_cost
Where am I going wrong? Or is there any other way to solve this problem?
I strongly advise you to use katrik gridview. It handles page summary much better and implements a lot of other usages. Alternatively add 'showPageSummary' => true at your gridview to display columns summary.
use kartik\grid\GridView;
// Create a panel layout for your GridView widget
echo GridView::widget([
'dataProvider'=> $dataProvider,
'filterModel' => $searchModel,
'columns' => $gridColumns,
'showPageSummary' => true
]);
Similar question: Yii2: Kartik Gridview sum of a column in footer
This has solved the problem anyway.
This in grid column.
[
'attribute'=>'total_impression',
'pageSummary' => true
],
And in grid view
'showPageSummary' => true,

Complex Database Queries in yii2 with Active Record

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,
]);
}

Yii2 filter Sum data in GridView

I'm trying display and filter data which I got via SQL SUM operator.
I have 2 table employee and department. Table employee contains department_id filed and salary filed. I need display all department and total SUM salary for every department.
I followed this guide, but GridView does not display any data.
Here is action:
public function actionIndex()
{
$searchModel = new DepartmentFrontendSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index',
[
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]
);
}
Model:
class DepartmentFrontendSearch extends DepartmentFrontend
public $employee_count;
public $salary;
public function rules() {
return [
[['employee_count','salary','name'], 'safe']
];
}
public function search($params) {
$query = DepartmentFrontend::find();
$subQuery = Employee::find()
->select('department_id, SUM(salary) as salary_amount')
->groupBy('department_id');
$query->leftJoin(['salarySum' => $subQuery], 'salarySum.department_id = id');
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$dataProvider->setSort([
'attributes' => [
'name',
'salary'
]
]);
$this->load($params);
if (!$this->validate()) {
return $dataProvider;
}
$query->andFilterWhere(['like', 'name', $this->name]);
$query->andWhere(['salarySum.salary_amount' => $this->salary]);
return $dataProvider;
}
GRID:
echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'salary_amount',
'employee_count',
'name',
],
]);
So, can anybody tell me what I did wrong?
If You need a filter for the alias salary_amount you should add a specific var for this (do the fact salary_amount is an alias for sum(salary) the var $salary is not useful)
public $employee_count;
public $salary;
public $salary_amount;
otherwise use salary as alias too
the in your filter you are using
$query->andWhere(['salarySum.salary_amount' => $this->salary]);
But salary_amount is the result of an aggregation so you should use having(SUM(salary) = $this->salary_amount)
$query->having('SUM(salary) = ' . $this->salary_amount);

Yii2 : Dataprovider append query issue in controller

I have append query in controller using dataprovider object but my last query is not apply after used getKeys() method.
Why without getKeys() method is possible but with getKeys() not possible?
Please view my below code.
$searchModel = new MySearchModel();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$dataProvider->sort = false;
$pKey = $dataProvider->getKeys();
$otherId = MyModel::find()->andWhere(['id' => $pKey])->select('id')->column();
//This query is not apply/append when i used `getKeys()` method
$dataProvider->query->andWhere(['id' => $otherId]);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);

Default Filter in GridView with Yii2

I don't know how to set the filter default of GridView. It's mean when page loaded, it's will load the filter with specific condition that I've set.
Any idea for this?
Thanks
A simple way to do this is by using the search model .
I am using Default Gii generated code to explain the ways
public function actionIndex()
{
$searchModel = new UserSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
Say you want a dynamic filter when the page is loaded
use the link as
../index.php?r=user/index&UserSearch[id]=7
This will add a filter where id = 7 ie in my case since id is the primary key only one user will be listed
Say if you want always apply a filter without showing anything in the url
public function actionIndex()
{
$searchModel = new UserSearch();
$searchModel->name = 'mid';
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
This will create a filter where user's name has string 'mid'
if you want more advanced filters
you can edit the search() function in the UserSearch Class there the query used to populate the data and ActiveDataProvider will be available to you .
say you do't want to list users who are inactive .
public function search($params)
{
$query = User::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$this->load($params);
$query->andFilterWhere(['active_status' => 1]);
....
this method will provide you with limitless ways to filter your results ..
Hope this helps ..
I had the same problem and it worked for me
public function actionIndex()
{
$searchModel = new UserSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$dataProvider->query->andFilterWhere(['status'=>1]);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
This helps performing the filter for action is needed and for all, in my case I needed it alone in an environment
public function actionIndex()
{
$searchModel = new UserSearch();
// Filtro por Defecto y Reflejado en Formulario de Filtrado en Grid
$params = Yii::$app->request->queryParams;
if (!isset($params['UserSearch'])) {
$params['UserSearch']['status']=1;
}
$dataProvider = $searchModel->search($params);
// -----------------------------------------------------------
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
NOTA: Usar (!isset($params['UserSearch'])) para que solo se aplique como búsqueda por defecto ('Si no se ha definido ninguna condición de filtrado')
NOTE: Use (**! Isset ($ params ['UserSearch']) **) so that it only applies as a default search ('If no filter condition has been defined')
Yii2 ActiveDataProvider it self need a query builder, means you can filter your results when passing it the query object eg:
$query = Post::find()->where['status' => 'published'];
// Todo and more conditions with $query object
$provider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 20,
],
]);
A bit late, but only to keep a record on SO.
One way of setting the allowed filters in a Yii2 GridView widget is to use its filterModel object's rules function to return the wanted filtering fields set with the save attributes. So you can remove from this list all the unwanted filters not needed to be displayed in the GridView.
You can then customize the ActiveDataProvider query under the search function of the filterModel to properly build the requested filtered data.

Categories