Eloquent eager loaded query not quite working with joins - php

I'm working on a Chemical database and am learning Eloquent as I go. This is in Slim Framework, not Laraval itself.
This thread helped get me close to what I need, but now I'm seeing something odd and I haven't been able to find a solution, though lots of people asking similar questions.
I have
$chemicals = $app->Chemical->with(array('Company', 'Room', 'Location', 'Measurement'))
->join('company', 'company_id', '=', 'company.id')
//->join('room', 'room_id', '=', 'room.id')
//->join('location', 'location_id', '=', "location.id")
->where('company', '=', 'ROUSSEL')
->get();
Notice the 2 Joins commented out. Those 2 fields do display properly in my table, but company is blank. If I switch which join is shown, it follows suit.
If I don't use any of the joins, I can do a where just fine with one of the chemical fields. My question is why does having a join seem to break that particular With statement and is there a way to fix it? I get the feeling that it's something to do with the With statement doing some kind of behind the scenes join, but I haven't been able to find any specifics..
Thanks,
Forgot to mention I had tried Eager Loading Constraints before as well.
$chemicals = $app->Chemical->with(
array(
'Company',
'Room',
'Measurement',
'Location' => function ($query) {
$query->where('location', '=', 'FLAMCAB');
}
))
//->where('company', '=', 'FISHER')
->get();
Using above without the where commented out gives me the message that the chemicals table does not have a column called company, which is true.
Not using it, I do get results:
As you can see it does filter the locations table, but it does only that, it doesn't filter chemicals by that, which is what I'm trying to do.

If you want to filter Chemicals based on their related Locations, you can combine constraining eager loads with querying relationship existence:
$filter = function ($query) {
$query->where('location', '=', 'FLAMCAB');
};
$chemicals = $app->Chemical->with(
array(
'Company',
'Room',
'Measurement',
'Location' => $filter
)
->whereHas('location', $filter)
->get();
I'm not sure how efficient this is though - you might want to check out the resulting query. Another option is to simply filter the collection using Collection filters after you run the query.
This question: Complex query with filters with Eloquent might also help you.

Related

Laravel: Orderby in related table and with

This is my model
User
Role
and relationship between of those models are many to many.
I want to create query like this:
return User::with('roles')->orderBy('roles.id')->paginate();
I don't want join because I created a base class for every model. I also don't want use orderBy after get because it must load all of my data and after that I should be able to sort and paginate it. So it is not a very good idea.
You can try something like this:
return User::with(['roles' => function ($query) {
$query->orderBy('id', 'desc');
}])->paginate();
But this will only order the eager loading attributes, but if you are interested to use join you can have something like this:
return User::with('roles')
->join('roles', 'user.id', '=', 'roles.user_id')
->orderBy('roles.id', 'desc')
->paginate();
In this you can easily use paginate which is your main concern.
Hope this helps.
User::where('role_id','!=','0')->orderBy('role_id','DESC')->paginate(10);

Laravel Eager loading internal constraints are not limited if followed by nested 'with'

This is a question and my own answer (solution I figured out by chance). Laravel documentation does not mention this and it brought me hours of programming suffering.
Let's say we have Posts with Comments and Votes (for comments). Laravel's favorite example. Models and relationships are Textbook (from Laravel's docs). Posts have comments, comments have votes.
So,
$comments_ids = [1,6,7,22];
Post::where('id', $post_id)
->with(['comments' => function($query) use ($comments_ids) {
$query->whereIn('id', $comments_ids);
}])
->with('comments.votes')
->first();
So, I should expect Post with comments which, ids are 1,6,7,22 and votes eager loaded.
But not so fast! I get ALL COMMENTS! ALL OF THEM! ...why?
Here is answer to that question:
Because, we eager load comments then we load votes, the votes forces all comments to load.
This:
$comments_ids = [1,6,7,22];
Post::where('id', $post_id)
->with(['comments' => function($query) use ($comments_ids) {
$query->whereIn('id', $comments_ids);
}])
->with('comments.votes') //this forces Builder to completely ignore whereIn clause above.
->first();
Should be written as following:
$comments_ids = [1,6,7,22];
Post::where('id', $post_id)
->with(['comments' => function($query) use ($comments_ids) {
$query->whereIn('id', $comments_ids)
->with('votes'); //eager load votes with filtered comments
}])
->first();
Then you will get the comments with ids specified in $comments_ids variable. And votes eager loaded with them.
This little nuance has caused much headaches.

Add where clause to relations table in laravel

I have a question. This query gets me the needed data and that includes the related table delivery
$RS = $this->instance->user()->with(['driver.trailer', 'driver.truck', 'driver.delivery']);
I researched this topic, and I think part below should do the trick, but I don't know how to combine the two together.
//add WHERE clause to driver.delivery:
->whereBetween('created_at', [
Carbon\Carbon::parse('last monday')->startOfDay(),
Carbon\Carbon::parse('next sunday')->endOfDay(),])
->get();
I am new to Laravel (doing it for about 10 days now) and this concept is very new to me. (I have no issues writing a standard query)
Try this:
return $this->instance->user()->with(['driver.delivery' => function($query){
$query->whereBetween('created_at', [
Carbon\Carbon::parse('last monday')->startOfDay(),
Carbon\Carbon::parse('next sunday')->endOfDay() ]);
}])->with(['driver.trailer' 'driver.truck'])->get();

Deeply nested relationships filtering

Recently I faced problem accessing and filtering deeply nested relationship, so I decided to seek for a help.
So, I have this db structure:
http://s21.postimg.org/motrjy3dj/Screenshot_from_2015_07_24_12_14_51.png
And I need to get all teams within a project, and then for each team I need to get assigned users (of that team).
So far so good, my problem starts when I try to get the offer for each user. User can have only one offer for the assigned team which brings me to a problem.
Here is my code:
$project = Project::with("variants")
->with(array(
"teams" => function($query) {
$query->with(array(
"users" => function($query) {
$query->with("offers");
}
));
}
))
->find($projectID);
I have a hasManyThrough relationship "offers" in the "User" model which returns me all offers for user, but actually I just need (one) offer for related team_user table.
I tried filtering offers with scopes but it's a bad solution, because for each user I have additional query to db..
Is there some way to filter these offers dynamically?
Thanks!
I highly recommend using joins for this sort of complex query:
$projectOffers = Project
->join('team', 'team.project_id', '=', 'project.id')
->join('team_user', 'team_user.team_id', '=', 'team.id')
->join('user', 'user.id', '=', 'team_user.user_id')
->join('offer', 'offer.id', '=', 'team_user.offer_id')
->get([
'project.id',
'team.id AS team_id',
'user.id AS user_id',
'offer.id AS offer_id',
// any other columns you want
]);

Eloquent eager loading acting as explicit join

I am building a small blog and I would like to use the built-in Eloquent eager loading, but I would like it to act as an explicit join.
Here's what I'm trying to do, using a join, which works, but not in the way I want.
$posts = Post::join('users', function($join){
$join->on('users.id', '=', 'posts.user_id');
$join->on('users.status', '=', DB::raw('active'));
})
->get();
My problem with this is that I can't use the user model on my post like so:
$posts[0]->user->firstname;
With the join, the user's data is directly set on the post model, so I have to use it like so:
$posts[0]->firstname;
The thing is: I would like to use my User model because it has a few method inside that I'd like to use. One for printing the full name, one for printing its URL, etc..
What I am not able to do with eager loading, is to prevent a post from loading when it has no user attached to it. When the user associated with the post doesn't have the status 'active', I still get the post, and NULL for the user. But I don't want the post at all.
Is that something possible?
Am I being clear enough in my explications?
Thanks
You're overcomplicating things. Eloquent has everything you need:
// first make sure you load only posts of active users
$posts = Post::whereHas('user', function ($q) {
$q->where('status', 'active');
})
// then eager load the users
->with('user')
->get();
And by the way this is how you do, what you tried, in your join (w/o DB::raw):
$posts = Post::join('users', function($join){
// on() is for comparing fields
$join->on('users.id', '=', 'posts.user_id');
// while where is for comparing to provided values/strings
// just like simple query where()
$join->where('users.status', '=', 'active');
})

Categories