HasManyThrough Relations includes a Pivot - laravel

I have a Module, Question and Category model.
Module hasMany Questions. (1 to Many)
Question belongsToMany Categories. (Many to Many)
For a given Module, I would like to access only the Questions where category_id = X .
I'm not sure what the most efficient way of doing this is. Can I do it through a HasManyThrough relation between Module and Category? Or do I have to create a loop? Or do it through a raw SQL query?
Update:
This SQL query seems to work. However, I'm sure there must be a more elegant solution?
SELECT id
FROM questions
INNER JOIN category_question ON questions.id = category_question.question_id
WHERE category_question.category_id = X and module_id = Y;

You can achieve that using this
Question::with(['module','categories'])
->join('category_question','category_question.question_id','=','question.id')
->where('category_question.category_id','=','X')
->where('questions.module_id','=','module_id')->get();

Question::with(['model','categories'=>function($query){
return $query->where('category_question.category_id',$category_id);
}])->get();
hope this works.

Related

Laravel Eloquent 'WITH' statement query

How to convert this mysql query to Eloquent
with wmh as (select * from whatsapp_message_histories ORDER BY created_at LIMIT 1)
select DISTINCT whatsapp_histories.*, wmh.created_at from whatsapp_histories
left join wmh on wmh.whatsapp_history_id = whatsapp_histories.id
ORDER BY wmh.created_at
Please Help me, thanks a lot!
(Edited)
My Problem is, I have Eloquent Model whatsapp_histories, has a relation to whatsapp_message_histories (using hasMany). My Objective is need to display whatsapp_histories order by whatsapp_message_histories.created_at. (This is eloquent, because i need to display other whatsapp_histories's relation). I can solve it in native way, but not in eloquent, but i'm not allowed to change the previous eloquent code
Try this:-
whm::with('whatsapp_histories',function($join){
$join->on('wmh.whatsapp_history_id','=','whatsapp_histories.id');
$join->orderBy('whatsapp_histories.created_at','ASC');
$join->limit(1);
})->orderBy('wmh.created_at','ASC)->get();

show rows with a relation before in Laravel

in a Laravel application, I have a list of companies. Some of those are related to a subscriptions table in a 1 x n relation.
I want to order the companies in a way that the ones which have a subscription appear before the ones which have no subscription using just one db query.
Any idea is much appreciated!
thanks!
Laravel uses separate queries to eager load relationships. So if you want to do this you will need to join the tables and order by...
Something in the form...
$companies = App\Companies::join('subscriptions as sub', 'sub.company_id', '=', 'company.id')
->orderBy('sub.somefield', 'DESC')
->select(what you need)
->with('if you need the relation data');
You know you can also only query those records that have a relationship with
$companies = App\Companies::has('subscription')->get();
Maybe that is all you need... and
$companies = App\Companies::doesntHave('subscription')->get();
... returns the opposite where the company has no subscription...

Laravel hasOne + group_by, get sum

I'm currently scratching my head with this one.
What I have.
$entries = Meta::whereIn('settlement_id', [1])->groupBy('client_id')->get();
Meta::model() has a hasOne relation with other tables, in my example with a Sport table. With one attribute in my array I can achieve what I want.
So in a loop I have my field available $entry->Sport->amount this works perfectly.
What I need.
$entries = Meta::whereIn('settlement_id', [1,2,3])->groupBy('client_id')->get();
Now when I expand my array with multiple id's, I expect that $entry->Sport->amount returns the sum of all id's. But it doesn't.
I can't figure out what i'm doing wrong.
All help is appreciated.
Your hasOne relation means that each Meta will have a separate Sport, so when you loop $entries each $entry->Sport will have the amount related to the current Meta, not a sum of all of them.
If you want to have the sum of Sport amount for specific Metas it might be easier to make a query from the Sport model, something like this (I don't know how your relations is setup so this is just an example):
Sport::whereIn('meta_id', [1,2,3])->sum('amount');
Thats correct, is there a way that I can achieve this? By changing hasOne to something else?
I don't want to query each specific related Meta table.
public function Sport()
{
return $this->hasOne('App\Sport', 'settlement_meta_id');
}
my database table Sport looks like this:
protected $fillable = [
'settlement_meta_id',
'bet',
'payout'
];

Laravel, Many-to-many relationships, find() vs where()

I have a Laravel 4.2 site with a pretty simple database layout. The important part is a People model and a Subject model that have a many-to-many relationship. This works, so that, for instance:
$id = 5;
$ppl = Subject::find($id)->people()->orderBy('lastname')->get();
Returns all People for a given Subject. What I'm trying to do is instead of finding all the People for a single subject, to find all the people for multiple subjects. My guess was something like this:
$subjects = array(5, 6, 7);
$ppl = Subject::whereIn('id', $subjects)->people()->orderBy('lastname')->get();
That doesn't work (undefined method people()). Neither does the following (undefined property people):
$ppl = Subject::whereIn('id', $subjects)->people->orderBy('lastname')->get();
I'm currently just using raw SQL t get around this. How can I use eloquent relationships with where() or whereIn() calls on a model? Or, is there just a better eloquent way of approaching this problem.
Edit: Here's the raw SQL I used to get a list of the people.id's for a given array of subjects:
SELECT
DISTINCT(people.id)
FROM people
LEFT JOIN person_subject ON person_subject.person_id=people.id
WHERE
person_subject.subject_id IN (%s) AND
deleted_at IS NULL
Your eloquent relationships should allow you to eagar load the people related to a subject, you can do something like this:
Subject::whereIn('id', $subjects)->with('people')->orderBy('lastname')->get();
This will return your people related to the subjects.
Let me know if this doesn't work.
What you need is to join the tables to order by the lastname:
$ppl = Subject::whereIn('id', $subjects)
->select('subjects.*')
->distinct()
->join('people', 'people.id', '=', 'subjects.people_id')
->orderBy('lastname')
->get();
Check all your tables names because I don't know them and above are just an example one.

Laravel Many to Many - 3 models

Some help with many to many relationships in Laravel:
Using the example for roles and users - basically:
a table for all the roles
a table for the users
and table with user_id and role_id.
I want to add to the third table, eg Year. basically the pivot table will have user_id, role_id and year_id.
I want to be able to make a query to pull for example all users assigned a specific role in a specific year. Eg All users with role_id = 2, and year_id = 1.
Any help will be appreciated
Before answering, I would like to suggest you not to put year on database like this.
All your tables should have created_at and updated_at which should be enough for that.
To filter users like you want. You could do this:
// This queries all users that were assigned to 'admin' role within 2013.
User::join('role_users', 'role_users.user_id', '=', 'users.id')
->join('roles', 'roles.id', '=', 'role_users.role_id')
->where('roles.name', '=', 'admin')
->where(DB::raw('YEAR(role_users.created_at)', '=', '2013')
->get();
This example may not be the precise query you are looking for, but should be enough for you to come up with it.
The best way to achieve a three way relation with Eloquent is to create a model for the table representing this relation. Pivot tables is meant to be used for two way relations.
You could have then a table called roles_users_year which could have data related to this 3 way relation like a timestamp or whatever...
A very late answer to a very old question, but Laravel has supported additional intermediate (pivot) table columns of at least Laravel 5.1 judging from the documentation, which hasn't changed at least through Laravel 6.x.
You can describe these extra columns when defining your many-to-many relationship:
return $this->belongsToMany(Role::class)->withPivot('column1', 'column2');
or in your case, the below would also do the job:
return $this->belongsToMany(Role::class)->withTimestamps();
which you can then access via the pivot attribute on your model:
$user = User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
Note that the pivot attribute is on the distant relationship model (a single Role) and not on the relationship itself.
To get all the Roles assigned to Users in any given year, you might create a special relationship:
// User.php
public function rolesInYear($year) {
return $this->belongsToMany(Role::class)
->wherePivot('created_at', '>=', Carbon::create($year))
->wherePivot('created_at', '<', Carbon::create($year + 1));
}

Resources