Retrieving distinct relationships of relationships using Eloquent in Laravel - php

Lets assume I have a model for: Race, Participant, Team
And these relations: Race 1-N Participant N-1 Team
See in another way :
races 1 - N participants
teams 1 - N participants
In Laravel terms:
/* Inside Race model */
public function participants()
{
return $this->hasMany(Participant::class);
}
/* Inside Team model */
public function participants()
{
return $this->hasMany(Participant::class);
}
/* Inside Participant model */
public function race()
{
return $this->belongsTo(Race::class);
}
public function team()
{
return $this->belongsTo(Team::class);
}
In the participants table, It looks like this:
id | team_id | race_id | [a lot of other columns...]
------------------------------
1 | 1 | 1 |
2 | 1 | 1 |
3 | 2 | 1 |
4 | 2 | 1 |
In the above example, I know that the race ID 1 has 2 teams.
I can count them by doing this:
$race = Race::find(1);
$number = $race->participants()->distinct()->count('team_id');
The problem
Counting the number of teams is cool, but I want to access to the list of corresponding Team model instances, so that I can use them for further operations (within a foreach loop for example).
I tried a lot of things without success.
Something like this:
$teams = $race->participants()->distinct()->[...] // Don't know what to put here
The working equivalent SQL query is:
SELECT teams.* FROM teams
INNER JOIN participants ON participants.team_id = teams.id
INNER JOIN races ON races.id = participants.race_id
WHERE races.id = 1
GROUP BY teams.id
Resulting in this:
When the participants table contains this:
I want to know if I can do it with Eloquent instead of using Query/Builder DB:: methods directly ?
EDIT 1
My closest result:
$race->participants()->distinct()->get('team_id')
And then using this list of team_id I can access to the teams using Team::find([list of IDs]) but it looks greedy to me.
EDIT 2
I forgot some information:
The participants.team_id column is NULLABLE because:
There are races with teams of participants
There are races with participants (without team)

I don't think you can easily access to it through the $race instance.
But, you can use the whereHas method on the Team model:
$race = Race::find(1);
$teams = Team::whereHas('participants', function ($query) use ($race) {
$query->where('race_id', $race->id);
})->get(); // ->count() also works
See documentation

To get distinct teams from a race model, you could add a many to many relationship between Race and Team with Participant acting as the pivot.
For example,
/* Race */
public function teams()
{
return $this->belongsToMany(Team::class, 'participants', 'race_id', 'team_id');
}
/* Team */
public function races()
{
return $this->belongsToMany(Race::class,'participants', 'team_id', 'race_id');
}
Then, you could just do this
$race = Race::find($id);
$teams = $race->teams()->distinct()->get();
to get a list of distinct teams for the race.
And since we set up the relationship on both models, you can now also get distinct races for each team by doing
$team = Team::find($id);
$teams = $team->races()->distinct()->get();

First, let's improve your eloquent relations.
// Race
public function teams()
{
return $this->hasMany(Team::class);
}
public function withoutTeamParticipants() // change name that suits you
{
return $this->hasMany(Participant::class);
}
// Team
public function participants()
{
return $this->hasMany(Participant::class); // In this case make sure that participant.race_id is null
}
// Participant
public function team()
{
return $this->belongsTo(Team::class); // When no team, then participant.team_id is null
}
public function race()
{
return $this->belongsTo(Race::class);
}
To access list of participants of a given race:
$race = Race::where('id', 1)->with('teams.participants', 'withoutTeamParticipants')->get();
In your blade you can further use this as:
// List of participants without a team
#foreach ($race->withoutTeamParticipants as $participant)
{{ $participant->name }}
#endforeach
// List of participants that are with a team
#foreach ($race->teams as $team)
#foreach ($team->participants as $participant)
{{ $participant->name }}
#endforeach
#endforeach

You can directly use hasManyThrough relationship inside Race model for fetching distinct teams associated with the race.
public function distinctTeams()
{
return $this->hasManyThrough(Team::class, Participant::class, 'race_id', 'id', 'id', 'team_id')->distinct();
}
Now, you can loop over the teams of a race by using $race->distinctTeams
Hopefully this helps.

Related

Get only 3 records using with from many to many relationship Laravel

I'm using laravel 7 and
I have 3 tables shown below. I want first three students data instead of all student. tables have many to many relationship.
groups
id
name
students
id
name
group_student_pivot
group_id
student_id
created_at
I have below relationship in models
Groups model
public function students()
{
return $this->belongsToMany(Student::class,'group_student_pivot')->withPivot(['status'])->withTimestamps();
}
Student model
public function groups()
{
return $this->belongsToMany(Group::class,'group_student_pivot')->withPivot(['status'])->withTimestamps();
}
$groups = Group::whereIn('id',$groupIds)->with('students')->get();
In above query I want first 3 students data instead of all students.
You can get 3 records like this:
$groups = Group::whereIn('id',$groupIds)->with('students', function($q){
$q->take(3);
})->get();
You can use with and whereHas method
$groups = Group::with('students')->whereHas('students', function($q){
$q->take(3);
})->whereIn('id',$groupIds)->get();

laravel get relationship with multiple foriegn keys

I have three tables
Teams
id | Name
Winners
id | user_id | league_id
team_user
team_id | user_id | league_id
What I am trying to do in my Winner.php model is create a relationship that ties a winner to a team via the user_id and league_id
So basically I can call Winner->team and it will return the Team that the Winner belongs to where the user_id and league_id all match.
I was thinking
public function team()
{
return $this->belongsTo('App\Team', 'user_id', 'league_id');
}
But that doesnt work obviously. I am scratching my head since I have all the info I need to get the team associate to that league_id and user_id.
if your winner model returns multiple team use below code:
public function teams()
{
return $this->belongsToMany('App\Team', 'user_id', 'league_id');
}
if your winner model returns only one team use below code:
public function team()
{
return $this->belongsTo('App\Team', 'user_id', 'league_id');
}
Note:Here 3rd parameter in belogsTo() specifying your parent table's custom key
You can attach additional where() clause:
public function team()
{
return $this->belongsTo('App\Team', 'user_id', 'user_id')->where('league_id', $this->league_id);
}
Note: This will work with lazy loading only. Look here, it may help you.
I just used this as a function and not a relationship and looped through in my controller. not the best but all I got to work:
Winner.php
public function team()
{
$team_id = DB::table('team_user')
->where('league_id', $this->league_id )
->where('user_id', $this->user_id )
->first()
->team_id;
$team = Team::find( $team_id );
return $team;
}
In my controller
foreach( $league->winners as $winner)
{
$winner['team'] = $winner->team();
}

Laravel 5.1 - Merge multiple Eloquent queries and sort by timestamp

I have three tables, a Users table, a Jobs table and a Bids table. Users can post multiple Jobs and Users can post a bid on Jobs which are not their's.
My Goal: I'm trying to query the Jobs table to find all of a users jobs and query the Bids table to get all of the jobs that a user is bidding on. Then I would like to sort the jobs and bids based on their timestamps and return job IDs
The tables are setup as follows:
| USERS | JOBS | BIDS
| id | id | id
| username | user_id | user_id
| password | title | job_id
| | | bid_amount
The relationships between the tables are as follows:
Jobs:
public function bids()
{
return $this->hasMany('App\RocketCandy\Repos\Bids\Bid');
}
public function user()
{
return $this->belongsTo('App\RocketCandy\Repos\Users\User');
}
Users:
public function jobs()
{
return $this->hasMany('App\RocketCandy\Repos\Jobs\Job');
}
public function bids()
{
return $this->hasMany('App\RocketCandy\Repos\Bids\Bid');
}
Bids:
public function jobs()
{
return $this->belongsTo('App\RocketCandy\Repos\Jobs\Job');
}
public function users()
{
return $this->belongsTo('App\RocketCandy\Repos\Users\User');
}
The following code I've developed returns the jobs and the bids but it's not clever enough to sort the IDs based on the updated_at timestamp.
public function getUsersJobs($userId)
{
// Get the jobs that the user posted
$postedJobs = Job::where('user_id', $userId)
->lists('id')
->toArray();
// Get the jobs that the user is bidding on
$biddedJobs = Job::with('bids')
->whereHas('bids', function ($q) use ($userId) {
$q->where('user_id', $userId);
})->lists('id')
->toArray();
$jobIds = array_merge($postedJobs, $biddedJobs);
// Return the jobs that a user has posted and is bidding on
return Job::whereIn('id', $jobIds)
->OrderBy('updated_at', 'DESC')
->get();
}
I think you can simplify it a lot by looking at it from the User instead of the Job:
$jobs = User::where('user_id', $userId)->jobs;
$bids = User::where('user_id', $userId)->bids;
$all = $jobs->toBase()->merge($bids)->sortByDesc('updated_at');
foreach ($all as $thing) {
dump($thing->id);
dump($thing->updated_at);
}

laravel eloquent relationships queries

I have two tables 1)users
{ id, password }
2)expertise { id, expertise}
the relationship I have is
Models
Expertise.php
function User()
{
$this->hasOne('Expertise');
}
User.php
function Expertise()
{
$this->hasOne('User');
}
So how can I query using Eloquent to get the first 10 users with a certain expertise?
I want to join users.id = expertise.id and get the first 10 people with a specified expertise (Where clause).
Beginner to laravel, I've checked other sources but was not successful
Right now you are having a problem with the way that you modeled your data. If you have a one-to-one relationship the best practice to model it is to have one entity store the id of the other. The Laravel convention for this is to have a column named <model>_id:
Users
| id | password |
Expertises
| id | expertise | user_id |
Then in your models you can do this:
Models
Expertise.php
class Expertise extends Eloquent
{
public function User()
{
// because expertise has a column user_id
// expertise belongs to user
return $this->belongsTo('User');
}
}
User.php
class User extends Eloquent
{
public function Expertise()
{
// because expertise is the one with the column
// user_id, user has one expertise
return $this->hasOne('Expertise');
}
}
The Query
After you have all this set up, to be able to query the first 10 users with a certain expertise you can do this.
$users = User::whereHas('Expertise', function($q)
{
$q->where('expertise', '=', <expertise you are looking for>)
})
->take(10)
->get();
To get a further reading in querying relationships in Laravel please take a look at this:
Laravel - Querying Relationships
Keep in mind
keep in mind that the tables name must be plural, if not then you should specify the name of the table inside the model:
protected $table = 'expertise';

Counting related rows in a child table

I have been trying to do some queries and getting a count on related tables using eloquent.
Tables:
requests
contact (belongs to requests)
history (belongs to contact)
As such X number of requests each have Y number of contacts which in term each have Z number of histories
Using sql I can do something like this to get all the counts.
SELECT
id,
(
SELECT count(contact.id)
FROM contact
WHERE contact.requests_id = requests.id
) AS n_contact,
(
SELECT count(history.id)
FROM contact INNER JOIN history ON (history.contact_id = contact.id)
WHERE contact.requests_id = requests.id
) AS n_history
FROM requests;
But I am a bit lost when using eloquent to build queries. If for instance I was selecting all contacts for a given request at what point would I join/count the history? Or do I need to add in some accessor's into the relevant Models for these 3 tables?
public function getAllContacts($id) {
return Requests::where('requests.id', '=', $id)
->join('requests', 'contact.requests_id', '=', 'requests.id')
->select('contact.*', 'requests.name');
->get();
}
Thanks in advance for any help.
You can use helper relation for this, if you'd like to use Eloquent instead of manual joins:
// Request model
public function contactsCount()
{
return $this->hasOne('Contact')->selectRaw('request_id, count(*) as aggregate')->groupBy('request_id');
}
public function getContactsCountAttribute()
{
if ( ! array_key_exists('contactsCount', $this->relations)) $this->load('contactsCount');
return $this->getRelation('contactsCount')->aggregate;
}
The same would go for Contact model towards History model.
For counting far relation (Request -> History) you can use hasManyThrough relation with a little adjustment.
This way you can eager load those aggregates for multiple models without n+1 issue, nice and easy:
$requests = Request::with('contactsCount', 'contacts.historyCount')->get();
// 1 query for reuqests, 1 query for contacts count and 2 queries for contacts.historyCount
// example output
$requests->first()->contactsCount; // 17
$requests->first()->contacts->first()->historyCount; // 5
/* Make Relation in the Request Model */
public function contacts()
{
return $this->hasMany('App\Model\Contact', 'request_id', 'id');
}
/* use withCount() to get the total numner of contacts */
public function getAllContacts($id) {
return Requests::with('contacts')
->withCount('contacts')
->find($id);
}

Categories