In the application, we have a feature where it will display all the years as tab. Each tab has amount(where the amount is the summarized) but at the same time it is have data.
Scenario
Just to give a background about the application, it is a car seller
Imagine you are the customer, you started to use this application way back 2019. You clicked this module to check the summarized of each year
Let's assume we have 2500 data for each year.
In current year I want to display the total amount of all previous years
2023 tab is the active, from 2019 - 2022 you have sold 300,000 each year
In getting the previous amount, I used the DB raw sum function to get the summation.
Question: Does DB raw sum function lessen the heavy query to get the summation?
You can accomplish this with eloquent, without having to load all of the inner relationship data.
Normally programmers do the following:
Model:
public function children()
{
return $this->hasMany(Children::class);
}
In the Controller:
$models = Model::with('children')->get();
Then they load the data normally in blade and count:
#foreach($models as $model)
<p>{{ $model->children->count() }}</p>
#endforeach
Instead you can do the following:
In the Controller:
$models = Model::withCount(['children'])->get();
In the blade you load the data with the count:
#foreach($models as $model)
<p>{{ $model->children_count }}</p>
#endforeach
The withCount method will place a {relation}_count attribute on the resulting models
Use Barry DebugBar to test out the speed with heavy data, you will notice that doing it this way will reduce heavily the time needed to load the relationship, since you are not loading all of the inner relationship, rather you are just counting the inner relationship and displaying it.
For withCount Reference: https://laravel.com/docs/9.x/eloquent-relationships#aggregating-related-models
For withSum Reference: https://laravel.com/docs/9.x/eloquent-relationships#other-aggregate-functions
You can accomplish the same thing using withSum() and you can do this for deeper level of relationships as well.
Create and maintain a summary table, with year, and the sum for that year.
Working from that table, the query would be much faster, even if you had to do multiple summing.
Or, that table could have the cumulative amount since day-1. Then, the sum for "prior to 2023" is "cumulative through 2022" minus the "cumulative through 2018".
Summary Tables
Related
I built an application to keep track of the sales. In my customers view, I want a column with total sales per customer, but as the customer base is growing, the list is loading more and more slowly. This is what I did (simplified):
Controller:
$customers = App\Customer::get();
View:
#foreach ($customers as $customer)
{{ $customer->name }} {{ $customer->totalSales() }}
#endforeach
Model:
public function totalSales()
{
$invoiceLines = InvoiceLine::whereHas('invoice', function ($query) {
$query->where('customer_id', $this->id);
})->get();
$sales = $invoiceLines->reduce(function ($carry, $invoiceLine) {
return $carry + ($invoiceLine->quantity * $invoiceLine->pricePerUnit);
});
return $sales ?: 0;
}
What would be the best way to make this view/report more "scalable"?
I have been thinking in creating command that calculates the total sales per customer overnight and put the result in the customer table, but that means that the numbers won't be accurate during the day...
this seems like a very interesting problem.
I have been thinking in creating command that calculates the total
sales per customer overnight and put the result in the customer table
this is a good option.
but that means that the numbers won't be accurate during the day...
You can keep the numbers accurate by doing the following:
by incrementing the customers table count every time a invoice is made.
This should work for total sales.
Make sure you have an index on the customer_id column.
Search for ways to do a "SQL SUM on 2 columns using laravel".
Try and find some way to do "SQL SUM on 2 with a GROUP BY. Doing this will replace #2
A good way to speed up your application is to avoid making calls to the database in a loop. That is what #3 is suggesting (the loop in this case is the #foreach in your View and the database call is the InvoiceLine::...->get(); in totalSales()
Adding the index (if missing) and reducing the # of calls to the DB will yield the best results.
I have limited knowledge of Laravel but one way to do this with raw SQL would be:
SELECT c.name, ts.totalSales
FROM customer c
INNER JOIN (
SELECT customer_id, SUM(quantity * pricePerUnit) as totalSales
FROM invoice
GROUP BY customer_id
) ts ON c.id = ts.customer_id
You can see how all the data you're trying to print is pulled at once? I assume you'd want to try and write that using Laravel's Eloquent thingy.
Based on the answers above, I came to the following solution:
I created an event: App\Events\InvoiceSaved which is dispatched every time an invoice is "touched" (created, updated or deleted). The App\Events\InvoiceSaved event will calculate the total sales for the customer and add the result to the customers table (extra field total_sales). Now I can just query my customers table and need to query a relation. The loading time dropped from 7 seconds to 0.5 second!
Quick question to which I haven't been able to find a solution for myself, nor by googling it.
On my dashboard view I show my payment plans, simply by doing $plans = Plan::all() and I return that to the view. Nothing fancy there. But, on my actual dashboard view, I want to show the total amount in dollars from the transactions that have been done. So on my view, I do the following (stripped example):
#foreach($plans as $plan)
{{ $plan->paidAmount() }}
#endforeach
Which is using the following method from my model:
private function paidAmount()
{
return $this->transactions->sum('amount');
}
Which basically just grabs the total amount of all related transactions to that plan, and displays it on the page. However, this generates a new query for each of my plans. Let's say I have 5 plans, it will generate 5 additional queries for each amount I need to be displayed.
Same goes for the remaining amount that I want to display:
private function paidAmount()
{
return $this->amount - $this->paidAmount();
}
Which will generate an additional 5 queries. So for a simple table in which I display some information,a whopping 11 queries are being executed.
Is there any way that I can include the paidAmount and remainingAmount in my original query? I know there should be a way, I just haven't been able to find it yet. So just something along the lines of:
$plans = Plan::with('paidAmount')->get();
You can use sub queries inside your main query Like this to calculate the sum
Like this -
$plans = Plan::all('*', DB::RAW("(SELECT SUM(amount) FROM transactions WHERE plan_id=plans.id ) as amount") );
This way you will already have sum when rendering in views
I'm trying to display a list of all the users and how many posts they've each made this week, and how many they've made last week, inside of a form
I've used a hasMany relation between the two tables and the relationship it's self is working.
return $this->hasMany('App\applications', 'user_id');
inside of the view what I have that's displaying the post count is
{{$user->applications->count()}}
The main thing I'm stuck on is the SQL Query or using Carbon inside of the controller function to achieve this.
If anyone has done this before your help would be greatly appreciated!
Thank you.
Since you want to count posts for many users, eager load the data and use withCount():
User::withCount(['applications' => function($q) {
$q->whereBetween('created_at', [Carbon::now()->startOfWeek(), Carbon::now()]);
}])->get();
If you want to count the number of results from a relationship without actually loading them you may use the withCount method, which will place a {relation}_count column on your resulting models.
https://laravel.com/docs/5.4/eloquent-relationships#counting-related-models
I've been working on a shopping cart application and now I've come to the following issue..
There is a User, a Product and a Cart object.
The Cart table only contains the following columns: id, user_id, product_id and timestamps.
The UserModel hasMany Carts (because a user can store multiple products).
The CartModel belongsTo a User and CartModel hasMany Products.
Now to calculate the total products I can just call: Auth::user()->cart()->count().
My question is: How can I get the SUM() of prices (a column of product) of the products in cart by this User?
I would like to accomplish this with Eloquent and not by using a query (mainly because I believe it is a lot cleaner).
Auth::user()->products->sum('price');
The documentation is a little light for some of the Collection methods but all the query builder aggregates are seemingly available besides avg() that can be found at http://laravel.com/docs/queries#aggregates.
this is not your answer but is for those come here searching solution for another problem.
I wanted to get sum of a column of related table conditionally.
In my database Deals has many Activities
I wanted to get the sum of the "amount_total" from Activities table where activities.deal_id = deal.id and activities.status = paid
so i did this.
$query->withCount([
'activity AS paid_sum' => function ($query) {
$query->select(DB::raw("SUM(amount_total) as paidsum"))->where('status', 'paid');
}
]);
it returns
"paid_sum_count" => "320.00"
in Deals attribute.
This it now the sum which i wanted to get not the count.
I tried doing something similar, which took me a lot of time before I could figure out the collect() function. So you can have something this way:
collect($items)->sum('amount');
This will give you the sum total of all the items.
you can do it using eloquent easily like this
$sum = Model::sum('sum_field');
its will return a sum of fields,
if apply condition on it that is also simple
$sum = Model::where('status', 'paid')->sum('sum_field');
You can pass this as UserModel attribute. Add this code to UserModel.
public function getProductPriceAttribute(){
return $this->cart()->products()->sum('price');
}
I assume something like this:
UserModel has a one to many relationship with a CartModel named cart
CartModel has a one to many relationship with ProductModel named products
And then you can get sum price of the product like this:
Auth::user()->product_price
Since version 8, there is a withSum method on Eloquent, so you could use this.
Auth::user()->withSum('products', 'price')->products_sum_price;
This won't load all products into memory and then sum it up with collection method. Rather it will generate a sub query for the database, so it's quicker and uses less memory.
Also using query builder
DB::table("rates")->get()->sum("rate_value")
To get summation of all rate value inside table rates.
To get summation of user products.
DB::table("users")->get()->sum("products")
For people who just want to quickly display the total sum of the values in a column to the blade view, you can do this:
{{ \App\Models\ModelNameHere::sum('column_name') }}
You can also do it for averages:
{{ \App\Models\ModelNameHere::avg('column_name') }}
Min:
{{ \App\Models\ModelNameHere::min('column_name') }}
Max:
{{ \App\Models\ModelNameHere::max('column_name') }}
To get the Count of a table:
{{ \App\Models\ModelNameHere::count() }}
I am not sure on the correct logic to use in the following situation. This situation will come up several times in my app and I would assume, it may be experienced by others as well.
In Yii I have a loadModel function that is returning a CActiveRecord.
The function is as follows:
$model=Product::model()->with('performance','subcategory','sponsor')->findByPk($id);
As you can see, I am eagerly calling 3 relationships. One of those relationships - performance -
is a HAS_MANY relationship and relates to user reviews of the product.
So for product x, there may be 100 reviews all with different dates and scores.
What I am attempting to do is:
Pull all performance data (so all 100 reviews)
Pull the most recent performance data score (as long as it was submitted within the last 120 days)
The confusion in logic is this.
Should I create a function in my model class that goes through $model->performance to get the most recent information (#2).
Should I create an entirely separate relation just for this refined piece of data.
This most recent review data will be needed for each product in the CListView and the ListView needs to be sortable by this data. So, it seems as though it needs to be directly attached to the product active record that is being passed in to the view.
From both a performance standpoint and logic standpoint, how should I handle this?
As an aside, here is the current code I was trying to use that is not functioning:
public function scopes()
{
return array(
'recentPerf'=>array(
'condition'=>'perf_date > ' . strtotime('-120 days', strtotime(new CDbExpression('NOW()'))),
'order'=>'perf_date DESC',
'limit'=>1,
)
);
}
Thank you in advance for any suggestions!
Uday's answer got the scope working - now how is the correct way to use the scope?
Should I pass this amount in with the current model?
i.e. can I attach this to the:
$model=Product::model()->with('performance','subcategory','sponsor')->findByPk($id);
?
How I tested it to make sure it worked was:
$maxPerformance = ProdPerformance::model()->recentPerf()->find();
and am then passing that variable to the view. This seems like a very 'unclean' way of handling this situation. Should it instead be passed with the original $model variable?
I am not sure but possibly following line has a catch
'condition'=>'perf_date > ' . strtotime('-120 days', strtotime(new CDbExpression('NOW()'))),
condition is the data that will be sent to mysql so the date string should be in MySQL format not in PHP, try this
'condition'=>'perf_date > CURRENT_DATE - INTERVAL 120 DAYS',