Laravel - Query 3 tables based on relationships - php

I am wanting to query 3 seperate tables at once. The main one is Criteria, the two has Many tables, linked by criteria_id are Bedrooms and Properties.
A Criteria can have many Bedrooms and many Properties:
Criteria Model
public function bedrooms()
{
return $this->hasMany('Bedroom');
}
public function properties()
{
return $this->hasMany('Property');
}
I'm unsure if this is possible, but I want to query both of these tables, as well as Criteria to see which criteria has a certain bedroom and a certain property type. Please note, there could be multiple bedrooms and properties stored for each criteria_id.
So far, my query is:
$criterias = Criteria::select('id')
->where('min', '<=', Input::get('single_value'))
->lists('id');
My only logical explanation is -
Get all Criteria where Min <= Value and Criteria.Bedrooms = 1 and
Criteria.Properties = 5.
As if to loop through and see if a criteria has a bedroom / property with that value stored.
Many thanks for your help.

If you are looking for only criteria that meet your constraints, you can use whereHas() on the Bedroom and Property relationships. Then add your where() and lists().
$bedroom_id = '1';
$property_id = '5';
$criteria = Criteria::whereHas('bedrooms', function($q) use ($bedroom_id) {
$q->where('bedroom', $bedroom_id);
})->whereHas('properties', function($q) use ($property_id) {
$q->where('property', $property_id);
})->where('min', '<=', Input::get('single_value'))->lists('id');
Criteria.Bedrooms = 1 and Criteria.Properties = 5 was a little vague, but I added it in where they should go. I'm guessing you actually meant the id of 1 and 5 respectively so it would be getting all criteria that have both a bedroom of id 1 and a property of id 5.

Try Eager Loading the relationships
$criterias = Criteria::with('bedrooms', 'properties')
->where('min', '<=', Input::get('single_value'))
->lists('id');

Related

Laravel pagination with specific foreign key

I need to make pagination in Laravel, and as I read laravel documentation here is a several way to do it. but in my cases it is a little bit complexity. So let say I've two table in my DB, table1 and table2(many to many relationship), I want to paginate table1 with specific ids. So I mean if table1 contains ids 1,2,3,4,5 ... n, I want to paginate only rows which id is 2 and 3
I have tried :
private function check_wear($type){
$products_id = array(); //here is ids which $type is set as 'ON'
$wears = DB::select("SELECT prod_id FROM wears WHERE ".$type." = 'on' "); //Select specific products ids. (in our case it is **table1**)
foreach ($wears as $wr){
array_push($products_id,$wr->prod_id);
}
return $products_id; //return array with ids which should be paginate
}
public function hat($locale){
$hat_prod_ids = $this->check_wear('coat');
$p=12;
$products = DB::table('products')->paginate($p); // 12 per page
dd($products);
my code only paginate one table with all data, is there any built-in function to somehow write a logic condition? for pagination
If you need to filter your pagination with elements which you've in $hat_prod_ids, you're able to use WhereIn() method, that one checks if your elements (in our case ids) exist in your $hat_prod_ids and returns true or false
$products = DB::table('products')
->whereIn('id',$hat_prod_ids)
->paginate($p);
add that code and now you will be able to paginate only that ids which is matched in your array

Laravel Where In on Where Has

Is there any way to return a parent model with a relationship but only return some of the relationship rows by using a where in?
That may be quite confusing, let me explain.
At the moment I have 2 models, Buildings and rooms, 1 building can have many rooms.
I want to be able to pass in an array of room ids, and return the sites and only the rooms that are in the array.
Heres what I have at the moment
if($request->input('ids') && !is_null($request->input('ids'))){
$ids = explode(',',$request->input('ids'));
//Exploded ids looks like this "2,4,11,55,56"
$buildings = Buildings::join('rooms')->whereIn('rooms.id',$ids)->get();
} else {
$buildings = Buildings::whereHas('rooms')->get();
}
At the moment this will return all buildings that have a room which id is in the ids array and all of its rooms, which ends up returning a building with 200+ rooms. I need it to return the building and ONLY the rooms that have an id in that array.
Is this possible?
I know I can do it the inverse way and get all rooms as the parent then get the buildings, but I need buildings to be the parent as i'm running a foreach like this with the results
foreach($buildings as $key => $building){
<h1>{{$building->name}}</h1>
foreach($building->rooms as $k => $room){
<p>{{$room->name}}</p>
}
}
Incase thats still confusing, the real world scenario is that i'm generating a PDF of rooms. The rooms can be selected by ticking a checkbox next to the room in a room list. I then need to be able to pass the array of room ids, and get all buildings that contain one of the rooms. Then get all of the rooms for each building where the room id is in the array.
First you need to know, whereHas only filter your parent result but not the eager loading relation. So you need to apply that filter in eager load too. Like this
$ids = explode(',',$request->input('ids'));
$buildings = Buildings::with(['rooms' => function($q) use ($ids) {
$q->whereIn('id', $ids);
}])->whereHas('rooms', function($q) use ($ids) {
$q->whereIn('id', $ids);
})->get();
Here whereHas filter buildings and using with filter rooms.
$ids = explode(',',$request->input('ids'));
$building_ids = Room::whereIn('id',$ids)->pluck('building_id');
$buildings_with_specific_rooms = Building::join('rooms', 'buildings.id', '=', 'rooms.building_id')->select('buildings.name', 'rooms.name')->whereIn('buildings.id', $building_ids)->whereIn('rooms.id', $ids)->get();
hope this helps you.
you can do this with the following code:
$ids = explode(',',$request->input('ids'));
$buildings = Buildings::whereHas('rooms', function($q) use ($ids) {
$q->whereIn('id', $ids);
})->get();
Hope this helps.
You can eager load the child relation and get the buildings out of the rooms collection:
$buildings = Room::with('building')
->with('building.room')
->whereIn('id', $ids)
->get()
->pluck('building');
For this to work you need to have the relation declared in both Building and Room models.

filtering result using eager loading

I am creating a search in laravel for an API but my search gives me the wrong results. I am trying to search by location and food type. I have the following tables:
foods
shops
shop_food
users
comments
Here is my search code:
public function searchShop($food, $location)
{
//
if($food == " " || $location == " "){
return $this->index();
}
//get all records where city and food are equal to
$shops = Shop::where('city', '=', $location)
->with('comments.user')
->with(['foods'=> function($query) use($food){
$query->where('name','=', 'fish pepper'); }])
->get();
//check if empty and return all
if($shops->isEmpty()){
return $this->index();
}
return $shops;
}
my result is the below instead of just the record where location and food it shows all the shops filtered by location even where food isnt a match :
The with method that you're using does not filter in the way that you think it does. Your code actually filters the food results, telling Eloquent to retrieve all Shop and either no foods, or the foods with the name fish pepper. This is called constraining eager loads.
The method you're looking for is whereHas rather than with. This is referred to as querying a relationships existence.
$shops = Shop::where('city', '=', $location)
->with('comments.user')
->whereHas('foods', function($query) use($food){
$query->where('name','=', 'fish pepper');
})
->get();
This will now return only Shops that have a corresponding food entry with the name fish pepper.
If memory serves, whereHas won't actually populate foods for you, but in this instance you wouldn't need it, as it's safe to assume that they all have fish pepper. If you do want to pull all foods, change with('comments.user') to with(['comments.user', 'foods']).
Documentation for whereHas and other ways of achieving this can be found here.
Documentation about what you were doing with the with method can be found here.
Hope that helps.

Eloquent HasMany relationship, with limited record count

I want to limit related records from
$categories = Category::with('exams')->get();
this will get me exams from all categories but what i would like is to get 5 exams from one category and for each category.
Category Model
public function Exams() {
return $this->hasMany('Exam');
}
Exam Model
public function category () {
return $this->belongsTo('Category');
}
I have tried couple of things but couldnt get it to work
First what i found is something like this
$categories = Category::with(['exams' => function($exams){
$exams->limit(5);
}])->get();
But the problem with this is it will only get me 5 records from all categories. Also i have tried to add limit to Category model
public function Exams() {
return $this->hasMany('Exam')->limit(5);
}
But this doesnt do anything and returns as tough it didnt have limit 5.
So is there a way i could do this with Eloquent or should i simply load everything (would like to pass on that) and use break with foreach?
There is no way to do this using Eloquent's eager loading. The options you have are:
Fetch categories with all examps and take only 5 exams for each of them:
$categories = Category::with('exams')->get()->map(function($category) {
$category->exams = $category->exams->take(5);
return $category;
});
It should be ok, as long as you do not have too much exam data in your database - "too much" will vary between projects, just best try and see if it's fast enough for you.
Fetch only categories and then fetch 5 exams for each of them with $category->exams. This will result in more queries being executed - one additional query per fetched category.
I just insert small logic inside it which is working for me.
$categories = Category::with('exams');
Step 1: I count the records which are coming in response
$totalRecordCount = $categories->count()
Step 2: Pass total count inside the with function
$categories->with([
'exams' => function($query) use($totalRecordCount){
$query->take(5*$totalRecordCount);
}
])
Step 3: Now you can retrieve the result as per requirement
$categories->get();

Counting total distant relationships in Laravel (eager loading?)

I'm having issues getting a proper count total with my Laravel model.
Model Structure
User
Item
ItemLike
A user can have multiple Items, and each of these Items can have multiple ItemLikes (when a user 'likes' the item).
I can easily get the individual ItemLike counts when using an Item model:
return $this->itemLikes()->count();
But I can't figure out how to get the total # of ItemLike's a User has across all the Item's he owns.
EXAMPLE
User A has 3 Items. Each Item has 5 ItemLike's, for a grand total of 15.
I tried using eager loading on the User model like this:
return $this->items()->with('itemlikes')->get()->count();
But that returns 3 (the # of Items)
These are the queries it ran, which appears like the second query is the one I want, yet every way I try it I still get 3 instead of 15
select * from `items` where `items`.`user_id` = '1000'
select * from `item_likes` where `item_likes`.`item_id` in ('1000', '1001', '1002')
After suggestions from others I found 2 solutions to get the result.
Using whereIn:
$itemViewCount = ItemView::
whereIn('item_views.item_id', $this->items()->lists('id'))
->count();
return $itemViewCount;
2 queries for a total of 410μs
Using join:
$itemViewCount = $this->items()
->join('item_views', 'item_views.item_id', '=', 'items.id')
->count();
return $itemViewCount;
2 queries for a total of 600μs
Isn't it just a case of creating a method that would return the number of items for the model. e.g.:
#UserModel
public function nbLikes()
{
$nbLikes = 0;
foreach($this->items() as $item) {
$nbLikes += $item->itemLikes()->count();
}
return $nbLikes;
}
And then User::nbLikes() should return the piece of data you are looking for?
try this:
$query="select count(il.id) from item_likes il,item itm where il.item_id=itm.id and tm.user_id=1000";

Categories