Grouping a pivot table and getting the sum of pivot values - php

I'm trying to generate a monthly report in Laravel Livewire
I have a many to many relationship between books and orders with a pivot value for quantity of books in the order.
This is the database designer for the 3 tables :
These are the eloquent relationships in my App\Models\ ... .php
// In Order model
public function books(){
return $this->belongsToMany(Book::class)->withPivot('quantity');
}
// In Book model
public function orders(){
return $this->belongsToMany(Order::class)->withPivot('quantity')->as('orders');
}
Code generating the monthly report data:
public function generate($month)
{
// Gets count of orders and the sum of their totals from month by their status
$this->orderReport = Order::select(DB::raw('COUNT(*) as count,SUM(total_price) as total, status'))
->whereMonth('created_at', $month)
->groupBy('status')
->get()->keyBy('status');
// Gets number of all orders from the month
$this->totalOrders = $this->orderReport->sum('count');
// Gets number of orders and total spending of 10 users with most STATUS_SUCCESSFULL orders for the month
$this->orderUserReport = Order::select(DB::raw('COUNT(*) as count,SUM(total_price) as total, user_id'))
->whereMonth('created_at', $month)
->status(Order::STATUS_SUCCESSFULL)
->groupBy('user_id')
->orderBy('count', 'DESC')
->take(10)->get()->keyBy('user_id');
// THIS IS WHERE IM STUCK
$test = Order::whereMonth('created_at', $month)->status(Order::STATUS_SUCCESSFULL)->with('books')->get();
}
I would like to get a similar result for my products as i do for orders / users. Problem is the quantity of sold products is in the pivot table connecting Books with Orders.
So precisely what I need is the pivot table grouped by book_order.book_id with sums of book_order.quantity only where book_order.order_id is in orders with STATUS_SUCCESSFULL and whereMonth($month).
How would I go about attaining that data?
I had some trouble formulating this question in my mind so if anything is unclear please feel free to comment i'll clarify.
EDIT
$orders = Order::whereMonth('created_at', $month)->status(Order::STATUS_SUCCESSFULL)->with('books')->get();
foreach($orders as $order)
{
foreach($order->books as $book){
if(empty($this->productReport[$book->id])){
$this->productReport[$book->id] = $book->pivot->quantity;
break;
}
$this->productReport[$book->id] += $book->pivot->quantity;
}
}
ksort($this->productReport);
}
This code gives me the result I need but is ugly and inefficient, any way to reproduce this result with Eloquent or Query builder?
The above image is the result from the last snippet [book_id => quantity].

Related

How to get user order data by specific month in laravel?

I want to user orders data by specific month in a year.
Here is my order Table
id | user_id | price | quantity | total
Here is my order model
public function user() {
return $this->belongsTo(User::class);
}
Here is my user model
public function orders(){
return $this->hasMany(Order::class);
}
In a controller, by doing this I get all user orders
$user= User::with('orders')->get();
dd($user->orders);
Now, How can I get specific month user orders detail?
I want to show all user list with their order's total amount by month.
Try this to get users with their orders' total of September:
$users = User::withSum(['orders as september_total' => function ($query) {
$query->whereMonth('created_at', '9'); // September
}, 'total'])->get();
This will place a september_total attribute on your resulting models.
Take a look at these two queries:
$users = User::query()->with(['orders' => function($q){
$q->whereRaw('MONTH(orders.created_at) = 2');
}])->get();
// eager load all orders that have been created on month 2
$users = User::query()->whereHas('orders', function($q){
$q->whereRaw('MONTH(orders.created_at) = 2');
})->get();
// you'll get all users with orders on month 2 (no eager loading)
You can of course combine with (eager load) and whereHas (filter users) in order to "only get the users that have orders on month 2 AND eager load those orders".
Of course, feel free to use another column (e.g. ordered_at) or change the month if you need to.

Laravel get sum of related table's columns with eloquent

I'm having some trouble calculating the price of my carts with eloquent,
here are my tables:
cart_products:
- cart_id
- product_id
- quantity
products:
- price
One cart can have multiple cart_products, and each cart_products have one product associated
I'm making the request from the Cart Model, I'm trying to get the total price of the cart (cart_products.quantity * products.price).
Here is my query:
Cart::select('cart.*', \DB::raw('IFNULL(SUM(products.price*cart_products.quantity), 0) AS cart_price'))
->leftJoin('cart_products', 'cart.id', '=', 'cart_products.cart_id')
->join('products', 'cart_products.product_id', '=', 'products.id');
When I'm doing that, I do get the expected result but all the carts that doesn't contains product are excluded, I would like them to be included.
How could I include them ? Or is there a better way to do it (I saw withCount method but I couldn't make it work properly) ?
Another way would be to setup a virtual relation in your cart model and calculate your cart price like
class Cart extends Model
{
public function price()
{
return $this->hasOne(CartProducts::class, 'cart_id')
->join('products as p', 'product_id', '=', 'p.id')
->groupBy('cart_id')
->selectRaw('cart_id,IFNULL(SUM(products.price*cart_products.quantity), 0) as cart_price');
}
}
To get price data for your carts your can query as
Cart::with('price')->get()->sortByDesc('price.cart_price');
I finally managed to do it another way using raw SQL:
Cart::select('cart.*', \DB::raw('(SELECT IFNULL(SUM(products.price*cart_products.quantity), 0) from cart_products join products on products.id = cart_products.product_id where cart_products.cart_id = cart.id) AS cart_price'));
Thanks to you all for your help !

How do i calculate total amount for specific id in laravel?

I have two tables invoices and invoice_items. What I want is to sum amount for each invoice id and show in my view.
Invoice Table
id
invoice_items table
id
invoice_id
amount
I want to sum amount column for specific invoice id. I am new to laravel so how do I do that with laravel eloquent.
Because you are using one-to-many.
Solution 1:
You can use with and groupBy look like this:
$invoice = Invoice::with(['invoice_items' => function($query){
$query->groupBy('invoice_id')->select('invoice_id', DB::raw('SUM(amount) AS amount_sum'));
}])->get();
Solution 2:
Or you can use leftjoin and SUM look like this:
$invoice_query = InvoiceItem::groupBy('invoice_id')->select("invoice_id", DB::raw('SUM(amount) AS amount_sum'));
Invoice::leftjoin(DB::raw("({$invoice_query->toSql()}) AS ii"), 'ii.invoice_id', '=', 'invoices.id')
->mergeBindings($invoice_query->getQuery())
->select('invoices.*', 'amount_sum')
->get();
Solution 3:
Use accessor:
In your Invoice Model:
protected $appends = ['amount_sum'];
public function getAmountSumAttribute()
{
return $this->invoice_items()->where('invoice_id', $this->id)->sum('amount');
}
SO you can just get the sum of amounts:
Invoice::all()->toJSON();
Assuming in your Invoice model has the relation of this.
public function invoice_items() [
return $this->hasMany('App\Invoice_Items', 'invoice_id');
}
In your controller, your code should look like this.
Fetch all invoices with their invoice items total amount
$invoices = Invoice::with(['invoice_items' => function($query){
$query->sum('amount');
}])->get();
return view('invoice', compact('invoices'));
Fetch specific invoice with invoice items total amount
$invoice = Invoice::with(['invoice_items' => function($query){
$query->sum('amount');
}])->find($invoice_id);
return view('invoice', compact('invoices'));
Try with this one. I think this works
$result = \DB::table('Invoice')
->join('invoice_items', 'Invoice.id',
'=','invoice_items.invoice_id')->whereIn('Invoice.id', [1, 2, 3]))->sum('invoice_items.amount');
if somewhere i am wrong try with exchanging table name.

Get average with nested relationship belongs to in laravel

I want to ask for you. hopefully my problem will be quickly resolved.
I have 3 tables consist of :
ads
id
title_ads
transaction
id
transaction_id
id_ads
rating
id
transaction_id
rating_value
relationship :
ads to transaction is has many
transaction to rating is belongs to
I want to ask this, how to get avg rating value from table ads? I am confused, because I think to get avg rating maybe use hasManyThrought but in this case, There is relationship belongsTo. hwo to solved it? Thank you very much :)
Define a hasManyThrough relation in your Ad model
class Ad extends Model
{
/**
* Get all ratings of an Ad.
*/
public function ratings()
{
return $this->hasManyThrough('App\Rating', 'App\Transaction');
}
}
Now you can get the ratings in your controller with the Ad model and the ratings relation
public function getRatingsAverage()
{
// Fetch a single ad (or remove the find and get multiple)
$ad = Ad::where('id', 1)->with('ratings')->get();
// write average logic here...
// could be something like this:
$total_rating = 0;
foreach($ad->ratings as $rating) {
// loop through all the ratings of the ad and add the value to the total rating
$total_rating = $total_rating + $rating->value;
}
// divide the total rating by the amount of ratings to get the average
$average = $total_rating / count($ad->ratings);
}
To get to your ratings for each ad you can query the ads by eager loading the relationships. Then you can access the transactions and the ratings by looping through the $ads and add you average logic.
$ads = Ads::with('transaction.rating')->get();
//Load all ads with the transaction and rating relations
foreach($ads as $ad){
//All transactions from the ad
$transactions = $ad->transaction;
foreach($transactions as $transaction){
//The rating from the transaction
$rating = $transaction->rating;
}
}

Best practice to update or get ranks of players in Laravel

I've been starting a project where multiple players are in multiple leagues. They get points and in the end there is a ranking-table which displays the player with the most points.
So far so good, but I've got a problem getting the ranking of the players correctly.
The competitors I getting like that because competitors can be teams or players (of course teams OR players per league, not both in one league):
return $this->belongsToMany('App\User', 'competitors', 'league_id', 'competitors_id')
->where('competitors.competitors_type', 'App\User')
->withPivot('id', 'points', 'wins', 'lose', 'score', 'enemy_score')->withTimestamps();
I tried adding following method to the pivot-table-model Competitor:
public function getRankAttribute()
{
return $this->league->competitors()->where('points', '>=', $this->points)->count();
}
But the problem with this logic is, that I want to add more logic to the ranking like: Player A has same amount of points like Player B. But Player B is better than Player A because he has more wins.
Next I tried to give a rank in the query after multiple orderBy:
// $ranking is a relation or Eloquent Builder instance
// which has already got multiple orderBy() statements.
$query = null;
$baseQuery = null;
if($ranking instanceof Relation) {
$query = $ranking->getQuery();
$baseQuery = $ranking->getBaseQuery();
} else {
$query = $ranking;
$baseQuery = $ranking->getQuery();
}
// Set the rank offset
$offset = (int) $baseQuery->offset;
DB::statement(DB::raw("set #rank={$offset}"));
// Adjust SELECT clause to contain the rank
if ( ! count($baseQuery->columns)) $query->select($columns);
$query->addSelect([DB::raw('#rank:=#rank+1 as rank')]);
// Return the object again
return $ranking;
This doesn't work as well, because the sorting is done AFTER the rank was given to the entry. So I get the increasing number of the row but not the rank. In my example the last player which joins the league gets the highest "rank".
Now I'm thinking of a scheduled task which will update the ranks of the players every 5 minutes or so. But is this really best practice? What do you think? How should I do this?
I'm using a MYSQL database and Laravel 5.2

Categories