How to select specific fields from nested relations in Eloquent - php

I have the following query in Eloquent:
public function firstSubsectionIdsOnly()
{
return $this->model->with(['sections' => function ($q) {
$q->with(['subsections' => function ($q2) {
$q2->first();
}
])->first();
}])->whereHas('sections.subsections')->first();
}
This returns something like this:
{
"id": 1,
"name": "Training exercise",
"entry": "<p>sss</p>",
"created_at": "2018-04-20 09:38:36",
"updated_at": "2018-04-20 10:08:27",
"sections": [
{
"id": 1,
"name": "Section 1 Training",
"parent": null,
"position": 1,
"created_at": "2018-05-04 09:37:23",
"updated_at": "2018-05-04 09:37:23",
"pivot": {
"training_exercise_id": 1,
"section_id": 1
},
"subsections": [
{
"id": 2,
"name": "Subsection 1 training",
"parent": 1,
"created_at": "2018-05-04 09:54:09",
"updated_at": "2018-05-04 09:54:09"
}
]
}
]
}
I would like for it to only select the id fields for each relation so it should return something like this:
{
"id": 1,
"name": "Training exercise",
"entry": "<p>sss</p>",
"created_at": "2018-04-20 09:38:36",
"updated_at": "2018-04-20 10:08:27",
"sections": [
{
"id": 1,
"subsections": [
{
"id": 2,
}
]
}
]
}
I have tried adding $q->select('id'); to the subsections nested closure but that returns an empty subsections array.
Any idea on how I can achieve this? I am using Laravel 5.6

As others have stated before, you need to include the foreign key in the values you select. Let's say we have two models. Parent and Child. This is the bare minimum you have to select.
App\Parent::select('id')->with(['children' => function ($query) {
$query->select('id', 'parent_id');
}])->get();
It nets you something like
Illuminate\Database\Eloquent\Collection {
all: [
App\Parent {
id: 1,
children: Illuminate\Database\Eloquent\Collection {
all: [
App\Child {
id: 109,
parent_id: 1,
},
App\Child {
id: 153,
parent_id: 1,
},
],
},
},
],
}
If you're trying to go for the inverse, you still need the foreign key, like so
App\Child::select('id', 'parent_id')->with('parent' => function ($query) {
$query->select('id');
}])->get();
This nets you
Illuminate\Database\Eloquent\Collection {
all: [
App\Child {
id: 1,
parent_id: 58,
parent: App\Parent {
id: 58,
},
},
],
}
I'm using select() instead of get() for legibility on the queries but you must use select() for the subqueries.
If you use with('relation') => function ($query) { $query->get(['id']); } you'll get the whole object.

You have to include the foreign key columns. For the subsections relationship:
$q2->select('id', 'parent');

Related

Select * from a pivot table in laravel

I have a many to many relation with table users,items and the pivot table user_item and i need to call the query : Select * from user_item where user_id=$user->id in laravel and return the results in json format. I try with
$user=User::find(session('user_id'))->items()->get();
return response ()->json($user);
But it doesn't work. How can I do that?
class User extends Authenticatable {
public function items (){
return $this->belongsToMany ("App\Models\Item", "user_item", "user", "item");
}
}
class Item extends Models {
public function users (){
return $this->belongsToMany ("App\Models\User", "user_item", "item", "user");
}
}
You can define columns in your pivote table using withpivot method
public function items (){
return $this->belongsToMany ("App\Models\Item", "user_item",
"user", "item")->withPivot(['column1', 'column2','another_column']);
}
to get relation instead of using get(), you should use like below:
$user=User::find(session('user_id'))->items;
return response ()->json($user);
above will give below json result:
[{
"id": 4,
"name": "PC",
"pivot": {
"column1": 1,
"column2": 4,
"another_column": "2016-03-03"
}
},
{
"id": 5,
"name": "Phone",
"pivot": {
"column1": 1,
"column2": 4,
"another_column": "2016-03-03"
}
}]
you can also include pivot in use user model using with():
$user=User::with('items')->find(session('user_id'));
return response ()->json($user);
give json result something like:
{
"id": 1,
"name": "User Name",
"email": "email#user.com",
"created_at": null,
"updated_at": null,
"items": [{
"id": 4,
"name": "PC",
"pivot": {
"column1": 1,
"column2": 4,
"another_column": "2016-03-03"
}
},
{
"id": 5,
"name": "Phone",
"pivot": {
"column1": 1,
"column2": 4,
"another_column": "2016-03-03"
}
}]
}]

Eloquent Model relationship does not return sub-relationship specific column value

I have attempted to achieve to pull the user's info and the product details (Which specified the nickname and name) when the product name equal to "Oops" but I have no idea why the getProducts does not return any things.
User Model
public function getProducts(){
return $this->hasMany('App\Models\Product','users_id');
}
Product Model
public function users(){
return $this->belongsTo(User::class);
}
The code of pulling data:
$products = User::with(['getProducts' => function($query){
$query->select("users_id","name","nickname");
}])->get();
The current output:
"data": [
{
"id": 1,
"name": "John Smith",
"email": "john.smith#hotmail.com",
"email_verified_at": null,
"created_at": "2021-04-08T13:29:13.000000Z",
"updated_at": "2021-04-08T13:29:13.000000Z",
"role": 0,
"get_products": [
]
},
{
"id": 2,
"name": "Kelvin Ooi",
"email": "kelvin.ooi#hotmail.com",
"email_verified_at": null,
"created_at": "2021-04-08T13:29:13.000000Z",
"updated_at": "2021-04-13T12:07:11.000000Z",
"role": 1,
"get_products": [
{
"nickname":"MCD",
"name":"Oops"
},
{
"nickname":"Mary Brown",
"name":"Oops"
},
{
"nickname":"Kentucy",
"name":"KFC"
},
{
"nickname":"Texas Chicken",
"name":"TXS"
}
]
}
]
The expected output
"data": [
{
"id": 1,
"name": "John Smith",
"email": "john.smith#hotmail.com",
"email_verified_at": null,
"created_at": "2021-04-08T13:29:13.000000Z",
"updated_at": "2021-04-08T13:29:13.000000Z",
"role": 0,
"get_products": [
]
},
{
"id": 2,
"name": "Kelvin Ooi",
"email": "kelvin.ooi#hotmail.com",
"email_verified_at": null,
"created_at": "2021-04-08T13:29:13.000000Z",
"updated_at": "2021-04-13T12:07:11.000000Z",
"role": 1,
"get_products": [
{
"nickname":"MCD",
"name":"Oops"
},
{
"nickname":"Mary Brown",
"name":"Oops"
}
]
}
]
getProducts is not a good name for the relationship, lets simply call it products.
public function products()
{
return $this->hasMany('App\Models\Product','users_id');
}
Your code in the comment doesn't work because you specify the where clause in the main query, not the sub query.
return User::with(['products' => function ($query) {
$query->select("users_id","name","nickname");
}])
// This got to be in the sub query.
->where("products.name","Oops")
->get();
So let's update your code to this:
$productName = 'Oops';
return User::with(['products' => function ($query) use ($productName) {
$query->select("users_id","name","nickname", "price")
->where("name","LIKE", "%{$productName}%");
}])
->get();
I have seen your comment to this answer. Let's define a total custom attribute for the User model:
class User extends Model
{
protected $appends = ['total'];
public function getTotalAttribute()
{
// This is higher order message, if you haven't used it: https://laravel.com/docs/8.x/collections#higher-order-messages
return $this->products->sum->price;
}
}
Then the total attribute will be part of any user.

Laravel 6 pluck in hasManyThrough

Controller:
$files = File::where('agent_id', $user->id)->with('posts')->get();
Model:
public function posts()
{
return $this->hasManyThrough('App\User', 'App\Post', 'id', 'id', 'post_id', 'user_id');
}
So this return a bunch of data, for example:
{
"success": [
{
"id": 2,
"post_id": 1,
"transaction_id": 4,
"agent_id": 2,
"status": 0,
"posts": [
{
"id": 1,
"name": "john",
"email": "john#gmail.com",
"phone": "489797878",
"type": "1",
"verified": 1,
"otp": null,
"created_at": "2019-11-23 10:17:31",
"updated_at": "2019-11-23 10:17:51",
"api_token": null,
"laravel_through_key": 1
}
]
}
...
}
What I want is, exclude some data, like email or verified and etc. I tried pluck('email') and also makeHidden but no success. any idea how can I do this?
How about doing some thing like below, ( haven't tested the code but should give you a clue )
files = File::where('agent_id', $user->id)
->with(['posts' => function ($q) {
$q->select('name','phone'); // specify whatever you want
}])->get(['column1','column2']);
Just use ->get() include list of those you want to get:
example:
$files = File::where('agent_id', $user->id)->with('posts')->get(['post_id', 'status']);

Whats the best approach to create filter-based search in Laravel or raw SQL?

I'm working on a query to find offers based in different filters and need some guidance on how to do that.
My model structure is basically: offer -> place -> categories -> filters -> filterOptions
offer (belongsTo) <-> (hasMany) place
place (belongsToMany) <-> (belongsToMany) category
place (belongsToMany) <-> (belongsToMany) filter
filter (belongsToMany) <-> (belongsToMany) category
filter (hasMany) <-> (belongsToMany) filterOption
Search params comes in request JSON just like this below, which says "get offers from places with category 1 and has those filters + filterOptions"
{
"category_id":1,
"filters":[
{
"filter_id":1,
"options":[
{"option_id":2},
{"option_id":3}
]
},
{
"filter_id":2,
"options":[
{"option_id":4},
{"option_id":6}
]
}
]
}
So far I'm just getting the entire list without filtering.
$q = Offer::with([
'place',
'place.categories',
'place.categories.filters',
'place.categories.filters.filterOptions',
])
This is returning the JSON objects like this:
[
{
"id": 4,
"place_id": 2,
"place": {
"id": 2,
"categories": [
{
"id": 3,
"name": "Mechanics",
"pivot": {
"place_id": 2,
"category_id": 3
},
"filters": [
{
"id": 5,
"name": "Services",
"pivot": {
"category_id": 3,
"filter_id": 5,
},
"filter_options": [
{
"id": 8,
"name": "Tires",
"filter_id": 5
},
{
"id": 9,
"name": "Painting",
"filter_id": 5
}
]
},
{
"id": 6,
"name": "Amenities",
"pivot": {
"category_id": 3,
"filter_id": 6,
},
"filter_options": [
{
"id": 11,
"name": "Wifi",
"filter_id": 6
},
{
"id": 12,
"name": "Food",
"filter_id": 6
}
]
}
]
}
]
}
}
]
I have no idea on what is the starting point to perform this filtering.
Should I use eloquent or raw query? single or multiple queries? joins or grouping?
Solved this way:
$params = json_decode($request->getContent());
$q = Offer::with([
'place:id,title',
'place.categories:id,name',
'place.categories.filters:id,name',
'place.categories.filters.filterOptions:id,name,filter_id'
])
->whereHas('place.categories', function($q) use ($params){
$q->where('id', $params->category_id);
})
->whereHas('place.categories.filters', function($q) use ($params){
$filters = array_flatten(array_pluck($params->filters, 'filter_id'));
$q->whereIn('id', $filters);
})
->whereHas('place.categories.filters.filterOptions', function($q) use ($params){
$filterOptions = array_flatten(array_pluck($params->filters, 'options.*.option_id'));
$q->whereIn('id', $filterOptions);
})
->select('id','percent','place_id')
->get();

laravel 5.6 - get data of multiple layers of parent-child with belongs to many

Imagine I have a site with a lots of catalogs which have sections and with each section, i have items.
catalogs is belongstomany with sections.
sections is belongstomany with items.
What I want for final result is to have a nest data with:
{
"catalog_id": 1,
"catalog_name": "catalog 01",
"sections": [
{
"section_id": 1,
"name": "section 01",
"items": [
{
"item_id": 1,
"item_content": {
"name": "some content1"
}
},
{
"item_id": 2,
"item_content": {
"name": "some content2"
}
},
{
"item_id": 3,
"item_content": {
"name": "some content3"
}
}
]
},
{
"section_id": 2,
"name": "section 02",
"items": [
{
"item_id": 2,
"item_content": {
"name": "some content2"
}
},
{
"item_id": 3,
"item_content": {
"name": "some content3"
}
},
{
"item_id": 4,
"item_content": {
"name": "some content4"
}
}
]
}
]
}
How can I achieve that?
Technically I can use each-loop to get whatever I want, but I hope that is a better solution in Eloquent.
Try this
Catalogs::with('sections.items')->get();
I would add to #J.Doe answer that you can specify what you want to do. for example
Catalogs::with([
'sections' => function($query){
return $query->select('column')
->where('someCondition', 1)
->orderBy('id', 'asc');
'items' => //some other stuff
];
you can also use method with() for your relation. lets say sections related to someTable. if you have relation method someTable in your section model, then:
Catalogs::with([
'sections' => function($query){
return $query->select('column')
->with('someTable') // here's your relation
->where('someCondition', 1)
->orderBy('id', 'asc');
'items' => //some other stuff
];

Categories