Laravel Eloquent match to all parameters of join - laravel

I have a table that houses hotel ids and amenity ids. When a user chooses amenities i need to only pull hotels that have ALL of the chosen amenities. As of now it gets me all hotels that have at least one. How can i change this builder query to handle it so it does not include hotels that do not have all of them.
$builder = Hotels::query();
$builder->select('hotels.id','hotels'.'hotels_name')
$request_amenities = $request->amenities;
$builder->join('amenities_hotels', function ($join) use($request_amenities) {
$join->on('amenities_hotels.hotel_id', '=', 'hotel.id')
->whereIn('amenities_hotels.amenities_id', $request_amenities);
});

It's something like WhereAll you need ...
you have to add whereHas, for every Item in your array.
$builder = Hotels::query();
$builder->select('hotels.id', 'hotels' . 'hotels_name');
$request_amenities = $request->amenities;
if($request_amenities!=null)
{
for ($i = 0; $i < $this->count($request_amenities); $i++) {
$builder = $builder->whereHas('amenitiesHotels', function ($query) use ($i, $request_amenities) {
$query->where('amenities_hotels.amenities_id', $request_amenities[$i]);
});
}
see more about where in all in this question

Hotels model.
// ...
public function amenities(): BelongsToMany
{
return $this->belongsToMany(
Amenities::class,
"amenities_hotels"
);
}
// ...
Query to run.
Hotels::select(['hotels.id','hotels'.'hotels_name'])
->whereHas('amenities', function ($query, $request_amenities) {
$query->whereIn('amenities.id', $request_amenities);
}, '=', count($request_amenities))->get();
The above query tells Laravel to load the hotels that have count($request_amenities) amenities relation records when the amenities relation records are filtered by the given array of IDs.
Resource: "Where Has All" Functionality in Laravel

Related

API filtering with pivot table

I'm using laravel 7 and i've made an API to fetch data with a VUE component.
I want to filter restaurants based on their type of food. So if i query "pizza" i want to show only restaurants that make pizza.
getTypes is a many to many relation.
public function restaurants(Request $request)
{
$ricerca = $request->input('query');
$users = Restaurant::with(array('getTypes' => function ($query) use ($ricerca) {
$query->where('name', "LIKE", "%" . $ricerca . "%");
}))->get();
return response()->json($users);
}
I tried this, but this way i don't have access to other genres, if a restaurant make pizza and meat, i don't see meat anymore.
Any advice?
create a many to many relation with restaurant and pizza.
get an array which restaurant make pize,
$array = [];
or simple var
$array = $request('query');
and put this array in query.
$data = Project::whereHas('restorant_pizza', function ($q) use ($array) {
$q->whereIn('restorant_pizza.pizza_id', $array);
})->get();
I think this idea will help you

How to get count and avg of results in Laravel 5.8

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

Group collection by month and type user

I have a collection of invoices and to make a chart from it I want to group them in months and in those months a subdivision by the type of the user.
If I group by month or group by user type separately they work but if I want to group them by month and in that month by the user type it's not working.
Vue
const invoices = await this.$http.get('/invoices/stats', {
params: {
with: 'user,customer,extension',
start: this.startDate,
end: this.endDate,
}
})
Laravel
public function stats(Request $request)
{
$result = Invoice::with(explode(',', $request->with))
->scopes(['period'])
->get()
->groupBy(function ($q) {
return Carbon::parse($q->date)->format('m');
})
->groupBy('user.type');
return $result;
}
From what i know, i do not think you can do multiple groupby on a collection. Just did a quick test and it does not work on my side.
I think you have 2 options:
Do a query for each month, group each result collection by user type then you will merge the collections (basically). but you will do more queries.
If you just need counts to display charts you can do all of that with multiple group by on an SQL query, outside Eloquent.
It would look like :
Select count(*), YEAR(i.date), MONTH(i.date), u.type
from invoice i left join user u on i.user_id = u.id
group by MONTH(i.date), YEAR(i.date), u.type
Keep in mind the kind of data you need is a bit out of scope regarding what Eloquent is meant to be used for.
This is my solution:
// group by user types
$user_types = Invoice::with(explode(',', $request->with))
->scopes(['byMonth'])
->orderBy('date')
->get()
->groupBy('user.type');
// group the user types by month
// (double groupBy is not possible in 1 query)
foreach ($user_types as $key => $value) {
$user_types_by_month[$key] = $value->groupBy(function ($q) {
return Carbon::parse($q->date)->format('m');
});
}
// calculate the total by month by user types and replace the array with the total
foreach ($user_types_by_month as $i => $types) {
foreach ($types as $j => $months) {
$total = 0;
foreach ($months as $m) {
$total += $m->total;
}
$user_types_by_month[$i][$j] = $total;
}
}
return $user_types_by_month;

Sum of Items field in collection of collections

I'm trying to see if it's possible, but I would like to get the sum of a field in an item in a collection of collections.
I have the following in my controller:
$prefiltered_contacts = Contact::with(['donations' => function ($query) use ($request) {
$query->whereMonth('date_received', $request->month)
->whereYear('date_received', $request->year);
}])->get();
$contacts = $prefiltered_contacts ->filter(function ($contact) {
return $contact->donations->isNotEmpty();
});
My donation class has the following:
public function monetary_donations(){
return $this->hasMany('App\Payments_Distribution', 'donation_id','id');
}
Now the last part of this is that in the Payments_Distribution class, there is a field titled amount.
If I was coming directly from the Donation model, I would access the sum of the monetary donations as $donation->monetary_donations->sum('amount'); and I would receive the sum. But how would I go about doing this from the Contact model? Or is that even possible given that it would need to go through a collection of donations to get to the collection of monetary_donations? I'm trying to get a report of all Contacts donations (and monetary donations) and output a subtotal of the monetary_donations for that specific period.
Sum accepts a closure as the argument. So you could do something like this:
$sum = $donations->sum(function ($donation) {
return $donation->monetary_donations->sum('amount');
});
Or 1 level higher (from $contacts):
$sum = $contacts->sum(function ($contact) {
return $contact->donations->sum(function ($donation) {
return $donation->monetary_donations->sum('amount');
});
});
Edit:
I would also recommend eager loading your relationships and filtering out contacts without donations with SQL rather than collections:
$contacts = Contact::with(['donations' => function ($query) use ($request) {
$query
->with('monetary_donations')
->whereMonth('date_received', $request->month)
->whereYear('date_received', $request->year);
}])
->whereHas('donations') // Filter out those without donations with SQL
->get();

Filtering eloquent relationship many to many query

I have
Users, Departments, Positions, table.
User belongs to many department
Department has many positions
User belongs to many positions
I want a query that would give a single department with list of users and their position in that department.
$department = DepartmentView::with(['users.positions' => function($query) {
// do something?
}])->where('tag', $tag)
->get();
I also get the department through tag, and not ID. Position table does not have tag column, only department ID.
--
I could make it work by defining static Id, however the problem is I get the department via tag.
public function getDepartmentByTag($tag) {
$departmentId = 1; FOR TESTING PURPOSE, IT WORKS
$department = DepartmentView::with(['users.positions' => function($query) use($departmentId) {
$query->whereHas('department', function($query) use($departmentId) {
$query->where('departmentId', $departmentId);
});
}])->where('tag', $tag)
->get();
}
I want to only get the user position that is in the department
DepartmentView::with(['users.positions' => function($q) use($tag) {
$q->whereHas('department', function($q) use($tag) {
$q->where('tag', $tag);
});
}])
->get();

Resources