I am trying to get the list of results from the Orders table in my CakePhp 2.x application to sort themselves by the date closest to today.
In a usual mysql query I have had something similar working with say the following syntax:
ABS(DATEDIFF(Order.duedate, NOW()))
However in Cake I am not sure how to get such a custom query to work within the paginate helper. Is this something I may need to set in a finder query in the model?
Here is the controller I currently have (set to a bog standard descending sort)
public function index() {
$this->paginate = array(
'conditions' => array (
'Order.despatched' => array('Not Despatched' , 'Split Despatched')
),
'limit' => 25,
'order' => array('Order.duedate' => 'DESC')
);
$data = $this->paginate('Order');
$this->set('orders', $data);
}
Edit: Using information from the comment below I added a virtual field into the controller which causes an sql error. At first I thought this was due to the associations within the model, to attempt to rectify this I added the virtualField into the Order Model and into the constructor of the associated Account Model. However this made no change to the part of the sql which breaks. The full sql error is:
Error: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AS `Order__closestdate`, `Account`.`id`, `Account`.`company`, `Account`.`contac' at line 1
SQL Query: SELECT `Order`.`id`, `Order`.`order_id`, `Order`.`orderdate`,
`Order`.`duedate`, `Order`.`rework`, `Order`.`rework_notes`, `Order`.`comments`,
`Order`.`status`, `Order`.`customer`, `Order`.`last_edited`, `Order`.`value`,
`Order`.`quantity`, `Order`.`order_for`, `Order`.`warehouse`, `Order`.`haulier`,
`Order`.`terms`, `Order`.`type`, `Order`.`despatched`, `Order`.`despatched_date`,
`Order`.`invoiced`, `Order`.`invoice_date`, `Order`.`bookingref`,
`Order`.`purchaseref`, `Order`.`payment_due`, `Order`.`payment_status`,
`Order`.`payment_date`, (ABS(DATEDIFF(duedate, NOW())) AS `Order__closestdate`,
`Account`.`id`, `Account`.`company`, `Account`.`contact`, `Account`.`phone`,
`Account`.`email`, `Account`.`postcode`, `Account`.`created`, `Account`.`modified`,
`Account`.`customer`, `Account`.`address1`, `Account`.`address2`, `Account`.`town`,
`Account`.`county`, (ABS(DATEDIFF(duedate, NOW())) AS `Account__closestdate` FROM
`hunter_om`.`orders` AS `Order` LEFT JOIN `hunter_om`.`accounts` AS `Account` ON
(`Order`.`customer` = `Account`.`customer`) WHERE `Order`.`despatched` IN ('Not
Despatched', 'Split Despatched') ORDER BY (ABS(DATEDIFF(duedate, NOW())) DESC LIMIT 25
For reference the code in the models are:
//Order Model
public $virtualFields = array(
'closestdate' => 'ABS(DATEDIFF(duedate, NOW())'
);
//Account Model
public function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
$this->virtualFields['closestdate'] = $this->Order->virtualFields['closestdate'];
}
Any help is appreciated thanks.
You'll need to add a 'virtualField' to the Order model before paginating;
$this->Order->virtualFields['closestdate'] = "ABS(DATEDIFF(Order.duedate, NOW()))";
Then use that field as a regular field in your query/paginate
public function index() {
$this->Order->virtualFields['closestdate'] = "ABS(DATEDIFF(Order.duedate, NOW()))";
$this->paginate = array(
'conditions' => array (
'Order.despatched' => array('Not Despatched' , 'Split Despatched')
),
'limit' => 25,
'order' => array('Order.closestdate' => 'DESC')
);
$data = $this->paginate('Order');
$this->set('orders', $data);
}
Related
I am using laravel eager loading to load data on the jquery datatables. My code looks like:
$columns = array(
0 => 'company_name',
1 => 'property_name',
2 => 'amenity_review',
3 => 'pricing_review',
4 => 'sqft_offset_review',
5 => 'created_at',
6 => 'last_uploaded_at'
);
$totalData = Property::count();
$limit = $request->input('length');
$start = $request->input('start');
$order = $columns[$request->input('order.0.column')];
$dir = $request->input('order.0.dir');
$query = Property::with(['company','notices']);
$company_search = $request->columns[0]['search']['value'];
if(!empty($company_search)){
$query->whereHas('company', function ($query) use($company_search) {
$query->where('name','like',$company_search.'%');
});
}
$property_search = $request->columns[1]['search']['value'];
if(!empty($property_search)){
$query->where('properties.property_name','like',$property_search.'%');
}
if(!Auth::user()->hasRole('superAdmin')) {
$query->where('company_id',Auth::user()->company_id);
}
$query->orderBy($order,$dir);
if($limit != '-1'){
$records = $query->offset($start)->limit($limit);
}
$records = $query->get();
With this method I received error: Column not found: 1054 Unknown column 'company_name' in 'order clause' .
Next, I tried with following order condition:
if($order == 'company_name'){
$query->orderBy('company.name',$dir);
}else{
$query->orderBy($order,$dir);
}
However, it also returns similar error: Column not found: 1054 Unknown column 'company.name' in 'order clause'
Next, I tried with whereHas condition:
if($order == 'company_name'){
$order = 'name';
$query->whereHas('company', function ($query) use($order,$dir) {
$query->orderBy($order,$dir);
});
}else{
$query->orderBy($order,$dir);
}
But, in this case also, same issue.
For other table, I have handled this type of situation using DB query, however, in this particular case I need the notices as the nested results because I have looped it on the frontend. So, I need to go through eloquent.
Also, I have seen other's answer where people have suggested to order directly in model like:
public function company()
{
return $this->belongsTo('App\Models\Company')->orderBy('name');
}
But, I don't want to order direclty on model because I don't want it to be ordered by name everytime. I want to leave it to default.
Also, on some other scenario, I saw people using join combining with, but I am not really impressed with using both join and with to load the same model.
What is the best way to solve my problem?
I have table like: companies: id, name, properties: id, property_name, company_id, notices: title, slug, body, property_id
The issue here is that the Property::with(['company','notices']); will not join the companies or notices tables, but only fetch the data and attach it to the resulting Collection. Therefore, neither of the tables are part of the SQL query issued and so you cannot order it by any field in those tables.
What Property::with(['company', 'notices'])->get() does is basically issue three queries (depending on your relation setup and scopes, it might be different queries):
SELECT * FROM properties ...
SELECT * FROM companies WHERE properties.id in (...)
SELECT * FROM notices WHERE properties.id in (...)
What you tried in the sample code above is to add an ORDER BY company_name or later an ORDER BY companies.name to the first query. The query scope knows no company_name column within the properties table of course and no companies table to look for the name column. company.name will not work either because there is no company table, and even if there was one, it would not have been joined in the first query either.
The best solution for you from my point of view would be to sort the result Collection instead of ordering via SQL by replacing $records = $query->get(); with $records = $query->get()->sortBy($order, $dir);, which is the most flexible way for your task.
For that to work, you would have to replace 'company_name' with 'company.name' in your $columns array.
The only other option I see is to ->join('companies', 'companies.id', 'properties.company_id'), which will join the companies table to the first query.
Putting it all together
So, given that the rest of your code works as it should, this should do it:
$columns = [
'company.name',
'property_name',
'amenity_review',
'pricing_review',
'sqft_offset_review',
'created_at',
'last_uploaded_at',
];
$totalData = Property::count();
$limit = $request->input('length');
$start = $request->input('start');
$order = $columns[$request->input('order.0.column')];
$dir = $request->input('order.0.dir');
$query = Property::with(['company', 'notices']);
$company_search = $request->columns[0]['search']['value'];
$property_search = $request->columns[1]['search']['value'];
if (!empty($company_search)) {
$query->whereHas(
'company', function ($query) use ($company_search) {
$query->where('name', 'like', $company_search . '%');
});
}
if (!empty($property_search)) {
$query->where('properties.property_name', 'like', $property_search . '%');
}
if (!Auth::user()->hasRole('superAdmin')) {
$query->where('company_id', Auth::user()->company_id);
}
if ($limit != '-1') {
$records = $query->offset($start)->limit($limit);
}
$records = $query->get()->sortBy($order, $dir);
may be someone knows how to filter product grid by returned value from renderer.
In Grid.php I have:
protected function _prepareCollection() {
$collection->joinField( 'qty', 'cataloginventory/stock_item', 'qty', 'product_id=entity_id', '{{table}}.stock_id=1', 'left' );
$collection->joinTable( 'cataloginventory/stock_item','product_id=entity_id', array("stock_status" => "is_in_stock", "plan") )
->addAttributeToSelect('stock_status')
->addAttributeToSelect('plan');
protected function _prepareColumns() {
$this->addColumn( 'qty',
array(
'header' => Mage::helper( 'catalog' )->__( 'Qty' ),
'width' => '30px',
'type' => 'number',
'index' => 'qty',
'renderer' => new TBT_Enhancedgrid_Block_Catalog_Product_Grid_Plan(),
) );
}
In Plan.php
public function render(Varien_Object $row) {
$value = $row->getData('qty');
if (is_numeric($value))
{
$value = round($value, 2);
}
$value1 = $row->getData('plan');
if (is_numeric($value1))
{
$value1 = round($value1, 2);
}
$value3 = $value/$value1;
$valuepr = number_format( $value3 * 100, 2 ) . '%';
return '<strong>К: </strong>'.$value. '<br/>'.'<strong>P: </strong>'.$value1.'<br/>'.'<strong>Z: </strong>'.$value2.'<br/>'.'<strong>Pr: </strong>'.$valuepr;
}
}
It works fine and display result I need, but how I can filter in grid by value, which have been get in $valuepr?
Thank you for your answers.
If you want to filter collection by $valuepr then it has to be in the collection itself. At the moment you are computing it outside of your collection in the render() method.
In order to do so, you would need to amend collection in the _prepareCollection() method. This could be similar to the following code (please note that I am not able to test this, so it is more a hint than a ready-to-go solution):
protected function _prepareCollection()
{
// current collection settings goes here
$collection->getSelect()
->columns(
array(
'value_pr' => new Zend_Db_Expr(
'ROUND(ROUND(table_prefix_1.qty, 2) / ROUND(table_prefix_2.plan, 2), 2)'
)
)
);
}
where table_prefix_1 is a prefix of first cataloginventory/stock_item table and table_prefix_2 is a prefix of second stock_item table. If you want to know what prefixes those tables have you can log the SQL query, e.g.:
Mage::log($collection->getSelect()->assemble(), null, 'sql.log');
// or
$collection->load(false, true);
The first one will log the SQL query into sql.log file. You will find it in var/log folder of your Magento instance. This, however will not work fine with EAV collections like products, categories or customers. That's why I provided second way of checking the SQL. You can provide 2 parameters into collection's load() method. First one is a flag tells whether to display the query before running it against database and second one is a flag which tells whether to log the query into system.log.
Please note that you can filter a grid column only by one value. You cannot filter column qty by quantity and value_pr at the same time. You have to decide which one you want to use. You can always make it into separate columns.
I hope this helps.
For example I have two related models with MANY_MANY relation.
Need to find all models which name contains 'test' or in which relation model2.name contains 'test'.
On sql I wrote this query and it's what I want to get from ActiveRecord in result by using standard relations mechanism and CDbCriteria.
SELECT
m1.*
FROM
model1 m1
LEFT JOIN model_model mm
ON mm.from_id = m1.id
LEFT JOIN model2 m2
ON mm.to_id = m2.id
GROUP BY m1.id
HAVING (
m1.name LIKE '%test%'
OR GROUP_CONCAT(m2.name) LIKE '%test%'
);
Simple use Activerecord.findBySql is not good solution because I have many models such as above. So for faster combination any models a relations is prefered.
When I use CDbCriteria.with Yii generate 2 query.
When I use CDbCriteria.with with CDbCriteria.together Yii tried to select all columns from related tables, it's redundantly and may be slowly in future because number of relations can become much more than in this example.
Have any idea?
Thanks.
You should define the relation in "M1" model class:
public function relations()
{
return array(
'M2s' => array(self::MANY_MANY, 'M2',
'model_model(from_id, to_id)'),
);
}
In your M2 model create a scope:
public function nameScope($name)
{
$this->getDbCriteria()->mergeWith(array(
'condition'=>'name LIKE :name',
'params'=>array(':name'=>"%{$name}%"),
));
return $this;
}
If your ready you can do this:
M1::model()->findAll(
array(
'condition' => 'name LIKE %test%',
'with' => array(
'M2s' => array(
'scopes' => array(
'nameScope' => 'test',
)
)
),
)
);
This is related to one of my question earlier where:
Update table1 field with table2 field value in join laravel fluent
But since this is a different approach now, I will just ask another question:
How do you properly do an update using DB:raw?
I want to update the favorite_contents.type with the value of contents.type, but it doesn't do anything, the static setting of 1 to favorite_contents.expired is working.
This is my code which still doesn't update the type even when the DB::raw was used:
$table = 'favorite_contents';
$contents = DB::table($table)
->join('contents', function($join) use($table){
$join->on("$table.content_id", '=', 'contents.id');
})
->whereIn("$table.content_id",$ids)
->update(array(
"$table.expired" => 1
));
DB::raw("UPDATE favorite_contents, contents SET favorite_contents.type = contents.type where favorite_contents.content_id = contents.id");
This is the first code that doesn't update before I resorted to the above code that doesn't work as well:
$table = 'favorite_contents';
$contents = DB::table($table)
->join('contents', function($join) use($table){
$join->on("$table.content_id", '=', 'contents.id');
})
->whereIn("$table.content_id",$ids)
->update(array(
"$table.expired" => 1,
"$table.type" => "contents.type"
));
P.S:
This is working when done on an sql editor:
UPDATE favorite_contents, contents SET favorite_contents.type = contents.type where favorite_contents.content_id = contents.id
code raw updates like this:
...->update( array(
'column' => DB::raw( 'column * 2' )
) );
DB::statement("UPDATE favorite_contents, contents SET favorite_contents.type = contents.type where favorite_contents.content_id = contents.id");
Try DB::statement for raw queries that does not involve outputting something (select).
Will be work such similar, simple realization in Laravel 5.2 ,
Query Builder:
DB::table('stores')
->where('id', $request)
->update(['visibility' =>DB::raw($value)]);
This response is tested real site and working properly
I want to update my table where the input the same input fields names are array and has
add more function which generates the input fields like this:
I want to do update_batch in codeigniter
my model i created a function like this:
This is the code block:
function update_batch_all($tblname,$data=array(),$userid)
{
$this->db->trans_start();
$this->db->where('userid',$userid);
$this->db->update_batch($tblname,$data);
$this->db->trans_complete();
return TRUE;
}
it is not working.
can any one help me that how can i update tables data with update batch that has where condition?
You can read the docs for update_batch() here
Here's the short summary:
You pass in an associative array that has both, your where key, and the update value. As the third parameter to the update_batch() call, you specify which key in your assoc array should be used for the where clause.
For example:
$data = array(
array(
'user_id' => 1,
'name' => 'Foo'
), array(
'user_id' => 2,
'name' => 'Bar'
)
);
$this->db->update_batch($tbl, $data, 'user_id');
Breakdown of arguments passed:$tbl is the table name. $data is the associative array. 'user_id' tells CI that the user_id key in $data is the where clause.
Effect of the above query: Name for user with user_id = 1 gets set to Foo and name for user with user_id=2 gets set to Bar.
In your case, if you want to set the same user_id key in each array with your data array, you can do a quick for loop:
foreach ($data as &$d) {
$d['user_id'] = $user_id;
}
$this->db->update_batch($tbl, $data, 'user_id');
You can use,
$this->db->update_batch($tblname,$data,'user_id');
But all array within data must have a field 'user_id'
eg:
$data=array(
array('user_name'=>'test1','user_id'=>1),
array('user_name'=>'test2','user_id'=>2)
);
You can get more details about update_batch from here