laravel Eloquent Relationship query with whereHas - laravel

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);

Related

Nested eager loading dynamically created in a constrain

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.

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();

Get only one column from relation

I have found this: Get Specific Columns Using “With()” Function in Laravel Eloquent
but nothing from there did not help.
I have users table, columns: id , name , supplier_id. Table suppliers with columns: id, name.
When I call relation from Model or use eager constraints, relation is empty. When I comment(remove) constraint select(['id']) - results are present, but with all users fields.
$query = Supplier::with(['test_staff_id_only' => function ($query) {
//$query->where('id',8); // works only for testing https://laravel.com/docs/6.x/eloquent-relationships#constraining-eager-loads
// option 1
$query->select(['id']); // not working , no results in // "test_staff_id_only": []
// option 2
//$query->raw('select id from users'); // results with all fields from users table
}])->first();
return $query;
In Supplier model:
public function test_staff_id_only(){
return $this->hasMany(User::class,'supplier_id','id')
//option 3 - if enabled, no results in this relation
->select(['id']);// also tried: ->selectRaw('users.id as uid from users') and ->select('users.id')
}
How can I select only id from users?
in you relation remove select(['id'])
public function test_staff_id_only(){
return $this->hasMany(User::class,'supplier_id','id');
}
now in your code:
$query = Supplier::with(['test_staff_id_only:id,supplier_id'])->first();
There's a pretty simple answer actually. Define your relationship as:
public function users(){
return $this->hasMany(User::class, 'supplier_id', 'id');
}
Now, if you call Supplier::with('users')->get(), you'll get a list of all suppliers with their users, which is close, but a bit bloated. To limit the columns returned in the relationship, use the : modifier:
$suppliersWithUserIds = Supplier::with('users:id')->get();
Now, you will have a list of Supplier models, and each $supplier->users value will only contain the ID.

Laravel eloquent get data from two tables

Laravel Eloquent Query builder problem
Hello I have a problem when I am trying to get all the rows where slug = $slug.
I will explain in more details:
I have two tables, cards and card_categories.
cards has category_id.
card_categories has slug.
What I need is a query which returns all the cards which contents the slugs.
For example I made this:
$cards = Card::leftJoin('card_categories', 'cards.id', '=', 'card_categories.id')
->select('cards.*', 'card_categories.*')
->where('card_categories.slug', $slug)
->paginate(5);
But what happens is that just return 1 row per category.
I don't know what is wrong.
Many thanks.
I think I understand what you mean, from your explanation I would imagine your card model is as follows.
class Card extends Model {
protected $table = 'cards';
public function category()
{
return $this->belongsTo(Category::class)
}
}
In which case, you should just be able to do this:
$cards = Card::whereHas('category', function ($query) use ($slug) {
$query->where('slug', $slug);
})->paginate(5);
That will select all of the cards that has the category id of the given slug.

How to achieve this complex query with Eloquent?

I have this query below to get the email of all users that have a registration in a specific conference.
$users = User::whereHas('registrations', function ($query) use($conferenceID) {
$query->where('conference_id', '=', $$conferenceID);
})->get();
And dd($users) shows array:2 [▼ 0 => "....mail.com" 1 => "....mail.com"]. So its working fine.
But I want to get only the emails of the users that did a registration in a specific conference ($conferenceID) and this registration has 1 or more participants associated with a specific registration type.
Do you know how to properly achieve this query? I have the code below but it shows "Call to undefined relationship [Registration] on model [App\Conference]".
$getEmailsOfAspecificRegistrationType =
Conference::with('registrations',
'Registration.registrationTypes', 'Registration.registrationTypes.participants')
->where('id', $id)->get();
Table structure and example to explain better the context:
For example there is a conference "Conference 1" that has two registration types (rt1 and rt2). And for now there are only two registrations in the conference "Conference 1". One registration was done by Jake and other by Ben. Jake did a registration with 3 participants (him, Jane and Paul). Ben only did a registration for him. The tables structure is like below.
I want to get only the emails of the users that did a registration in the conference with id "1" and this registration has 1 or more participants associated with a specific registration type, for example "rt01". So in this case I only want to get the email of the user 1 "jakeemailtest" because is the only participant that did a registration that is associated with the registration type "rt01" because the participants Jake and Jane are associated with this registration type (as is possible to check in the participants table).
Conferentes table:
id name
1 Conference 1
Users table:
id name email
1 Jake jakeemailtest#test...
2 Ben benmailtest#test...
registration types table:
id name conference_id
1 rt1 1
2 rt2 1
Participants table:
id registration_id registration_type_id name
1 1 1 Jake
2 1 1 Jane
3 1 2 Paul
4 2 2 Ben
And the registrations table:
id status user_that_did_registration conference_id
1 Incomplete 1 1
2 Incomplete 2 1
Relevant models for the question:
Users model:
public function registrations(){
return $this->hasMany('App\Registration','user_that_did_registration');
}
Registration model:
// user that did the registration
public function customer(){
return $this->belongsTo(User::class, 'user_that_did_registration', 'id');
}
public function participants(){
return $this->hasMany('App\Participant');
}
public function conference(){
return $this->belongsTo('App\Conference');
}
Conference Model:
public function registrations(){
return $this->hasMany('App\Registration', 'conference_id');
}
Participants mode:
public function registration(){
return $this->belongsTo('App\Registration');
}
public function registration_type(){
return $this->belongsTo('App\RegistrationType');
}
According to your schema it looks like your model definitions should look like
User has Registrations => Registration belongs to User
Registration has Participants => Participant belongs to Registration
Registration Type has Participants => Participant belongs to Registration Type
Conference Has Registration Types => Registration Type belongs to Conference
Conference Has Registrations => Registration belongs to Conference
Considering above your Conference model should also have following mapping
public function registration_types(){
return $this->hasMany('App\RegistrationType', 'conference_id');
}
You could go with following to get the desired users
$users = User::whereHas('registrations.participants.registration_type', function ($query) use ($conference_id, $request) {
$query->where('name', '=', $request->rype)
->where('conference_id', '=', $conference_id);
})->whereHas('registrations', function ($query) use ($conference_id) {
$query->where('conference_id', '=', $conference_id);
})->get(); // use pluck('email') instead of get to select only email
You are specifying wrong relationship names. Strings that you provide in with() method should match the relationship methods name on model. The correct way to eager load relationships is:
$getEmailsOfAspecificRegistrationType = Conference::with(
'registrations.participants'
) ->where('id', $id)->get();
Also note that, you dont need to load each relation explicitly. When you eager load nested relationships, higher order relations are automatically loaded.

Resources