Yii Sortable Column in CGridView - sorting

I have a calculated column I wanna have sortable.
I have a get method for it...
public function getCur_margin() {
return number_format(($this->store_cost / $this->store_price) / $this->store_cost * 100, 2) . '%';
}
And I followed this thread...
CGridview custom field sortable
But now it's looking for an actual column in the database. How can I fetch a field using <calculations> AS cur_margin only temporarily for this CActiveDataProvider?
Here is the controller code for the provider:
$dataProvider = new CActiveDataProvider('Products', array(
'criteria' => array(
'condition' => 'store_id IN (SELECT `id` FROM `stores` WHERE `user_id` = ' . Yii::app()->user->id . ')',
),
'pagination' => array(
'pageSize' => 15,
),
'sort'=>array(
'attributes'=>array(
'cur_margin' => array(
'asc' => 'cur_margin ASC',
'desc' => 'cur_margin DESC',
),
),
),
));

Have you tried to specify your custom field in select property of CDbCriteria?
'select' => array(
..., // fields to select
'(<calculations>) AS cur_margin'
)
You don't have to declare getCur_margin() method then but only declare a public $cur_margin member in your model. All required calculations will be done in SQL query.
You can also refer to this thread as an example.

Related

Yii filter search working for one relation but not for other

I am using Yii GridView filter and my data comes from Purcahses table.
That tables realted to
1 .vendors
2 .clients
Below are my relations in Purchases Model
'vendor' => array(self::BELONGS_TO, 'Vendors', 'Vendor'),
'client' => array(self::BELONGS_TO, 'Clientsinfo', 'clientID'),
and i am using that relations in my admin view , Below is code of admin.php file
$this->widget('zii.widgets.grid.CGridView', array(
'id' => 'purchases-grid',
'dataProvider' => $model->search(),
'filter' => $model,
'columns' => array(
array(
'name' => 'Vendor',
'value' =>'$data->vendor->VendorName',
),
array(
'name' => 'clientID',
'value' =>'$data->client->Company'
),
array(
'name' => 'PurchaseType',
'value' => 'Purchasetype::model()->findByPk($data->PurchaseType)->PurchaseType',
'filter' => '',
),
),
));
and lastly here is model search function where is use with and together to get data from other tables
public function search() {
$criteria = new CDbCriteria;
$criteria->together = true;
$criteria->with = array('vendor');
$criteria->with = array('client');
$criteria->compare('PurchaseType', $this->PurchaseType);
$criteria->compare('vendor.VendorName', $this->Vendor, true);
$criteria->compare('client.Company', $this->clientID, true);
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
));
}
Getting data from client successufully , but when i use vendor fitter it throw me error below
CDbException
CDbCommand failed to execute the SQL statement: SQLSTATE[42S22]: Column not found: 1054 Unknown column
'vendor.VendorName' in 'where clause'. The SQL statement executed was: SELECT COUNT(DISTINCT t.PurchaseID
) FROM purchases t LEFT OUTER JOIN clientsinfo client ON (t.clientID=client.ClInfoID
) WHERE ((Vendor=:ycp0) AND (vendor.VendorName LIKE :ycp1)) (C:\xampp\htdocs\laurenparker\framework
\db\CDbCommand.php:543)#0 C:\xampp\htdocs\laurenparker\framework\db\CDbCommand.php(433): CDbCommand-
queryInternal('fetchColumn', 0, Array)
Youre overriding relations in criteria. Change this:
$criteria->with = array('vendor');
$criteria->with = array('client');
To this:
$criteria->with = array('vendor', 'client');

Laravel insert or update multiple rows

Im new in laravel, and im trying to update my navigation tree.
So i want to update my whole tree in one query without foreach.
array(
array('id'=>1, 'name'=>'some navigation point', 'parent'='0'),
array('id'=>2, 'name'=>'some navigation point', 'parent'='1'),
array('id'=>3, 'name'=>'some navigation point', 'parent'='1')
);
I just want to ask - is there posibility in laravel to insert(if new in array) or update my current rows in database?
I want to update all, because i have fields _lft, _right, parent_id in my tree and im using some dragable js plugin to set my navigation structure - and now i want to save it.
I tried to use
Navigation::updateOrCreate(array(array('id' => '3'), array('id'=>'4')), array(array('name' => 'test11'), array('name' => 'test22')));
But it works just for single row, not multiple like i tried to do.
Maybe there is another way to do it?
It's now available in Laravel >= 8.x
The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method's third and final argument is an array of columns that should be updated if a matching record already exists in the database:
Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], ['departure', 'destination'], ['price']);
I wonder why this kind of feature is not yet available in Laravel core (till today). Check out this gist The result of the query string would look like this: here
I am putting the code here just in case the link breaks in the future, I am not the author:
/**
* Mass (bulk) insert or update on duplicate for Laravel 4/5
*
* insertOrUpdate([
* ['id'=>1,'value'=>10],
* ['id'=>2,'value'=>60]
* ]);
*
*
* #param array $rows
*/
function insertOrUpdate(array $rows){
$table = \DB::getTablePrefix().with(new self)->getTable();
$first = reset($rows);
$columns = implode( ',',
array_map( function( $value ) { return "$value"; } , array_keys($first) )
);
$values = implode( ',', array_map( function( $row ) {
return '('.implode( ',',
array_map( function( $value ) { return '"'.str_replace('"', '""', $value).'"'; } , $row )
).')';
} , $rows )
);
$updates = implode( ',',
array_map( function( $value ) { return "$value = VALUES($value)"; } , array_keys($first) )
);
$sql = "INSERT INTO {$table}({$columns}) VALUES {$values} ON DUPLICATE KEY UPDATE {$updates}";
return \DB::statement( $sql );
}
So you can safely have your arrays inserted or updated as:
insertOrUpdate(
array(
array('id'=>1, 'name'=>'some navigation point', 'parent'='0'),
array('id'=>2, 'name'=>'some navigation point', 'parent'='1'),
array('id'=>3, 'name'=>'some navigation point', 'parent'='1')
)
);
Just in case any trouble with the first line in the function you can simply add a table name as a second argument, then comment out the line i.e:
function insertOrUpdate(array $rows, $table){
.....
}
insertOrUpdate(myarrays,'MyTableName');
NB: Be careful though to sanitise your input! and remember the timestamp fields are not touched. you can do that by adding manually to each arrays in the main array.
I've created an UPSERT package for all databases: https://github.com/staudenmeir/laravel-upsert
DB::table('navigation')->upsert(
[
['id' => 1, 'name' => 'some navigation point', 'parent' => '0'],
['id' => 2, 'name' => 'some navigation point', 'parent' => '1'],
['id' => 3, 'name' => 'some navigation point', 'parent' => '1'],
],
'id'
);
Eloquent Style
public function meta(){ // in parent models.
return $this->hasMany('App\Models\DB_CHILD', 'fk_id','local_fk_id');
}
.
.
.
$parent= PARENT_DB::findOrFail($id);
$metaData= [];
foreach ($meta['meta'] as $metaKey => $metaValue) {
if ($parent->meta()->where([['meta_key', '=',$metaKey]] )->exists()) {
$parent->meta()->where([['meta_key', '=',$metaKey]])->update(['meta_value' => $metaValue]);
}else{
$metaData[] = [
'FK_ID'=>$fkId,
'meta_key'=>$metaKey,
'meta_value'=> $metaValue
];
}
}
$Member->meta()->insert($metaData);
No, you can't do this. You can insert() multiple rows at once and you can update() multiple rows using same where() condition, but if you want to use updateOrCreate(), you'll need to use foreach() loop.
I didn't find a way to bulk insert or update in one query. But I have managed with only 3 queries. I have one table name shipping_costs. Here I want to update the shipping cost against the shipping area. I have only 5 columns in this table id, area_id, cost, created_at, updated_at.
// first get ids from table
$exist_ids = DB::table('shipping_costs')->pluck('area_id')->toArray();
// get requested ids
$requested_ids = $request->get('area_ids');
// get updatable ids
$updatable_ids = array_values(array_intersect($exist_ids, $requested_ids));
// get insertable ids
$insertable_ids = array_values(array_diff($requested_ids, $exist_ids));
// prepare data for insert
$data = collect();
foreach ($insertable_ids as $id) {
$data->push([
'area_id' => $id,
'cost' => $request->get('cost'),
'created_at' => now(),
'updated_at' => now()
]);
}
DB::table('shipping_costs')->insert($data->toArray());
// prepare for update
DB::table('shipping_costs')
->whereIn('area_id', $updatable_ids)
->update([
'cost' => $request->get('cost'),
'updated_at' => now()
]);
in your controller
use DB;
public function arrDta(){
$up_or_create_data=array(
array('id'=>2, 'name'=>'test11'),
array('id'=>4, 'name'=>'test22')
);
var_dump($up_or_create_data);
echo "fjsdhg";
foreach ($up_or_create_data as $key => $value) {
echo "key ".$key;
echo "<br>";
echo " id: ".$up_or_create_data[$key]["id"];
echo "<br>";
echo " Name: ".$up_or_create_data[$key]["name"];
if (Navigation::where('id', '=',$up_or_create_data[$key]["id"])->exists()) {
DB::table('your_table_ name')->where('id',$up_or_create_data[$key]["id"])->update(['name' => $up_or_create_data[$key]["name"]]);
}else{
DB::insert('insert into your_table_name (id, name) values (?, ?)', [$up_or_create_data[$key]["id"], $up_or_create_data[$key]["name"]]);
}
}

CakePHP paginator isn't sorting

In CakePHP, I would like to sort my index list (created by the paginator component) on sequence ASC, but it won't work. If I see the query setup in the CakePHP docs (http://book.cakephp.org/2.0/en/core-libraries/components/pagination.html#query-setup), my paginator settings should look like this:
public function index()
{
$this->Paginator->settings = array(
'Attraction' => array(
'conditions' => array(
'Attraction.deleted' => null
),
'order' => array(
'Attraction.sequence ASC',
'Attraction.id ASC'
),
'limit' => 15
)
);
$attractions = $this->Paginator->paginate('Attraction');
$this->set('attractions', $attractions);
}
But every time I load my index file, the list is sorted on ID and ignores the "order" setting. Can anybody tell me if there's anything wrong with my "order" item in my paginator settings? ;)
Thx!
try
'order' => array(
'Attraction.sequence' => 'asc',
'Attraction.id' => 'asc'
),
edit:
The sorting direction must be in the array value, while the field name should be the index. The examles I found in the documentations are always showing that
I don't think you should have that Attraction index in your options array - in other words, I think your options array should look like this:
$this->Paginator->settings = array(
'conditions' => array(
'Attraction.deleted' => null
),
'order' => array(
'Attraction.sequence ASC',
'Attraction.id ASC'
),
'limit' => 15
);

filter and CActiveDataProvider in CGridView

I have these code in index action of controller:
public function actionIndex()
{
$cid = #$_GET['cid'];
$country = Country::model()->findByPk($cid);
if($cid)
$dataProvider=new CActiveDataProvider('City', array(
'criteria'=>array(
'condition'=>'ci_co_id ='.$cid,
),
));
else
$dataProvider=new CActiveDataProvider('City');
$this->render('index',array(
'dataProvider'=>$dataProvider,
'country' => $country
));
}
and these in view/index.php file:
<?php
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'city-grid',
'dataProvider'=>$dataProvider,
'filter' => $dataProvider,
'columns'=>array(
array(
'name' => ' ',
'value' => '$row + 1',
),
'ci_name',
'ci_pcode',
array(
'class'=>'CButtonColumn',
),
)
));
?>
but Yii gives me this error:
CActiveDataProvider and its behaviors do not have a method or closure named "getValidators".
What is the problem?
The filter has to be a class that extends CModel. However, you don't seem to be doing any actual filtering, so you can just comment the filter line of your CGridView out.
As a side note, you have major security hole in your criteria. You are leaving yourself wide open to an SQL injection attack.
Specify your criteria as follows to let the database handler properly escape the input:
'criteria'=>array(
'condition'=>'ci_co_id =:cid',
'params'=>array(':cid'=>$cid),
),

yii sort by a custom attribute

In my Vacation model Vac I have this function
public function getVacCount(){
this function returns how many days there are in one vacation.
and I want to add a custom column to the cgridview like this:
<?php
$this->widget('zii.widgets.grid.CGridView', array(
...
array(
'name' => 'count',
'value' => '$data->getVacPeriod()'
),
...
),
));
?>
it works fine.
but I don't know how can I sort upon this custom attribute.
I tried to use CSort but it does not work. any idea?
To use CSort for sorting, you'll need to convert your vacation function into a SQL query and then stash the results in a public variable in your model.
CSort only works with SQL statements/functions, as underneath it's using ORDER BY to do all the sorting.
More info (and demo code) available here
Here's a sample of how I'm doing it on a site of mine:
$criteria->select = array(
"*",
new CDbExpression("IF(survey.RequestDate, survey.RequestDate, SurveyCompleteDate) AS SurveyDate")
);
This then allows me to do this type of filter:
return new CActiveDataProvider($this, array(
'criteria' => $criteria,
'sort'=>array(
'attributes'=>array(
'SurveyDate' => array(
'asc' => 'SurveyDate',
'desc' => 'SurveyDate DESC',
),
'*',
),
),
);
Note: you'll also need a public variable defined in your model to hold the results of the CDbExpression that you're doing. Mine is called SurveyDate.

Resources