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

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.

Related

ManyToMany with and whereIn

I have a ManyToMany relationship between AdInterest and AdInterestGroup models, with a belongsToMany() method in each model so I can use dynamic properties:
AdInterest->groups
AdInterestGroup->interests
I can find all the "interests" in a single group like this:
$interests = AdInterestGroup::find(1)->interests->pluck('foo');
What I need is a merged, deduplicated array of the related 'foo' field from multiple groups.
I imagine I can deduplicate with ->unique(), but first, as you'd expect, this:
AdInterestGroup::whereIn('id',[1,2])->interests->get();
throws:
Property [interests] does not exist on the Eloquent builder instance.
The advice seems to be to use eager loading via with():
AdInterestGroup::with('interests')->whereIn('id',[1,2])->get();
Firstly, as you'd expect that's giving me an array of two values though (one for each ID).
Also, if I try and pluck('foo') again, it's looking in the wrong database table: from the AdInterestGroup table, rather than the relationship (AdInterest).
Is there a nice, neat Collection method / pipeline I can use to combine the data and get access to the relationship fields?
Use pluck() and flatten():
$groups = AdInterestGroup::with('interests')->whereIn('id', [1, 2])->get();
$interests = $groups->pluck('interests')->flatten();
$foos = $interests->pluck('foo')->unique();

HasManyThrough Relations includes a Pivot

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.

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...

Eloquent Relationship Between Two Unions

I have four models. TableFoo and TableFooArchive share a common schema (items for TableFoo are periodically moved to TableFooArchive). I also have TableBar and TableBarArchive. TableFoo may or may not have one related row in TableBar or TableBarArchive. TableFooArchive may or may not have one related row in TableBarArchive.
I'd like to be able, using Eloquent, to union TableFoo and TableFooArchive then eager load the union of TableBar and TableBarArchive. If I only had TableFoo and TableBar, I could just set up a hasOne on the model and be done. How can I achieve the same with all four tables?
My start is to have a repository with the following:
public function getData($id)
{
$a = TableFoo::selectRaw('
columnA,
columnB as column_b_abc,
columnB as colum_b_def,
created_at
')
->whereRaw("
id = $id,
and so forth...
");
$b = TableFooArchive::selectRaw('
columnA,
columnB as column_b_abc,
columnB as colum_b_def,
created_at
')
->whereRaw("
id = $id,
and so forth...
")
->unionAll($a)
->orderBy('created_at', 'DESC')
->get();
return $b;
}
My guess is that it's possible to define a method on each model that returns TableBarTableBarArchive. I'm just not sure how I would do that nor how I would pull it through on the above using ->with('unionedResults'). FWIW, I can't change the table structure to use two instead of four.
I feel like there's probably an obvious and simple solution that just isn't occurring to me at the moment. Any help or insight would be appreciated!
UPDATE:
What I was looking for was to lazy load the relationship. The example above achieves this provided you have the correct methods defined on the related models.
It would still be interesting to see an example of eager loading the whole thing.

Laravel whereDoesntHave() - multiple OR conditions

In Laravel 4.2 I have a model called Product with many-to-many relationshis to other models like Country or Category. I want to filter out products that are "incomplete", which means they have no connected countries or no connected categories. I can use whereDoesntHave() method to filter out one relation. When I use it two times in one query it creates AND condition, but I need OR. I can't find orWhereDoesntHave() method in API documentation. I can't pass multiple relations as arguments because it expects first argument to be a string.
I need something like this:
$products = Product::whereDoesntHave('categories')->orWhereDoesntHave('countries')->get();
Is there any way to achive whereDoesntHave() with multiple OR conditions?
You can use doesntHave and specify the boolean operator:
$products = Product::doesntHave('categories')->doesntHave('countries', 'or')->get();
Actually you only need whereDoesntHave if you want to pass in a closure to filter the related models before checking if any of them exist. In case you want to do that you can pass the closure as third argument:
$products = Product::doesntHave('categories', 'or', function($q){
$q->where('active', false);
})->doesntHave('countries', 'or')->get();
Since Laravel 5.5 there is an orWhereDoesntHave function.
You may use it like this
Product::whereDoesntHave('categories', function($q){ //... })
->orWhereDoesntHave('countries', function($q){//...})
->get();
From you example it seems that you are not using a where clause, so you may just use
Product::doesntHave('categories')
->orDoesntHave('countries')
->get();
Use
Product::whereDoesntHave('categories')->doesntHave('countries', 'or')->get();
Laravel Source Code:
whereDoesntHave https://github.com/illuminate/database/blob/master/Eloquent/Builder.php#L654
calls
https://github.com/illuminate/database/blob/master/Eloquent/Builder.php#L628
internally.
Let’s say we have Authors and Books, with 1-n relationship – one Author can have one or many Books. Here’s how it looks in app\Author.php:
public function books()
{
return $this->hasMany(\App\Book::class, 'author_id');
}
Now, what if we want to show only those Authors that have at least one book? Simple, there’s method has():
$authors = Author::has('books')->get();
Similarly, there’s an opposite method – what if we want to query only the authors without any books? Use doesnthave():
$authors = Author::doesnthave('books')->get();
It’s not only convenient, but also super-easy to read and understand, even if you’re not a Laravel developer, right?

Resources