I am using Laravel 7.0.
My domain_prices table has price, duration, addPrice columns.
I created DomainPrice model.
Domain price for specific duration is price + addPrice.
But for 5 years duration, I need to sum all prices and all addPrices for 1, 2, 3, 4, 5 years.
I set custom attribute to get totalPrice as following:
public function getTotalPriceAttribute()
{
return $this->price+$this->addPrice;
}
I wanted to make another custom attribute to get sumPrice as following:
public function getSumPriceAttribute()
{
$sumPrice = 0;
$prices = $this->where('Duration', '<=', $this->Duration)->get(); // I removed domain filter query here.
foreach($prices as $price)
{
$sumPrice += $price->totalPrice;
}
return $sumPrice;
}
But this didn't return exact results.
Please teach me with fancy approach. Thanks.
I think you used the wrong variable here when you sum your $sumPrice. you should change $subPrice in foreach loop to $sumPrice
public function getSumPriceAttribute()
{
$sumPrice = 0;
$prices = $this->where('Duration', '<=', $this->Duration)->get(); // I removed domain filter query here.
foreach($prices as $price)
{
$sumPrice += $price->totalPrice; // change $subPrice to $sumPrice
}
return $sumPrice;
}
Related
How to sum data
After getting the previous calculations on controller Laravel
private $nn;
public function detailproject($code_project)
{
$countdata = count($listdata);
$countdata = 15 / $countdata;
foreach ($listdata as $obj ) {
$this->nn = $obj->percent * $countdata / 100;
$this->nn +=($obj->persen);
}
dd($this->nn);
}
How to do this?
You could use array_reduce() or reduce(). Since it's a Laravel question, I'll go with reduce()
$collection = collect($listdata);
$percentage = 0.15 * $collection->count();
$sum = $collection->reduce(function ($carry, $item) use ($percentage) {
return $carry + ($item->percent * $percentage);
}, 0);
dd($sum);
if you are returning after saving data use this method where you pull data from existing columns and show them on blade file..
//initialize percentage if constant for every record on DB
//if not constant pull($Percentage) from DB as column as shown below.
//$x; $y are specific columns found in table in DB.
// replace mathematical signs where necessary.
// if needed as per row mask the formulae in your for each loop
$percentage = ''
$total = array_sum(array_map(function($x, $y) { return $x * $y; },
$carry, $item)) * ($percentage);
I'm overriding Mage_Catalog_Block_Product_List 's _getProductCollection by adding:
foreach ($this->_productCollection as $product) {
$product->setDistance(Mage::helper('myhelper')->getDistance($product));
}
Now I want the collection to be sorted by distance, I tried the following:
$this->_productCollection = Mage::helper('myhelper')->sortProductByDist($this->_productCollection);
The helper for sorting is like following (stolen from SO):
public function sortProductByDist($products) {
$sortedCollection = Mage::getSingleton('catalog/layer')
->getProductCollection()->addFieldToFilter('entity_id', 0);
$sortedCollection = $sortedCollection->clear();
$collectionItems = $products->getItems();
usort($collectionItems, array($this,'_sortItems'));
foreach ($collectionItems as $item) {
$sortedCollection->addItem($item);
}
return $sortedCollection;
}
protected function _sortItems($a, $b) {
$order = 'asc';
$al = strtolower($a->getDistance());
$bl = strtolower($b->getDistance());
if ($al == $bl) {
return 0;
}
if ($order == 'asc') {
return ($al < $bl) ? -1 : 1;
} else {
return ($al > $bl) ? -1 : 1;
}
}
The problem is the product collection is no longer paginated when this additional sort is applied.
Anyone knows how to fix this?
You are not doing it the right way, and there are no easy solutions. You need to use the database to do the sorting.
The _productCollection is not an array, it's an object that has references, the query at this point can still be updated, the pagination will be handled by the query to the database.
if you do a
Mage::log((string) $this->_productCollection->getSelect());
you will see the query in the logs
What you do is to load the products of the current page, add the distance on all products of the page, and create a new collection where you force your items in. So that collection's data is not coming from the database and only contains the elements of the current page.
Sorting using php is a bad idea, because if you have a lot of products it means you need to load them all from the database. That will be slow.
The solution
Calculate distance in the database directly by modifying the query.
You can edit the select query and do the distance calculation in the database
$this->_productCollection
->getSelect()
->columns("main.distance as distance")
Now you can add a sort on the product collection
$this->_productCollection->setOrder('distance');
The complicated part will be to write the equivalent of your getDistance method in mysql. In my example I assumed distance was in the database already.
Don't hesitate to print the query at various steps to understand what is going on.
Let's say I have these 2 models:
Order Model:
id
state (incomplete, complete)
Item Model:
id
order_id
type
is_worthy.
.
/**
* Returns the item's price according to its worthy
*/
public function getPriceAttribute()
{
return $this->is_worthy ? 100 : 10; // $
}
So far so good.
Now I want to summarize the price of the complete orders. So I'm doing this:
App\Item::whereHas('order', function ($query) {
$query->where('state', 'complete');
})->sum('price')
But the thing is, that I don't have in my items table the column price. Because the price attribute is generated in the Model.
So my question is, how can I summarize price of the complete orders?
There are 2 ways to do this:
1. Have PHP do all the work
$items = App\Item::whereHas('order', function ($query) {
$query->where('state', 'complete');
})->get();
$sum = $items->sum(function($item) {
return $item->price;
});
// In Laravel 5.4, you can replace the last line with $sum = $items->sum->price;
2. Have SQL do all the work
$items = App\Item::whereHas('order', function ($query) {
$query->where('state', 'complete');
})->select('*', DB::raw('IF(is_worthy, 100, 10) as price'))->sum('price');
Laravel
model
/**
* Returns the item's price according to its worthy
*/
public function getPriceAttribute()
{
return $this->is_worthy ? 100 : 10; // $
}
controller
retrun OrderItem::where('state', 'complete')->get()->sum('price');
I'm using Laravel 5.3 to build an API and I have an model for products. Whenever I retrieve a product, I want to retrieve the product's rating and it's recommended rate. I also have a model for reviews and products have many reviews.
$product = Product::where('slug', $slug)->with('reviews')->first()->toArray();
Rating is computed by looping through $product->reviews in the controller, adding up the score of each review, then dividing it by the total number of reviews.
if (count($product['reviews']) > 0) {
$i = 0;
$totalScore = 0;
foreach ($product['reviews'] as $review) {
$totalScore = $totalScore + $review['Rating'];
$i++;
}
$product['averageReviewRating'] = $totalScore / $i;
} else {
$product['averageReviewRating'] = null;
}
Recommended rate is computed with a SQL query.
$product['recommendedRate'] = round(DB::select("
select ( count ( if (Recommend = 1,1,NULL) ) / count(*)) * 100 as rate
from Review where PrintProduct_idPrintProduct = " . $product['idPrintProduct']
)[0]->rate);
This leaves me with $product['averageReviewRating'] and $product['recommendedRate'] with the data I want but seems very sloppy. I would like to just be able to do something similar to this below and have those two values assigned to each object of a collection, than access them via $product->averageReviewRating and $product->recommendedRate or even not include them in with and have those values eagerly assigned.
$product = Product::where('slug', $slug)->with(['Reviews', 'RecommendedRate', 'AverageReviewRating'])->first();
Anyone know a way to do this with ORM? I've looked high and low and have not found anything.
You can do this way
protected $appends = [
'reviews',
'recommendedRate',
'averageReviewRating'
];
public function getReviewsAttribute() {
return $this->reviews()->get();
}
public function getRecommendedRateAttribute() {
if (count($this->reviews) > 0) {
$i = 0;
$totalScore = 0;
foreach ($this->reviews as $review) {
$totalScore = $totalScore + $review->Rating;
$i++;
}
return $totalScore / $i;
} else {
return null;
}
}
public function getAverageReviewRatingAttribute() {
return round(DB::select("
select ( count ( if (Recommend = 1,1,NULL) ) / count(*)) * 100 as rate
from Review where PrintProduct_idPrintProduct = " . $this->idPrintProduct
)[0]->rate);
}
then simply call Product::where('slug', $slug)->first()->toArray();
P.S. This is just the way you can do, I might miss part of logic or names..
The way to get the sum in Laravel Eloquent is using the Aggregate sum and for average avg
https://laravel.com/docs/5.4/queries#aggregates
If you want to add a custom property to your model for that, you can use
class Product {
function __construct() {
$this->{'sum'} = DB::table('product')->sum();
$this->{'avg'} = DB::table('product')->avg();
}
}
edit: to set the attributes, you can use the built in function https://github.com/illuminate/database/blob/v4.2.17/Eloquent/Model.php#L2551
In SilverStripe I want to calculate the sum of only the first three DataObjects, to get a sub amount.
I tried the following but the function always returns the sum of all DataObjects and not only the first three.
public function getSubSum() {
$service = ServiceProvided::get()->filter(array(
'InvoiceObjectID' => $this->ID
));
$sum = $service->limit(3, 0)->sum('Sum');
return $sum;
}
How do I calculate the sum of only the first three DataObjects?
The SUM is calculated first, and and then the limit is applied after the aggregate sum function has already been calculated. In effect you're asking it to calculate the sum first, which returns a single row, and then limit that result to three rows.
To do this you will probably need to use a subquery like:
SELECT SUM("Sum") FROM (SELECT "Sum" FROM "ServiceProvided" WHERE ... LIMIT 3)
To execute custom SQL in SilverStripe you can use DB::query(). However, you need to be careful when doing this to avoid SQL injection. A simpler approach would just be to calculate the sum in PHP.
To calculate the sum in PHP use a for loop to go through each database row returned, and add the value to a variable.
public function getSubSum() {
$services = ServiceProvided::get()
->filter('InvoiceObjectID', $this->ID)
->limit(3, 0);
$sum = 0;
foreach($services as $service) {
$sum += $service->Sum;
};
return $sum;
}