Subtraction instead sum operation in yii2

Hello everyone, taking a cue from the solution on this link:

YII2 gridview sort sum of values of two columns

I would like to get a subtraction between two columns with numeric values ​​and the resulting difference, both positive and negative, must appear on a third column. I’ll explain:

Columns:

Q.ty available-------------Q.ty used-------------Q.ty remaining
10--------------------------------5 ----------------------------?
12--------------------------------3 ----------------------------?

If I change the sign from addition to subtraction, it does not work anyway,
I know it’s my serious shortcoming.
Thanks in advance.

add what you have tried so far.

Below Controller, Models (with modifies) and Views:

app/controllers/SistopController
namespace app\controllers;

use Yii;
use app\models\Sistop;
use app\models\SistopSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;

/**
 * SistopController implements the CRUD actions for Sistop model.
 */
class SistopController extends Controller
{
    /**
     * {@inheritdoc}
     */
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['POST'],
                ],
            ],
            'access' => [
                'class' => \yii\filters\AccessControl::className(),
                'rules' => [
                    [
                        'allow' => true,
                        'actions' => ['index', 'view'],
                        'roles' => ['view']
                    ],
                    [
                        'allow' => true,
                        'actions' => ['update'],
                        'roles' => ['update']
                        ],
                    [
                        'allow' => true,
                        'actions' => ['create'],
                        'roles' => ['create']
                    ],
                    [
                        'allow' => true,
                        'actions' => ['delete'],
                        'roles' => ['delete']
                    ],

                    [
                        'allow' => false
                    ]
                ]
            ]
        ];
    }
    /**
     * Lists all Sistop models.
     * @return mixed
     */
    public function actionIndex()
    {
        $searchModel = new SistopSearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

        return $this->render('index', [
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
        ]);
    }

    /**
     * Displays a single Sistop model.
     * @param integer $id
     * @return mixed
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionView($id)
    {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }

    /**
     * Creates a new Sistop model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        $model = new Sistop();

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id_sistop]);
        }

        return $this->render('create', [
            'model' => $model,
        ]);
    }

    /**
     * Updates an existing Sistop model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id_sistop]);
        }

        return $this->render('update', [
            'model' => $model,
        ]);
    }

    /**
     * Deletes an existing Sistop model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function actionDelete($id)
    {
        $this->findModel($id)->delete();

        return $this->redirect(['index']);
    }

    /**
     * Finds the Sistop model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Sistop the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Sistop::findOne($id)) !== null) {
            return $model;
        }

        throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exist.'));
    }
}
app/models/Sistop.php
namespace app\models;

use Da\User\Model\User;
use Yii;
use yii\db\Expression;
use yii\behaviors\TimestampBehavior;
use yii\behaviors\BlameableBehavior;



/**
 * This is the model class for table "{{%sistop}}".
 *
 * @property int $id_sistop
 * @property string|null $elencosistop Sistema Operativo
 * @property string|null $licsistop Licenza Uso
 * @property int|null $qdsistop Q.tà disp.
 * @property int|null $qrsistop Q.tà rim.
 * @property string|null $created_at Data inserimento
 * @property int|null $created_by Inserito da
 * @property string|null $updated_at Data modifica
 * @property int|null $updated_by Modificato da
 *
 * @property Gestionalepc[] $gestionalepcs
 */
class Sistop extends \yii\db\ActiveRecord
{
    private $Gestionalepc;
    
    public $sum;

    public function getSum()
    {
        $this->sum = 0;

        if (is_numeric($this->qdsistop) && is_numeric($this->qusistop)) {
            $this->sum = $this->qdsistop + $this->qusistop;
        }

        return $this->sum;
    }

    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return '{{%sistop}}';
    }

    public function behaviors()
    {
        return [
            [
                'class' => TimestampBehavior::className(),
                'createdAtAttribute' => 'created_at',
                'updatedAtAttribute' => 'updated_at',
                'value' => new Expression('NOW()'),
            ],
            [
                'class' => BlameableBehavior::className(),
                'createdByAttribute' => 'created_by',
                'updatedByAttribute' => 'updated_by',
            ],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['qdsistop', 'qusistop', 'sum'], 'integer'],
            [['elencosistop'], 'string', 'max' => 255],
            [['elencosistop'], 'unique'],
            [['elencosistop'], 'required'],
            [['sum'], 'safe'],
            [['licsistop'], 'string', 'max' => 80],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'id_sistop' => Yii::t('app', 'ID'),
            'elencosistop' => Yii::t('app', 'Sistema Operativo'),
            'licsistop' => Yii::t('app', 'Product Key'),
            'qdsistop' => Yii::t('app', 'Q.tà disp.'),
            'qusistop' => Yii::t('app', 'Q.tà util.'),
            'sum' => Yii::t('app', 'Sum'),
            'created_at' => Yii::t('app', 'Data inserimento'),
            'created_by' => Yii::t('app', 'Inserito da'),
            'updated_at' => Yii::t('app', 'Data modifica'),
            'updated_by' => Yii::t('app', 'Modificato da'),
        ];
    }

    public function getCreatore()
    {
        return $this->hasOne(User::className(), ['id' => 'created_by']);
    }

    public function getModificatore()
    {
        return $this->hasOne(User::className(), ['id' => 'updated_by']);
    }

    /**
     * Gets query for [[Gestionepcs]].
     *
     * @return \yii\db\ActiveQuery|GestionalepcQuery
     */
    public function getGestionalepcs()
    {
        return $this->hasMany(Gestionalepc::className(), ['os' => 'id_sistop']);
    }

    /**
     * {@inheritdoc}
     * @return SistopQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new SistopQuery(get_called_class());
    }
}
app/models/SistopSearch.php
namespace app\models;

use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\Sistop;

class SistopSearch extends Sistop
{
    
    public function attributes()
    {
        // add related fields to searchable attributes
        return array_merge(parent::attributes(), ['sum']);
    }
    
    public function rules()
    {
        return [
            [['id_sistop', 'qdsistop', 'qusistop', 'sum'], 'integer'],
            [['elencosistop', 'licsistop', 'sum'], 'safe'],
        ];
    }

    public function scenarios()
    {
        return Model::scenarios();
    }

    public function search($params)
    {
        $query = Sistop::find()->select('*, (`qdsistop` + `qusistop`) AS `sum`');
        
        // add conditions that should always apply here
        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);
        
        // enable sorting for the related columns
        $dataProvider->sort->attributes['sum'] = [
            'asc' => ['sum' => SORT_ASC],
            'desc' => ['sum' => SORT_DESC],
        ];
        
        $this->load($params);
        if (!$this->validate()) {
            return $dataProvider;
        }
        // grid filtering conditions
        $query->andFilterWhere([
            'id_sistop' => $this->id_sistop,
            'qdsistop' => $this->qdsistop,
            'qusistop' => $this->qusistop,
            'created_at' => $this->created_at,
            'created_by' => $this->created_by,
            'updated_at' => $this->updated_at,
            'updated_by' => $this->updated_by,
        ]);
        
        
        $query->andFilterWhere(['like', 'elencosistop', $this->elencosistop])
            ->andFilterWhere(['like', 'licsistop', $this->licsistop]);
        return $dataProvider;
        
        // if the sum has a numeric filter value set, apply the filter in the HAVING clause
        if (is_numeric($this->sum)) {
            $query->having([
                'sum' => $this->sum,
            ]);
        }
    }
}
app/views/sistop/index.php
use yii\helpers\Html;
use yii\widgets\DetailView;

/* @var $this yii\web\View */
/* @var $model app\models\Sistop */

$this->title = $model->elencosistop;
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Elenco Sistemi Operativi'), 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
\yii\web\YiiAsset::register($this);
?>
<div class="sistop-view">

    <h1><?= Html::encode($this->title) ?></h1>

    <p>
        <?= Html::a(Yii::t('app', 'Modifica'), ['update', 'id' => $model->id_sistop], ['class' => 'btn btn-primary']) ?>
        <?= Html::a(Yii::t('app', 'Elimina'), ['delete', 'id' => $model->id_sistop], [
            'class' => 'btn btn-danger',
            'data' => [
                'confirm' => Yii::t('app', 'Are you sure you want to delete this item?'),
                'method' => 'post',
            ],
        ]) ?>
        <?= Html::a(Yii::t('app', 'Aggiungi'), ['create'], ['class' => 'btn btn-success']) ?>
        <?= Html::a(Yii::t('app', 'Elenco Completo'), ['index'], ['class' => 'btn btn-warning']) ?>
    </p>

    <?= DetailView::widget([
        'model' => $model,
        'attributes' => [
            //'id_sistop',
            'elencosistop',
            'licsistop',
            'qdsistop',
            [
                'attribute' => 'qusistop',
                'value' => function ($sistopModel) {
                    return $sistopModel->getGestionalepcs()->count();
                }
            ],
            'created_at:datetime',
            'creatore.username',
            'updated_at:datetime',
            'modificatore.username',
        ],
    ]) ?>

</div>

I took inspiration from other solutions that made the example of adding two columns and the result in a third. My problem is that I don’t need addition but subtraction between the two columns.
In my case:
‘qdsistop’ = Q.ty available
‘qusistop’ = Q.ty used (and it works well)

and a third column which could be:
‘qrsistop’ = that is the difference between ‘qdsistop’ and ‘qusistop’

I inserted the sum function and it returns me (no value) and if I try to replace the ‘+’ with the sign ‘-’ it always returns the wording for each line (no value).

I checked and double-checked but I didn’t really understand where the problem lies.

Thanks in advance for your interest.

I also tried to modify this way on the Sistop.php model but nothing to do, it gives me back (no value).

public function getDiff()
{
    $this->diff = 0;

    if (is_numeric($this->qdsistop) && is_numeric($this->qusistop)) {
        $this->diff = $this->qdsistop - $this->qusistop;
    }

    return $this->diff;
}

Modified and add also in SistopSearch.php

    public function attributes()
    {
        return array_merge(parent::attributes(), ['diff']);
    }

   .......................
   
public function search($params)
    {
        $query = Sistop::find()->select('*, (`qdsistop` - `qusistop`) AS `diff`');
        // add conditions that should always apply here
        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        // enable sorting for the related columns
        $dataProvider->sort->attributes['diff'] = [
            'asc' => ['diff' => SORT_ASC],
            'desc' => ['diff' => SORT_DESC],
        ];

........

        if (is_numeric($this->diff)) {
            $query->having([
                'diff' => $this->diff,
            ]);
        }

use an ArrayDataProvider instead of ActiveDataProvider, it will make it easier to do what you want.

1 Like

Hi @photowebstyle,

I see nothing bad in your idea and code. But …

Would you please try this instead?

    $query = Sistop::find()->select([
        '{{sistop}}.*',
        '([[qdsistop]] - [[qusistop]]) as diff',
    ]);

$columns parameter of select() seems to be safer in array format than in string format.
https://www.yiiframework.com/doc/api/2.0/yii-db-query#select()-detail

And one more thing.

The public attribute of diff and the getter method getDiff might cause a collision problem, since they share the same name of diff.
I would write like the following in Sistop.php


protected $_diff = null;

public function getDiff()
{
    if ($this->$_diff === null) {
         if (is_numeric($this->qdsistop) && is_numeric($this->qusistop)) {
            $this->$_diff = $this->qdsistop - $this->qusistop;
        } else {
            $this->$_diff = 0;
        }
    }
    return $this->$_diff;
}

public function setDiff($value)
{
    $this->$_diff = $value;
}

Hi @softark, the variable $ _diff is not valued and gives me an error.
In the meantime, I redone everything in this way, also because I realized that the column ‘qusistop’ (Used Quantity) in the mysql table always remained NULL.
So I did it again:

CREATE TABLE `sistop` (
  `id_sistop` int(11) NOT NULL,
  `elencosistop` varchar(255) DEFAULT NULL COMMENT 'Sistema Operativo',
  `licsistop` varchar(80) DEFAULT NULL COMMENT 'Licenza Uso',
  `qdsistop` int(5) NOT NULL DEFAULT '0' COMMENT 'Q.tà disp.',
  `qusistop` int(5) NOT NULL DEFAULT '0' COMMENT 'Q.ta util.',
  `qrsistop` int(5) NOT NULL DEFAULT '0' COMMENT 'Q.tà rim.',
  `created_at` timestamp NULL DEFAULT NULL COMMENT 'Data inserimento',
  `created_by` int(11) DEFAULT NULL COMMENT 'Inserito da',
  `updated_at` timestamp NULL DEFAULT NULL COMMENT 'Data modifica',
  `updated_by` int(11) DEFAULT NULL COMMENT 'Modificato da'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
app\models\Sistop.php
namespace app\models;

use Da\User\Model\User;
use Yii;
use yii\db\Expression;
use yii\behaviors\TimestampBehavior;
use yii\behaviors\BlameableBehavior;

/**
 * This is the model class for table "{{%sistop}}".
 *
 * @property int $id_sistop
 * @property string|null $elencosistop Sistema Operativo
 * @property string|null $licsistop Licenza Uso
 * @property int|null $qdsistop Q.tà disp.
 * @property int|null $qusistop Q.tà rim.
 * @property string|null $created_at Data inserimento
 * @property int|null $created_by Inserito da
 * @property string|null $updated_at Data modifica
 * @property int|null $updated_by Modificato da
 *
 * @property Gestionalepc[] $gestionalepcs
 */
class Sistop extends \yii\db\ActiveRecord
{
    private $Gestionalepc;

    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return '{{%sistop}}';
    }

    public function behaviors()
    {
        return [
            [
                'class' => TimestampBehavior::className(),
                'createdAtAttribute' => 'created_at',
                'updatedAtAttribute' => 'updated_at',
                'value' => new Expression('NOW()'),
            ],
            [
                'class' => BlameableBehavior::className(),
                'createdByAttribute' => 'created_by',
                'updatedByAttribute' => 'updated_by',
            ],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['elencosistop'], 'string', 'max' => 255],
            [['licsistop'], 'string', 'max' => 80],
            [['qdsistop', 'qusistop','qrsistop'], 'integer'],
            [['elencosistop'], 'unique'],
            [['elencosistop','qdsistop', 'licstop'], 'required'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'id_sistop' => Yii::t('app', 'ID'),
            'elencosistop' => Yii::t('app', 'Sistema Operativo'),
            'licsistop' => Yii::t('app', 'Product Key'),
            'qdsistop' => Yii::t('app', 'Q.tà disp.'),
            'qusistop' => Yii::t('app', 'Q.tà util.'),
            'qrsistop' => Yii::t('app', 'Q.tà rim.'),
            'created_at' => Yii::t('app', 'Data inserimento'),
            'created_by' => Yii::t('app', 'Inserito da'),
            'updated_at' => Yii::t('app', 'Data modifica'),
            'updated_by' => Yii::t('app', 'Modificato da'),
        ];
    }

    public function getCreatore()
    {
        return $this->hasOne(User::className(), ['id' => 'created_by']);
    }

    public function getModificatore()
    {
        return $this->hasOne(User::className(), ['id' => 'updated_by']);
    }

    /**
     * Gets query for [[Gestionepcs]].
     *
     * @return \yii\db\ActiveQuery|GestionalepcQuery
     */
    public function getGestionalepcs()
    {
        return $this->hasMany(Gestionalepc::className(), ['os' => 'id_sistop']);
    }

    /**
     * {@inheritdoc}
     * @return SistopQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new SistopQuery(get_called_class());
    }
}
app\models\SistopSearch.php
namespace app\models;

use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\Sistop;

class SistopSearch extends Sistop
{
    public function rules()
    {
        return [
            [['id_sistop', 'qdsistop', 'qusistop', 'qrsistop'], 'integer'],
            [['elencosistop', 'licsistop'], 'safe'],
        ];
    }

    public function scenarios()
    {
        return Model::scenarios();
    }

    public function search($params)
    {
        $query = Sistop::find();
        // add conditions that should always apply here
        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);
        $this->load($params);
        if (!$this->validate()) {
            return $dataProvider;
        }
        // grid filtering conditions
        $query->andFilterWhere([
            'id_sistop' => $this->id_sistop,
            'qdsistop' => $this->qdsistop,
            'qusistop' => $this->qusistop,
            'created_at' => $this->created_at,
            'created_by' => $this->created_by,
            'updated_at' => $this->updated_at,
            'updated_by' => $this->updated_by,
        ]);
        $query->andFilterWhere(['like', 'elencosistop', $this->elencosistop])
              ->andFilterWhere(['like', 'licsistop', $this->licsistop]);
        return $dataProvider;
    }
}
app\views\sistop\index.php
use app\models\Sistop;
use yii\helpers\Html;
use yii\grid\GridView;
use yii\widgets\Pjax;

/* @var $this yii\web\View */
/* @var $searchModel app\models\SistopSearch */
/* @var $dataProvider yii\data\ActiveDataProvider */

$this->title = Yii::t('app', 'Elenco Sistemi Operativi');
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="sistop-index">

    <h1><?= Html::encode($this->title) ?></h1>

    <p>
        <?= Html::a(Yii::t('app', 'Aggiungi nuovo'), ['create'], ['class' => 'btn btn-success']) ?>
    </p>

    <?php Pjax::begin(); ?>
    <?php // echo $this->render('_search', ['model' => $searchModel]); 
    ?>
    <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],

            //'id_sistop',
            'elencosistop',
            'licsistop',
            'qdsistop',
            [
                'attribute' => 'qusistop',
                'value' => function ($sistopModel) {
                    return $sistopModel->getGestionalepcs()->count();
                }
            ],
            [
                'attribute' =>  'qrsistop',
                'value' => $data->qdsistop - $data->getGestionalepcs()->count()
            ],
            //'created_at:datetime',
            //[
            //    'attribute' => 'created_by',
            //    'value' => 'creatore.username',
            //],
            //'updated_at:datetime',
            // [
            //    'attribute' => 'updated_by',
            //    'value' => 'modificatore.username',
            //],
            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]); ?>

    <?php Pjax::end(); ?>

</div>
app\views\sistop\view.php
use yii\helpers\Html;
use yii\widgets\DetailView;

/* @var $this yii\web\View */
/* @var $model app\models\Sistop */

$this->title = $model->elencosistop;
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Elenco Sistemi Operativi'), 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
\yii\web\YiiAsset::register($this);
?>
<div class="sistop-view">

    <h1><?= Html::encode($this->title) ?></h1>

    <p>
        <?= Html::a(Yii::t('app', 'Modifica'), ['update', 'id' => $model->id_sistop], ['class' => 'btn btn-primary']) ?>
        <?= Html::a(Yii::t('app', 'Elimina'), ['delete', 'id' => $model->id_sistop], [
            'class' => 'btn btn-danger',
            'data' => [
                'confirm' => Yii::t('app', 'Are you sure you want to delete this item?'),
                'method' => 'post',
            ],
        ]) ?>
        <?= Html::a(Yii::t('app', 'Aggiungi'), ['create'], ['class' => 'btn btn-success']) ?>
        <?= Html::a(Yii::t('app', 'Elenco Completo'), ['index'], ['class' => 'btn btn-warning']) ?>
    </p>

    <?= DetailView::widget([
        'model' => $model,
        'attributes' => [
            //'id_sistop',
            'elencosistop',
            'licsistop',
            'qdsistop',
            [
                'attribute' => 'qusistop',
                'value' => function ($sistopModel) {
                    return $sistopModel->getGestionalepcs()->count();
                }
            ],
            'qrsistop',
            'created_at:datetime',
            'creatore.username',
            'updated_at:datetime',
            'modificatore.username',
        ],
    ]) ?>

</div>

In this way I solved the problem on the column ‘qusistop’ of the table in mysql. I have not resolved however on ‘qrsistop’ because it gives me an error:
PHP Notice - yii \ base \ ErrorException Undefined variable: data

And if i comment (in app\views\sistop\index.php)

//'value' => $data->qdsistop - $data->getGestionalepcs()->count()

Not appare error but not working ‘qrsistop’ column.

I don’t know where I mistake.

I changed e recreate all but not working.

With the modify

[
   'attribute' =>  'qrsistop',
        'value' => function ($data) {return $data->qdsistop - $data->getGestionalepcs()->count();
         }
],

it works on the view, but in the mysql column ‘qrsistop’ and ‘qusistop’ it does not change.

Hi @photowebstyle,

qusistop is the count of related gestionalepc records, and qrsistop equals qdsistop - qusistop. Do I get you right?
Then you don’t need to have qusistop and qrsistop columns in the table because they can be calculated.

Please look at the following section of the guide. All that you want to know is written there.
Guide > Active Record > Selecting extra fields

1 Like

I understand, thanks for help me.

with an array data provider, you will need to do those calculations in php, just fetch the date with sql and manipulate it in php.