I have a Part entity that has a many-to-many relationship with Order and Project. The pivot tables contains a quantity field which might have a positive or negative value, and by adding the sums of both pivot tables, for a specific part, I get the current stock.
I am able to do this, but get the N+1 problem. And I am having a hard time figuring out how I can do it with eager loading. I've read this post, but I don't understand how to adapt it to my needs.
So I am looking for a way to provide a eager loadable stock property on the Part model, suggestions to how I can accomplish this?
I was able to figure it out with the help of this post. This is what I did:
Part model
Create two relationship orderPartsCount and projectPartsCount, add attribute calculatedStock to sum them and provide an easy way of retrieving.
public function orderPartsCount()
{
$a = $this->orders();
$a1 = $a->selectRaw($a->getForeignKey() . ', sum(count) as stock')
->where('done', '>', 0)
->groupBy($a->getForeignKey()
);
return $a1;
}
public function projectPartsCount()
{
$b = $this->projects();
$b1 = $b->selectRaw($b->getForeignKey() . ', sum(count) as stock')
->where('status', '>', 0)
->groupBy($b->getForeignKey()
);
return $b1;
}
public function getCalculatedStockAttribute()
{
$orders = $this->orderPartsCount->first() ? $this->orderPartsCount->first()->stock : 0;
$projects = $this->projectPartsCount->first() ? $this->projectPartsCount->first()->stock : 0;
// invert project parts, since they are listed as positive counts but shall reduce the stock
return $orders + ( $projects * -1);
}
Part controller
Eager load orderPartsCount and projectPartsCount in the controller.
public function index()
{
return View::make('parts.index', [
'parts' => Part::with('category', 'location', 'orderPartsCount', 'projectPartsCount')
->orderBy('description_no')
->paginate(100)
]);
}
Related
I have made a list of all items using many to many relationship.
With a pivot I make sure that there is an order.
Now I am trying to remove 1 item from the list.
And the order numbers must also move, this does not work.
From the view I provide the $id of the item and the order number.
public function removefromlist($id, $nummer)
{
$user = User::find(auth()->user()->id);
$user->manytomany()->detach($id);
$nummerlist = $user->manytomany()->count();
for($i = $nummer + 1;$i <= $nummerlist;$i++){
$testt = $user->manytomany()->where('nummer', $i);
$user->manytomany()->updateExistingPivot($testt->first()->item_id , ['nummer' => $i -1]);
}
return view('welcome')->with('itemslist', $user->manytomany);
}
How can I ensure that when I delete an item, the other move up?
You can try the following
public function removefromlist($id, $nummer)
{
$user = auth()->user();
$user->manytomany()->detach($id);
$user->manytoMany()
->where('nummer', '>', $nummer)
->get()
->each(function($item) use($user) {
$user->manytomany()
->updateExistingPivot(
$item->id,
['nummer' => $item->pivot->nummer - 1]
);
});
return view('welcome')->with('itemslist', $user->manytomany);
}
The models I have are : Invoice - Credit_note - Payment
An Invoice hasMany Credit_notes
Each Credit_note has a single Payment
A Payment is not directly related to a Credit_note
What I need to do is to calculate the sum of the Payments(amount) of the Credit_notes of an Invoice.
With the relations I currently have in place, I can load all the Credit_notes (with their Payments) of an Invoice.
Credit_note::with(['payment'])->where('invoice_id', $i->id)
I could then sum the amounts by hand... but, is there a way to directly return the sum with the query ?
I tried to add a ->sum('payment.amount') or something similar, with no luck.
Thanks.
$sum = Credit_note::with([
'payments' => function ($query) {
$query->selectRaw('payment_id, SUM(payments.amount) AS cn_total')->groupBy('payment_id');
},
])
->where('invoice_id', $i->id)->get()->pluck('payments.cn_total')->sum();
Solution :
$sum = Credit_note::with(['payment:id,amount'])
->where('invoice_id', $i->id)
->get()
->pluck('payment.amount')
->sum();
Inside Credit_note model make this function
public function sum_of_payment(){
$result=0;
$paiments=$this->payment()->get();
foreach($paiments as $paiment){
$result+=$paiment->amount;
}
return $result;
}
then you can get SUM like this
$sum=Credit_note::with(['payment'])->where('invoice_id', $i->id)->first()->sum_of_payment();
I need to get multiple results from different queries on one table.
For example I need to get Count, Sum, Average of one table. Should I do like this or is there a shorter way?
public function index()
{
$count = Patient::all()->count();
$dateCount = Patient::where('date', date("Y-m-d"))->count();
$loanAmount = DB::table('patients')->sum('loan_amount');
$payAmount = DB::table('patients')->sum('pay_amount');
return view('index', compact('count','dateCount','loanAmount' ,'payAmount'));
}
If you see All queries are for one table to get specific results, So basically is there a short way to get these results not by single queries for each?
You can do this by DB query as below :
$data=\DB::table('patients')
->selectRaw('count(id) as count,sum(loan_amount) as loanAmount,sum(pay_amount) as payAmount,sum(case when date = "'.date("Y-m-d").'" then 1 else 0 end) AS dateCount')
->first();
You can also do this using eloquent but it will return you collection.
$data=Patient::selectRaw('count(id) as count,sum(loan_amount) as loanAmount,sum(pay_amount) as payAmount,sum(case when date = "'.date("Y-m-d").'" then 1 else 0 end) AS dateCount')
->first();
You can do something like this.
public function index()
{
$patient = Patient::all();
$count = $patient->count();
$dateCount = Patient::today()->count();
$loanAmount = $patient->sum('loan_amount');
$payAmount = $patient->sum('pay_amount');
return view('index', compact('count','dateCount','loanAmount' ,'payAmount'));
}
Also you can create scope in your patient model:
public function scopeToday($query) {
return $query->where('date', date("Y-m-d"));
}
I have TypeOfVehicle model that has many Vehicle.
How can I group all vehicles by type of vehicle name with counts to get something like this
[
'Sedan' => 10,
'SUV' => 25,
'Crossover' => 5
]
I assume you have eloquent relation type in Vehicle model, e.g.:
public function type()
{
return $this->belongsTo('\App\TypeOfVehicle');
}
So, you can get you data this way:
$result = Vehicle::select('vehicle_type_id', DB::raw('count(vehicle_type_id) as cnt'))
->with('type') // with your type relation
->groupBy('vehicle_type_id') // group by type of vehicle
->get() // get collection from database
->pluck('cnt', 'type.name') // get only needful data
->toArray(); // cast to array if necessary
You can use
$counts = DB::table('tablename')
->select('TypeOfVehicle', DB::raw('count(*) as total'))
->groupBy('TypeOfVehicle')
->get();
Counting Related Models
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.
$typeOfVehicles = App\TypeOfVehicle::withCount('vehicles')->get();
foreach ($typeOfVehicles as $typeOfVehicle) {
echo $typeOfVehicle->vehicles_count;
}
Try this it gives you your solution
$vehicle=Vehicle::all();
$grouped = $vehicle->groupBy(function ($item, $key) {
return substr($item['that_coloumn_name_like_type'], 0);
});
$groupCount = $grouped->map(function ($item, $key) {
return collect($item)->count();
});
//dd($groupCount); to check it work correctly
I have the below query returning all available fiction books from all libraries:
$results = Library::with('category.books')
->whereHas('category.books', function($query) {
$query->where('available', 1);
})
->whereHas('category', function($query) {
$query->where('name', 'fiction');
})
->get();
Now, what is the best way to get the total number of books and the average rating per book (book has field rating), per library?
I assume I have to create a collection of these results somehow, apply a custom function.
You can get all the libraries while made changes to them using map() function.
You can count number of items in the collection using count() function.
You can get average by a property of items in the collection using average() function.
$libraries = $results->map(function($library) {
// All the books of the library
$books = $library->category->flatMap->books;
// set book count and average rating to the library object
$library->book_count = $books->count();
$library->average_rating = $books->average('rating');
return $library;
});
Now every library object in this $libraries collection has those new two properties called book_count and average_rating.
This could be achieved in a number of ways. From the results that you have:
$libraries = Library::with('category.books')
->whereHas('category.books', function($query) {
$query->where('available', 1);
})
->whereHas('category', function($query) {
$query->where('name', 'fiction');
})
->get();
$books = $libraries->map(function ($library) {
return $library->category->books;
})
->collapse()
->filter(); // This is an optional step to remove NULL books, if there are any.
$count = $books->count();
$avg = $books->avg('rating'); // Or $books->average('rating');
But I think a better approach could be calculating the count and average from a query starting from Book, let's define a relationship named category in the model class if you haven't have one:
class Book
{
// This could be a different type of relationships.
public function category()
{
return $this->belongsTo('category');
}
}
Then write a query.
$query = Book::where('available', 1)
->whereHas('category', function($query) {
$query->where('name', 'fiction');
});
$count = $query->count();
$avg = $query->avg(); // Or $query->average();
You just need to define the relation in library model for count average of book and rating like below. I am expecting that you have a column library_id in books table.
In your library model.
public function TotalBooks()
{
return $this->hasMany('App\Books', 'library_id')
->selectRaw('SUM(id) as total_book');
}
public function AvgRating()
{
return $this->hasMany('App\Books', 'library_id')
->selectRaw('AVG(rating) as avg_arting');
}
$data = Library::with('TotalBooks')->with('AvgRating'); // Get record