Get average with nested relationship belongs to in laravel - php

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;
}
}

Related

Grouping a pivot table and getting the sum of pivot values

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].

How to get all books from another table when using many to many relationship Laravel / Eloquent / Query builder

Let me explain the scenario.
i have tables:
competitions
id title body
then
books
id name user_id
then i have a pivot table to store participants
so
participants
id competition_id user_id
Now i have set some relationships for these models.
Competition model
public function participants()
{
return $this->belongsToMany('App\User');
}
User model
public function participatedCompetitions()
{
return $this->belongsToMany('App\Competition');
}
Now i am fetching one single competition and in that same query i need to fetch a list of books of the participants of that competition.
So how can i achieve this.
Thanks.
Here is how you get all the books:
$userIds = $competition->participant()->get()->pluck('id');
// $userIds is your collection of User Ids that is participant in that compititions.
$books = Book::whereIn('user_id',$userIds)->get();

Load laravel eloquent model withCount of related model

Given I have two eloquent models: Booking and Customer.
When I list all Bookings along with the respective Customer, I also want to show the amount of Bookings the respective customer has in total (count of this Booking + all other bookings).
Example output:
Booking1: Customer A (has 20 Bookings total)
Booking2: Customer B (has 10 Booking total)
Booking3: Customer C (VIP: has 100 Bookings total)
In order to avoid the n+1 problem (one additional query per booking while showing this), I'd like to eager load the bookingsCount for the Customer.
The relations are:
Booking: public function customer() { return $this->belongsTo(Customer::class) }
Customer: public function bookings() { return $this->hasMany(Booking::class) }
Example for querying the Bookings with eager loading
Working, but without eager loading of the bookingsCount:
Booking::whereNotCancelled()->with('customer')->get();
Not working:
Booking::whereNotCancelled()->with('customer')->withCount('customer.bookings')->get();
I learned, that you cannot use withCount on fields of related models, but you can create a hasManyThrough relation and call withCount on that relation, e.g. Booking::whereNotCancelled()->withCount('customerBookings'); (see accepted answer here).
However: This doesn't work. I guess, it's because a Booking belongsTo a Customer and a Customer hasMany Bookings.
Here's the hasManyThrough relation of class Booking
public function customerBookings()
{
// return the bookings of this booking's customer
return $this->hasManyThrough(Booking::class, Customer::class);
}
Here's the failing test for hasManyThrough
/**
* #test
*/
public function it_has_a_relationship_to_the_customers_bookings()
{
// Given we have a booking
$booking = factory(Booking::class)->create();
// And this booking's customer has other bookings
$other = factory(Booking::class,2)->create(['customer_id' => $booking->customer->id]);
// Then we expect the booking to query all bookings of the customer
$this->assertEquals(3, Booking::find($booking->id)->customerBookings()->count());
}
Reported error
no such column: customers.booking_id (SQL: select count(*) as aggregate from "bookings" inner join "customers" on "customers"."id" = "bookings"."customer_id" where "customers"."booking_id" = efe51792-2e9a-4ec0-ae9b-a52f33167b66)
No surprise. There is no such column customer.booking_id.
The Question
Is the intended behavior even possible in this case? If so, how would I eager load the booking's customer's total count of bookings?
Try this:
public function customer() {
return $this->belongsTo(Customer::class)->withCount('bookings');
}
Booking::whereNotCancelled()->with('customer')->get();

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

Counting related rows in a child table

I have been trying to do some queries and getting a count on related tables using eloquent.
Tables:
requests
contact (belongs to requests)
history (belongs to contact)
As such X number of requests each have Y number of contacts which in term each have Z number of histories
Using sql I can do something like this to get all the counts.
SELECT
id,
(
SELECT count(contact.id)
FROM contact
WHERE contact.requests_id = requests.id
) AS n_contact,
(
SELECT count(history.id)
FROM contact INNER JOIN history ON (history.contact_id = contact.id)
WHERE contact.requests_id = requests.id
) AS n_history
FROM requests;
But I am a bit lost when using eloquent to build queries. If for instance I was selecting all contacts for a given request at what point would I join/count the history? Or do I need to add in some accessor's into the relevant Models for these 3 tables?
public function getAllContacts($id) {
return Requests::where('requests.id', '=', $id)
->join('requests', 'contact.requests_id', '=', 'requests.id')
->select('contact.*', 'requests.name');
->get();
}
Thanks in advance for any help.
You can use helper relation for this, if you'd like to use Eloquent instead of manual joins:
// Request model
public function contactsCount()
{
return $this->hasOne('Contact')->selectRaw('request_id, count(*) as aggregate')->groupBy('request_id');
}
public function getContactsCountAttribute()
{
if ( ! array_key_exists('contactsCount', $this->relations)) $this->load('contactsCount');
return $this->getRelation('contactsCount')->aggregate;
}
The same would go for Contact model towards History model.
For counting far relation (Request -> History) you can use hasManyThrough relation with a little adjustment.
This way you can eager load those aggregates for multiple models without n+1 issue, nice and easy:
$requests = Request::with('contactsCount', 'contacts.historyCount')->get();
// 1 query for reuqests, 1 query for contacts count and 2 queries for contacts.historyCount
// example output
$requests->first()->contactsCount; // 17
$requests->first()->contacts->first()->historyCount; // 5
/* Make Relation in the Request Model */
public function contacts()
{
return $this->hasMany('App\Model\Contact', 'request_id', 'id');
}
/* use withCount() to get the total numner of contacts */
public function getAllContacts($id) {
return Requests::with('contacts')
->withCount('contacts')
->find($id);
}

Categories