Eloquent query if value in relationship isn't same as value in other relationship - laravel

I have four models: Item, Car, ItemLocation and Branch.
An Item has a Car and a ItemLocation via a person_id and a item_location_id field in the DB.
A Car has a branch_id which links to Branch, and an ItemLocation also has a branch_id in the same way.
What I want to do is to select all Items where their Cars's branch is not equal to their ItemLocation's branch.
I tried this statement, though I knew that it wouldn't work:
Item::with('car','item_location')
->where('car.branch_id', '<>', 'item_location.branch_id')
->get();
I'm aware of querying on relationships, but don't understand how to do that across relationships like this.
Any ideas?

Querying relationships won't help you, since you want to compare values from separate tables. You need joins:
$items = Item::select('items.*')
->join('item_locations as il', 'il.id', '=', 'items.item_location_id')
->join('cars', function ($j) {
$j->on('cars.person_id', '=', 'items.id')
->on('cars.branch_id', '<>', 'il.branch_id');
})
->get()
This will fetch all the items having both cars and item_location and matching your criteria. If you want to include also ones that don't have either of the relations, then use leftJoins instead and whereNull('cars.id')
ps. It's hard to read your question. Instead of describing these relationships, better simply show the tables with relevant fields.

Related

Laravel how to query a pivot table created_at timestamp equal to a date

I have users and habits, and a habit_user table to join them.
I am querying like this:
$track = $h->userAnswers()->where('user_id', Auth::user()->id)->wherePivot('created_at', '=', Carbon\Carbon::now()->subDays($i))->first();
This is running in a loop that is counting back for 7 days. there is a record in the db that is created_at: 2018-10-23 04:48:44
In my habit model I have the method you'd expect:
public function userAnswers()
{
return $this->belongsToMany('App\Habit', 'habit_user_answers')->withTimestamps()->withPivot('answer_one', 'created_at')->orderBy('pivot_created_at', 'desc');
}
Why won't query get a record?
You are comparing the date time so only if both date and time is same, the query will throw a result.
You can compare dates like so:
wherePivot('created_at', '>=', Carbon\Carbon::now()->subDays($i)->startOfDay())->wherePivot('created_at', '<=', Carbon\Carbon::now()->subDays($i)->endOfDay())
First, I think you need to consider Laravel conventions about naming methods and properties.
I'll assume the following based on your structure that includes users and habits. So, we have a User model and a Habit model, a user belongsToMany habits and a habit belongsToMany users. Also the pivot table habit_user contains extra fields like answer_one, answer_created_at and timestamps.
If you want now to query the habits now you have two solutions:
1- using wherePivot()
auth()->user()->habits()->wherePivot('answer_created_at', today())->get();
auth()->user()->habits()->wherePivot('answer_one', '!=', 'something')->get();
2- using whereHas()
auth()->user()->whereHas('habits', function($query){
$query->where('pivot.answer_one', 'something');
})->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 Eloquent - How Do You Add a Where Condition to a Distantly Related Model Using the ORM?

I currently have the following:
Cars::with('cases')->with(['parts', 'parts.engines', 'parts.engines.metals'])
->orderBy('car_name', 'DESC')->orderBy('id', 'DESC');
The above will list all rows in my cars table along with the metal associated with the engine in each of those cars. The metals table is related to the cars table through the parts and then the engines tables.
I've tried using:
Cars::with('cases')->whereHas(['parts', 'parts.engines', 'parts.engines.metals'], function($query){
$query->where('weight', '=', 45)
})->orderBy('car_name', 'DESC')->orderBy('id', 'DESC');
But this errors out since whereHas() does not accept an array for its first parameter and I don't see a way to link to distant relationships with it.
How do I apply a WHERE conditional on a column in the metals table using the built-in ORM?
whereHas() only needs the name of the relationship for which you'd like to add the conditions. So, if you're trying to add the condition to the metals, you just need to restrict the parts.engines.metals relationship.
On a side note, when you eager load nested relationships, you don't need to also specify to load the intermediate relationship. That is, when you eager load parts.engines, you don't need to also eager load parts.
So, your query would look something like:
Cars::with(['cases', 'parts.engines.metals'])
->whereHas('parts.engines.metals', function($query) {
$query->where('weight', '=', 45)
})
->orderBy('car_name', 'DESC')
->orderBy('id', 'DESC');
This query will only retrieve cars that have a related metal with a weight of 45. Additionally, for those cars that are retrieved, it will also eager load all of the cases, parts, engines, and metals related to those cars.
I think you mean this:
Cars::with(['cases', 'parts', 'parts.engines', 'parts.engines.metals' => function($query){
$query->where('weight', '=', 45);
}])->orderBy('car_name', 'DESC')->orderBy('id', 'DESC');

Laravel3 Eloquent: Query for attribute in Many-To-Many Relation

This Problem blows my mind at the moment:
I have the following relation:
Groups --> Intermediate Many-To-Many Table <-- Cities (Every Group can have multiple Cities assigned)
Now I want to get all the Group-Models where the assigned City is either id X and id Y.
Like Saying "get me all the groups which are assigned to Boston and New York"
Is that possible with Laravel 3's Eloquent?
Thank you very much!
Matthias
In my experience, where clauses do not work inside of many-to-many relationships. But they do work if you eager load them.
Group::with(array('cities' => function($q) {
$q->or_where('id', '=', X);
$q->or_where('id', '=', Y);
})->get();
Extending aowie1's answer above (which is correct), it's often useful to nest or_where clauses (as this makes logical sense, especially if you're looking to expand the query with non-or-where conditions):
Group::with(array('cities' => function($q) {
$q->where(function($where) {
$where->or_where('id', '=', 'x');
$where->or_where('id', '=', 'y');
});
})->get();
Again, just expanding aowie1's answer, which I've upvoted, as it's correct - this is just some additional info regarding nested queries with or conditions :)

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