Laravel: Get not related items in a many to many relation - php

Is it possible to get all items not related to the specific instance, in a many to many relation in Laravel?
Example:
I have two models, User and Pet. They have a many to many relationship, because a User can have many pets, but a pet can belong to more users.
But I want a list of pets, which a specific user is not having.
How is that possible?
EDIT
The above is just an example, so not related to my current task. Just more simple to explain with users and pets. But to specify:
PETS TABLE:
ID TYPE
1 Dog
2 Cat
3 Rabit
4 Horse
5 Rat
6 Bat
7 Bird
USERS TABLE:
ID NAME
1 Martin
2 Peter
3 Viggo
PET_USER TABLE:
ID PET USER
1 1 1
2 3 1
3 5 1
Then Martin has a dog, a rabbit and a rat.
But I want a list of pets, that he does not have:
Cat, horse, bat and bird

You may try this (your question sounds a little confusing to me tho):
$pets = Pet::with('users')->whereDoesntHave('users', function($query) use ($id) {
$query->where('users.id', $id);
})->get();
Also, try this as well (if you want):
$pets = Pet::with('users')->whereHas('users', function($query) use ($id) {
$query->where('users.id', '!=', $id);
})->get();

There is no function from laravel to run this out of the box.
You have to join this manually:
$pets = Pet::leftJoin('pet_user', function($join) use ($userId){
$join->on('pets.id', '=', 'pet_user.pet_id');
$join->on('pet_user.user_id', '=', \DB::raw("'".$userId."'"));
})->whereNull('pet_user.pet_id')->get();

i would suggest this method if you already defined ManyToMany relationship on your models :
$user = User::find($user_id);
$pets = new Pet;
$exception = $user->profile->pluck('id')->toArray();
$pets = $pets->whereNotIn('id',$exception)->get();

My Solution: worked for me atleast.
Just use :
Pet::whereDoesntHave('user');
or
Pet::whereDoesntHave('user', function($qu) {
// ...(extended query filter logic)
});

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. OrderBy query with last item hasMany relationship

need some help with a query on Laravel(PHP)
what I have :
Two tables. Expedients & ExpedientRevs with a hasMany relationship beetween them.
Expedient Model:
public function ExpedientRevs() {
return $this->hasMany('App\ExpedientRev');
}
What I need:
A list of the expedients (Distinct Expedient) order DESC by the last Expedients Revisions (last id of ExpedientRev)
I mean, if I have in ExpedientRev:
id expedient_id ..........
--- --------------
1 1
2 2
3 3
4 3
5 1
My result should be the expedients in this order : 1,3,2 (no duplicated expedients)
Any help will be appreciated
Thanks
Simplest way to do that;
$expedients = Expedient::orderByDesc(ExpedientRev::select('id')
->whereColumn('expedient_revs.expedient_id', 'expedients.id')
->latest()
->take(1)
)->get();
This will get the latest records by ExpedientRev model. If you want to include ExpedientRev in your results. You can use latestOfMany.
In your Expedients model add this one;
public function rev()
{
return $this->hasOne(ExpedientRev::class)->latestOfMany();
}
So you can use like this;
$expedients = Expedient::orderByDesc(ExpedientRev::select('id')
->whereColumn('expedient_revs.expedient_id', 'expedients.id')
->latest()
->take(1)
)->with('rev')->get();

How to print user email using vehicle user_id in Laravel 5.6

I am working with Laravel 5.6 and mysql in my app. and I am new to the Laravel. I have following table as vehicles,
id name number user_id
1 car 123 1
2 van 258 2
3 lorry 125 5
4 car 896 7
and user table like this,
id name email
1 hyu agt#mail.com
2 opv yyuj#mail.com
3 kio juik#mail.com
4 lop kiuf#mail.com
5 hyg htgd#mail.com
Now I need print email using above vehicles table user_id in my blade file, how can I do this?
Edit
I have following blade file to print vehicles table,
{{$vehicle->name}}
{{$vehicle->number}}
edit
my vehiclecontroller,
public function show($id)
{
$vehicles = Vehicle::find($id);
return view('vehicles.show')->withVehicles($vehicles);
}
and show blade file,
<div class="col-md-5" id="vl">
<div><label >Brand:</label>{{$vehicles->name}}</div>
<br>
<div><label >Model:</label>{{$vehicles->number}}</div>
<br>
If you have a relation in place on your Vehicle model which looks like this:
public function user()
{
return $this->belongsTo(User::class);
}
you can simply access the user property to get the email: {{ $vehicle->user->email }}
For improved performance, it makes sense to eager load the users when you fetch the vehicles from the database:
$vehicles = Vehicle::with('user')->get();
First take a look at this to create model for tables and then you can join it for both the tables and get user email too.
$vehicles = Vehicle::select('vehicles.name', 'vehicles.number', 'users.email')
->leftjoin('users', 'users.id', '=', 'vehicles.user_id')->get();
You can also use Laravel relations to achieve this.
$userEmails= DB::table('vehicles')
->join('contacts', 'user.id', '=', 'vehicles.user_id')
->select('users.email')
->get();
Try above code.
This is SQL join concept.
For your reference https://laravel.com/docs/5.7/queries#joins

Many to Many Relationship with 2 pivot table data search

Skills
-------
id name
1 PHP
2 Laravel
Qualifications
-----------------
id name
1 MBA
2 Graduate
3 Post Graduate
students
------------
id name
1 John
2 Smith
2 pivot tables
student_skills
student_id skill_id
1 1
1 2
2 1
student_qualifications
tutor_id qualification_id
1 2
1 2
2 2
In front-end the user can select multiple qualifications(checkboxes) and skills(checkboxes) according to the options selected we need to display the data.
For Example: How to get students with qualification graduate and MBA and skills Laravel and PHP
Here is an example on how you can do it ( it might be inaccurate due to the lack of details in your question but it should give you an idea)
(i think you better use a Mutli-select box instead of check-boxes for skills & qualification)
Assuming you want to search student by skills & qualification
And that you have the right Relations methods inside your models
//Don't forget to import Students model at top of the controller
public function whatever(Request $request)
{
$data = Student::with('qualifications', 'skills');
// assuming that the form will pass an array of skills names
//if the form passes an array of ID's of skills it becomes easier
if (!empty($request->skills)) {
foreach ($request->skills as $skill) {
$data->whereHas('skills', function ($query) use ($skill) {
$query->where('id', $skill);
})->get();
}
}
// same goes for qualifications
if (!empty($request->qualifications)) {
foreach ($request->qualifications as $qualification) {
$data->whereHas('qualifications', function ($query) use ($skill) {
$query->where('id', $qualification);
})->get();
}
}
// execute the query now
$data->get(); // or pagniate(10)
}
if you are passing an array of ID's instead of names
if (!empty($request->skills)) {
$data->whereHas('skills', function ($query) use ($skills) {
$query->whereIn('id', $skills);
})->get();
}
same goes for qualifications.
I hope you get the point here, you can Sort and stuff , this might not be the exact thing u need, but It will help you a lot if you get it.

Laravel Eloquent: Get record that contains two specific relations

I have a project where a user can create conversations with other users. A conversation can belongsToMany users and user can belongsToMany conversations.
I now need to get the conversation in which two specific users participate.
I tried a combination of solutions using whereIn and I tried the following:
$c = Conversation::whereHas('users', function($q)
{
$q->whereIn('user_id', array(1,3));
})
->get();
Here the problem is that whereIn('user_id', [1,3]) gets records that contains EITHER 1 or 3. I need it to return records that contains BOTH 1 and 3.
Conversation Model
class Conversation extends Model {
public function users(){
return $this->belongsToMany('App\User');
}
}
User Model
class User extends Model {
public function conversations(){
return $this->belongsToMany('App\Conversation');
}
}
Tables
conversations:
id | subject
conversation_user:
id | user_id | conversation_id
Data from table conversation_user
Your newest edit makes a lot more sense, this is actually a very easy fix. whereHas takes two additional parameters where it's going to look for the count.
$users = [1, 3];
$c = Conversation::whereHas('users', function($q) use ($users)
{
$q->whereIn('user_id', $users);
}, '>', count($users) )
->get();
This will get all conversations where user's 1 and 3 have participated in, even if there are additional users that have participated in those conversations. If you want only the conversations with only users 1 and 3, change the > to an =.
Edit: I just realized your pivot table has an id column. This method may not work if your pivot table is going to have duplicates. For example, if you have user_id of 1 in there twice with the same conversation_id both times, it will return that conversation even though it technically only has 1 user. I suggest removing the id column and creating a composite primary key of user_id and conversation_id. If there is the possibility of duplicates, it might be safer to use lukasgeiter's solution.
You are currently querying conversations which either user 1 and/or 3 takes part in. To achieve what you want you need two whereHas calls:
$c = Conversation::whereHas('users', function($q)
{
$q->where('user_id', 1);
})
->whereHas('users', function($q)
{
$q->where('user_id', 3);
}
->get();
And if you have more than two users, add them in a loop:
$users = [1, 2, 3, 4, 5];
$c = Conversation::query();
foreach($users as $userId){
$c->whereHas('users', function($q) use ($userId)
{
$q->where('user_id', $userId);
});
}
$c = $c->get();
I hope this will help you...
$userIds = array(1,3);
$c = Conversation::whereHas('users', function($q) use ($userIds)
{
$q->whereIn('user_id', $userIds);
})
->get();

Categories