Nested eager loading dynamically created in a constrain - laravel

songs table
id version number
1 AAA 1
2 BBB 1
3 CCC 1
4 DDD 2
5 EEE 3
6 FFF 4
7 GGG 4
The objective is to obtain, for example, all songs that have the same number as the song with id 1 (songs with ids 1, 2 e 3)
Song model
public function songs()
{
return $this->hasMany(Song::class, 'id_hierarchy');
}
Query: it doesn't work it's just for demonstration (there is no sameNumber model)
public function readSongsVersions()
{
$data = Hierarchy::query()
->whereNull('parent_id')
->with([
'children.songs'
'children.songs.sameNumber'
])
->get();
}
There is no sameNumber model.
Theoretically, a relationship would have to be created in the model song and it would be a relationship with the model song itself; Is such a solution possible?
The solution will eventually go through a constraint.
$data = Hierarchy::query()
->whereNull('parent_id')
->with([
'children.songs'
'children.songs' => function ($query) {
$query->where('number', '=', function($query) {
$query->select(DB::raw('i.number)'))
->from('songs as i')
->whereRaw('i.number = songs.number')
});
},
])
->get();
}
How to nest in eager loading the result of the subquery ( 'children.songs' => function ($query) {...}) to get the sameNumber music collection for each song;
the equivalent of: 'children.songs.sameNumber'

You can create relationships, that references itself. A where statement to ensure, not the same row is returned, since you are relying on the number column.
In Song.php
public function sameNumber()
{
return $this->hasMany(Song::class, 'number', 'songs.number')
->whereColumn('version', '!=', 'songs.version');
}
There is one part you have to figure out, but when you run the code it should be fairly obvious, 'version' and 'number' columns are gonna be ambiguous and you have to check what naming Laravel uses, when dealing with tables named the same. Change the code like so songs2.version, dependent on the naming. You can also always call ->toSql() on the relationships to see the SQL query.

Related

Nested eager loading with constraint groupBy and get just last item of group

Query with non-functional constrain to demonstrate what is intended
public function readSongsVersions()
{
$data = Hierarchy::query()
->whereNull('parent_id')
->with(['children.song' => function ($query) {
$query->groupBy('number')
->orderBy('id', 'asc')
->max('id') //this does not work
}])
->get();
}
songs table
id version number
1 AAA 1
2 BBB 1
3 CCC 1
4 DDD 2
5 EEE 3
6 FFF 4
7 GGG 4
The goal is to get only the latest version of each song
Song number 1 has 3 versions and de last one is: id:3 with version:CCC
Song number 4 has 2 versions and de last one is: id:7 with version:GGG
Expected songs versions result
CCC
DDD
EEE
GGG
You can try using subqueries to achieve the desired output
//Assuming `child_id` is the foreign key on `songs` table
//which references primary key `id` on `children` table
public function readSongsVersions()
{
$data = Hierarchy::query()
->whereNull('parent_id')
->with([
'children.songs' => function ($query) {
$query->where('id', '=', function($query) {
$query->select(DB::raw('MAX(i.id)'))
->from('songs as i')
->whereRaw('i.number = songs.number')
->whereRaw('i.child_id = songs.child_id');
});
}
])
->get();
}
Or if you want to do it without subquery
//Assuming `child_id` is the foreign key on `songs` table
//which references primary key `id` on `children` table
public function readSongsVersions()
{
//Get ids of songs which have the highest id for particular number and child_id
//Means get the id of latest version of song for particular number
//within records of songs which belong to a particular children record
$ids = Song::query()
->select(DB::raw('MAX(id) as id'), 'child_id', 'number')
->groupBy('child_id', 'number')
->get()
->pluck('id');
//Then use these $ids to filter out the songs records
$data = Hierarchy::query()
->whereNull('parent_id')
->with([
'children.songs' => fn($query) => $query->whereIn('id', $ids)
])
->get();
}
I wonder if this would work in your case.
$data = Hierarchy::select("version","number")
->whereNull('parent_id')
->withMax(children.song','id')
->get()
->toArray();
You should have something like song_version_max_id into your array. or similar, use dd($data) to see results.

laravel Eloquent query WhereHas wrong result

I have two tables and relations as bellow
user
the user table:
id
name
active
1
abc
1
2
xyz
Null
3
abx
0
the book table:
id
user_id
name
active
1
1
book1
0
2
2
book2
0
3
1
book3
0
relation is as this
user->books (HasMany)
return $this->hasMany(Book::class,'user_id','id');
my query is as bellow
User::with('book')
->WhereHas('book', function($query) {
$query->where(['active'=> 1]);
})
->where(['id'=> 1,'active'=>1])
->get();
This query is getting zero records because active is 0 in books
But i want to see all user record and if there is matching record with active 1 in book.
second is query user for active 1 or Null and for that if use ->orwhereNull('active')
All records changes.
Thanks
I guess you need to apply filter on related records (with()) instead of applying filter on whole query. So this way you will always get a list of users along with the list of active books only
User::with(['book'=> function($query) {
$query->where('active', 1);
}])
->where(function ($query) {
$query->where('active', 1)
->orWhereNull('active');
})
->get();
I have used logical grouping here so in case if you have more filter clauses to add in your query.
Or you can define a new relation in your user model to pick only active related books as
class User extends Model
{
public function book()
{
return $this->hasMany(Book::class, 'user_id', 'id');
}
public function active_books()
{
return $this->hasMany(Book::class, 'user_id', 'id')
->where('active', '=', 1);
}
}
User::with('active_books')
->where(function ($query) {
$query->where('active', 1)
->orWhereNull('active');
})
->get();
You have id and active column on both table, so I think you need to change your query like this :
User::with('book')
->WhereHas('book', function($query) {
$query->where(['books.active'=> 1]); // books table active column
})
->where(['id'=> 1,'active'=> 1]) // users table id, active column
->get();

laravel Eloquent Relationship query with whereHas

I have two tables as bellow
user:
id
name
email
1
abc
abc#...
2
xyz
xyz#...
3
abx
abx#...
books:
id
user_name
book
price
1
abc:x1
book1
10
2
xyz:x2
book1
20
3
abc:x5
book3
30
4
ab:x2
book1
10
If you notice that my 2nd table field user_name have user_name of user table plus :x and number
maybe this didn't make sense but this is my project requirements
Now when I want to query some user with books like If I search a user with ID (1)
it should give me records of 1 & 3 because of user_name
I tried this
$user = USER::with('books')
whereHas('books', function ($query) {
$query->where('user_name', 'like', 'name:%');
})->get();
Also, I tried this
USER::with('books')
->whereHas('books', function ($query) use ($name) {
$query->where('user_name', 'like', $name . ':%');
})
Also not working.
Also how to get 3rd extra column value after :x
Also, is this possible to write this query in model?
Thanks
I would suggest you that you should always store normalized data in database irrespective of the presentation layer of the data. The data can be transformed in your desired form at query level or in the app itself.
So you should introduce 2 columns in your books table
user_id
sequence
like
id
user_id
sequence
book
price
1
1
1
book1
10
2
2
2
book1
20
3
1
5
book3
30
4
3
2
book1
10
This way you can filter out the users easily and you can apply filter on user books also. To construct the user name you can use accessor and a virtual attribute in your book model which will construct the user name for you like
class Book extends Model
{
protected $appends = ['user_name'];
protected $with = ['user'];
public function getUserNameAttribute()
{
return $this->user->name .":x". $this->sequence;
}
public function user()
{
return $this->belongsTo(User::class, 'id', 'user_id');
}
}
$appends will hold the custom attributes for your models and the accessor method will construct a user name using the user() relation. So its important if you load books then you should also eager the related users so that in accessor $this->user->name will have the name from user table otherwise it will be empty, For this you can use $with property in your model which will intimate that related user should be loaded whenever books are loaded.
Now you can get the user with specific id as
User::with('books')->find(1);
Or books by the name of user as
Book::whereHas('user', function ($query) use ($name) {
$query->where('name', 'like', $name);
});
Or books by the name of user and with the number after x that is sequence
Book::whereHas('user', function ($query) use ($name, $sequence) {
$query->where('name', 'like', $name);
})->where('sequence', $sequence);

Laravel - Count of items through with belongs to

A company can have many delivery dates. A delivery date may have many entries. The Entry model contains
public function company()
{
return $this->belongsTo(Company::class);
}
public function delivery_date()
{
return $this->belongsTo(MgDeliveryDate::class, 'mg_delivery_date_id');
}
What I want is a count of how many entries each delivery date has for each company. For example, something like
$companies = Company->with('delivery_dates', 'delivery_dates.entries')->withCount('delivery_dates.entries')
So if my data was
Company Delivery Date Entry Number
A 1/2/2020 1
A 1/2/2020 2
A 2/2/2020 3
B 1/2/2020 4
I would get two companies, company A with two delivery dates and a count of 2 for the first date(1/2/2020) and 1 for the second date(2/2/2020) and company B with one delivery date, with an entry count of 1.
Good question. Give this a shot.
$companies = Company::with('delivery_dates.entries')->get();
$companies->map(function($company){
return $company->delivery_dates->map(function($delivery){
return $delivery->entries->count();
})->groupBy('delivery_date');
});
I have used Laravel Collection map() method.
// i am not sure what you actually want.. if you want the data like this then you can try this
[
{ company:abc , date:[{1/20} , {2/20} ...] , entry:[{1} ,{2} ...]} ,
{},
....
]
Company model
public function entry()
{
return $this->hasMany(entry::class);
}
And Then
$companies = Company::withCount(['entry', 'delivery_dates' => function ($query) {
$query->where(your condition);
}])->get();
You may write:
$companies = Compnay::with(['delivery_dates' => function ($q) {
$q->withCount('entries');
}])
->get();
You will get the following result:

Laravel Eloquent with() selecting specific column doesn't return results

Say I have 2 models, Category and POI where 1 Category can have many POIs.
$categoryDetails = Category::with([
'pois' => function ($query) {
$query->where('is_poi_enabled', true);
},
])->findOrFail($id);
The above query returns results from the specific Category as well as its POIs.
However, with the query below:
$query->select('id', 'name')->where('is_poi_enabled', true);
The POIs become empty in the collection.
Any idea why this is happening? When added a select clause to the Eloquent ORM?
While doing a select it's required to fetch the Relationship local or Primary key.
For an example POIs table contains category_id then it's required to select it
Try this:
$categoryDetails = Category::with([
'pois' => function ($query) {
$query->select(['id', 'category_id', 'is_poi_enabled'])
->where('is_poi_enabled', true);
},
])->findOrFail($id);
Good luck!

Resources