Laravel: Limit columns retrieve in an eagerly loaded relationship, with constraints - laravel

When I eagerly load a straightforward Eloquent relationship I can limit the columns retrieved using the following syntax:
MyModel::with(myRelation:id,col_2,col_3);
When I want to constrain the members of the relationship, I do this:
MyModel::with(['myRelation' => function ($query) {
$query->where([
['field_1', 'a_value'],
['field_2', 'b_value']
]);
}]);
But that loads all columns of the related models. I tried replacing the key myRelation with the full syntax, myRelation:id,col_2,col_3, but it throws an error which says the relation name is not found.
I also tried adding the following methods to the $query:
->select('id', 'col_2', 'col_3')
or
->addSelect('id', 'col_2', 'col_3')
or
->get('id', 'col_2', 'col_3')
None of these were successful.

One option could be keeping all the columns (except id, col_2, col_3) hidden in your original model MyRelation is pointing to.
protected $hidden = ['col_4', 'col_5',...];
You could also change the relationship definition in your model MyModel:
public function myRelation()
{
return $this->belongsTo('MyRelation')->select(array('id', 'col_2', 'col_3'));
}
But, in general, this should work:
MyModel::with(array('myRelation'=>function($query){
$query->select('id','col_2', 'col_3)
->where([
['field_1', 'a_value'],
['field_2', 'b_value']
]);
}))->get();
Remember, you have to include the joining key (say id) in the select list above.

Ok, so to get this to work, I had to:
include the foreign_key in the list of selects (thanks, #ankitPatel), and
pass the parameters to the select statement as an array, as follows:
->select(['id','foreign_key','col3','col4'])

Related

Laravel eloquent for four tables

I'm new to Laravel. I am developing a project. and in this project I have 4 tables related to each other
-Users
-Orders
-OrderParcels
-Situations
When listing the parcels of an order, I want to get the information of that order only once, the user information of that order once again, and list the parcels as a table under it. so far everything ok. but I also want to display the status of the parcels listed in the table as names. I couldn't add the 4th table to the query. do you have a suggestion? I'm putting pictures that explain the structure below.
My current working code is
$orderParcels = Orders::whereId($id)
->with('parcels')
->with('users:id,name')
->first();
and my 'orders' model has method
public function parcels(){
return $this->hasMany(OrderParcels::class);
}
public function users(){
return $this->hasOne(User::class,'id','affixer_id');
}
Note[edit]: I already know how to connect like this
$orderParcels = DB::table('order_parcels as op')
->leftjoin('orders as o','op.orders_id','o.id')
->leftjoin('users as u','o.affixer_id','u.id')
->leftjoin('situations as s','op.status','s.id')
->select('op.*','o.*','u.name','s.situations_name')
->where('op.orders_id',$id)->get();
but this is not working for me, for each parcels record it returns me orders and user info. I want once orders info and once user info.
Laravel provides an elegant way to manage relations between models. In your situation, the first step is to create all relations described in your schema :
1. Model Order
class User extends Model {
public function parcels()
{
return $this->hasMany(OrderParcels::class);
}
public function users()
{
return $this->hasOne(User::class,'id','affixer_id');
}
}
2. Model Parcel
class Parcel extends Model {
public function situations()
{
return $this->hasOne(Situation::class, ...);
}
}
Then, you can retrieve all desired informations simply like this :
// Retrieve all users of an order
$users = $order->users; // You get a Collection of User instances
// Retrieve all parcels of an order
$parcels = $order->parcels; // You get a Collection of User instances
// Retrieve the situation for a parcel
$situations = $parcel->situations // You get Situation instance
How it works ?
When you add a relation on your model, you can retrieve the result of this relation by using the property with the same name of the method. Laravel will automatically provide you those properties ! (e.g: parcels() method in your Order Model will generate $order->parcels property.
To finish, in this situation where you have nested relations (as describe in your schema), you should use with() method of your model to eager load all the nested relation of order model like this :
$orders = Orders::with(['users', 'parcels', 'parcels.situations'])->find($id)
I encourage you to read those stubs of Laravel documentation :
Define model relations
Eager loading
Laravel Collection
Good luck !
Use join to make a perfect relations between tables.
$output = Orders::join('users', 'users.id', '=', 'orders.user_id')
->join('order_parcels', 'order_parcels.id', '=', 'orders.parcel_id')
->join('situations', 'situation.id', '=', 'order_parcels.situation_id')
->select([
'orders.id AS order_id',
'users.id AS user_id',
'order.parcels.id AS parcel_id',
'and so on'
])
->where('some row', '=', 'some row or variable')->get();

how to get list of users who are not under belongsToMany relation under a table in laravel?

Consider the following case.
we have the Users table and Tasks table. they are in relation with belongsToMany with table task_user.
How to get the list of all users who are not under any task? i.e. their user_id is not at all under that given task or even in the task_user table.
why I need this is because like this we can only provide a list of users who are yet to be assigned a task. the task will be assigned to users and not a single user at a time.
Editing_____________
also how to filter with users based on group table? below is not working
$users = Group::with(['subscribers' => function ($q){
$q->doesntHave("tasks");
}])->whereId($gid)->latest()->get();
Assuming you've named your relationships properly, you should be able to use doesntHave("tasks"):
$tasklessUsers = User::doesntHave("tasks")->get();
doesntHave() checks for the non-existence of the supplied relationship ("tasks", in this case) and returns all objects that pass this check.
If your function name is different, use that, but the relationship should be:
User.php:
public function tasks(){
return $this->belongsToMany(Task::class, "task_user");
}
Edit: doesntHave() is the simple version, whereDoesntHave() allows a custom query. See https://laravel.com/docs/5.8/eloquent-relationships#querying-relationship-absence for full details.
Second Edit:
As stated in the comments below, with() will not filter the Model it is being called on, so this query won't work as you'd expect:
$users = Group::with(['subscribers' => function ($q){
$q->doesntHave("tasks");
}])->whereId($gid)->latest()->get();
To fix this, use a chained doesntHave() query:
$query = Group::doesntHave('subscribers.tasks')
->where('id', '=', $gid)
->latest()
->first();
// OR
$query = Group::whereHas('subscribers', function($subQuery){
$subQuery->doesntHave('tasks');
})->where('id', '=', $gid)
->latest()
->first();
$users = $query->subscribers; // Return `users` (aliased to `subscribers`)
Either approach will check the existence of subscribers that don't have any associated tasks relationship, and also only return where id is $gid.
Note: Used first() for the queries, as using id in a query should only ever return a single Group record, and get() is for returning multiple records in a Collection

Laravel 5.0 how to order an eloquent with() query

I have two models: item and faq. The are in a belongsToMany with each other with a correctly created join table: item_faq (singular of both). My join table has an additional field on it for order.
In my view I get all the faq's and if they have a pivot table record I output "checked" on a checkbox. I also have drag and drop ordering on the checkbox list and that works well.
A few code notes:
// ITEMS MODEL
public function faqs(){
return $this->belongsToMany('App\Faq');
}
// FAQ MODEL
public function items(){
return $this->belongsToMany('App\Item');
}
public function hasItem($item) {
$items = $this->items->lists('id');
return in_array($item, $items);
}
Schema of join table:
item_id
faq_id
order
timestamps
My issue is that they faq's don't load sorted by the order column on the pivot table.
I am using a very simple:
$faqs = \App\Faq::with('items')->get();
To retrieve the FAQ's and this works at getting all the faq's and if they are related, it checks the checkbox.
How can I order these by the order column on the join table?
Have a look at Eager Load Constraints and I think it will help provide a solution. From the docs:
Of course, eager loading Closures aren't limited to "constraints". You may also apply orders:
$users = User::with(['posts' => function($query) {
$query->orderBy('created_at', 'desc');
}])->get();

Laravel - Eloquent "Has", "With", "WhereHas" - What do they mean?

I've found the concept and meaning behind these methods to be a little confusing, is it possible for somebody to explain to me what the difference between has and with is, in the context of an example (if possible)?
With
with() is for eager loading. That basically means, along the main model, Laravel will preload the relationship(s) you specify. This is especially helpful if you have a collection of models and you want to load a relation for all of them. Because with eager loading you run only one additional DB query instead of one for every model in the collection.
Example:
User > hasMany > Post
$users = User::with('posts')->get();
foreach($users as $user){
$users->posts; // posts is already loaded and no additional DB query is run
}
Has
has() is to filter the selecting model based on a relationship. So it acts very similarly to a normal WHERE condition. If you just use has('relation') that means you only want to get the models that have at least one related model in this relation.
Example:
User > hasMany > Post
$users = User::has('posts')->get();
// only users that have at least one post are contained in the collection
WhereHas
whereHas() works basically the same as has() but allows you to specify additional filters for the related model to check.
Example:
User > hasMany > Post
$users = User::whereHas('posts', function($q){
$q->where('created_at', '>=', '2015-01-01 00:00:00');
})->get();
// only users that have posts from 2015 on forward are returned
The documentation has already explained the usage, so I will use SQL to explain the methods.
Example:
Assuming there is an Order (orders) has many OrderItem (order_items) and you already built the relationship between them:
// App\Models\Order:
public function orderItems() {
return $this->hasMany('App\Models\OrderItem', 'order_id', 'id');
}
These three methods are all based on a relationship.
with
Result: with() return the model object and its related results.
Advantage: It is eager-loading which can prevent the N+1 problem.
When you are using the following Eloquent Builder:
Order::with('orderItems')->get();
Laravel change this code to only two SQL:
// get all orders:
SELECT * FROM orders;
// get the order_items based on the orders' id above
SELECT * FROM order_items WHERE order_items.order_id IN (1,2,3,4...);
And then Laravel merges the results of the second SQL query with the results of the first SQL by foreign key, finally returning the collection results.
So if you selected columns without the foreign_key in a closure, the relationship result will be empty:
Order::with(['orderItems' => function($query) {
// $query->sum('quantity');
$query->select('quantity'); // without `order_id`
}
])->get();
#=> result:
[{ id: 1,
code: '00001',
orderItems: [], // <== is empty
},{
id: 2,
code: '00002',
orderItems: [], // <== is empty
}...
}]
has
Has will return the model's object when its relationship is not empty.
Order::has('orderItems')->get();
Laravel changes this code to one SQL query:
select * from `orders` where exists (
select * from `order_items` where `orders`.`id` = `order_items`.`order_id`
)
whereHas
The methods whereHas and orWhereHas put where conditions on your has queries. These methods allow you to add customized constraints to a relationship constraint.
Order::whereHas('orderItems', function($query) {
$query->where('status', 1);
})->get();
Laravel changes this code to one SQL query:
select * from `orders` where exists (
select *
from `order_items`
where `orders`.`id` = `order_items`.`order_id` and `status` = 1
)

How to pick only certain columns when eager loading

is there a way to select only certain columns when doing eager-loading in laravel?
$columns = ['col_1', 'col_2', 'col_3'];
$model = MyModel::findOrFail(Input::get('id'), $columns);
$model->load('position');
In My model I Have defined:
public function position() {
return $this->belongsToMany('Position', 'mymodel_positions')->withTimestamps();
}
When I don't provide the $columns parameter like MyModel::findOrFail(id). Positions are
loaded correctly. However if I do provide $columns, the positions ale always empty.
What I want: eagerly load a relation AND specify the columns (Not the ones in the loaded relation). I'm sure It's possible, but i simply don't get it...
Hope for your help.
tldr; You always need to SELECT primary key and corresponding foreign key of the relation.
Most likely this:
$columns = ['col_1', 'col_2', 'col_3'];
doesn't contain your model's primary key, so Eloquent is unable to match related models.
That being said, all you need is:
$columns = ['id', 'col_1', 'col_2', 'col_3'];
or whatever field is the primary key.

Resources