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

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 :)

Related

Laravel Eloquent: Getting sum of data in nested relations and adding it as a new attribute

I want to find a single query for a complex problem (and an even more complicated one). Hope someone can help me.
I've an orders table with many products (many to many relationship). You can order products and select a quantity, which is saved in the pivot table.
I know I can get all orders with their products with
$query = Order::with(['products'=>function($q){
$q->select('name', 'price', 'quantity', 'unit')->orderBy('name','asc');
}]);
Now what I want to retrieve is an array of objects containing the name of every product only once with the sum of their quantity for all orders.
In this case, the products represent types of meat. So the outcome should look something like this:
[{
name: steak,
unit: piece,
totalQuantity: 20
},
{
name: bacon,
unit: kg,
totalQuantity: 112
}
]
If someone could come up with a solution for this, that would be great!
Somewhat more complicated:
Every order can also have many colli's (packages) which contain many products with a certain amount/quantity.
So orders table -> manyToMany -> collis table -> manyToMany -> products table
I need the same as above, but now with the sum of all quantities of all products in the colli's together with the sum of all quantities of the other products.
Any help is appreciated because I can't find many in the docs about eager loading.
You can try this.
$query = Order::with('products'=>function($q){
$q->select('name', 'price','unit',DB::raw("SUM(quantity) as total_quantity"))->groupBy('name')->orderBy('name','asc');
});
i hope it would work for you.
Found the answer to the first problem:
$allProducts = DB::query()
->select(['p.id', 'p.name', 'c.name as category_name', DB::raw('sum(op.quantity) as total_product_quantity')])
->from('products as p')
->join('order_product as op', 'p.id', '=', 'op.product_id')
->join('orders as o', 'op.order_id', '=', 'o.id')
->join('categories as c', 'p.category_id', '=', 'c.id')
->groupBy('p.id');
For the second problem, I get exactly the same table as above. Now with all products in all colli's:
$allProductsInAllCollis = DB::query()
->select(['p.id', 'p.name', 'c.name as category_name', DB::raw('sum(co.quantity * colp.quantity) as total_colli_quantity')])
->from('products as p')
->join('colli_product as colp', 'p.id', '=', 'colp.product_id')
->join('collis as col', 'colp.colli_id', '=', 'col.id')
->join('colli_order as co', 'col.id', '=', 'co.colli_id')
->join('orders as o', 'co.order_id', '=', 'o.id')
->join('categories as c', 'col.category_id', '=', 'c.id')
->groupBy('p.id', 'p.name', 'category_name');
(No idea why I had to pass 'p.name' and 'category_name' in the groupBy, but got an sql error otherwise.
Now the last thing to do is perform another groupBy to add 'total_product_quantity' of the first table to 'total_colli_quantity' in the second table. How can I do this with 2 queries saved in variables?

Laravel Eloquent - Get Relationship Elements only where "where" condition in "with" is true

I have the model student, courses and group. student and group are related through courses in a many to many relationship. group has a field called year.
I'm trying to get all the students that have a group related to them in which the group has the field year equals to a variable $year. The initial query works great, but I also need that the student model returns with the group that equals the $year value. There can be only one group in the same year associated to that student. Here is the query that I have right now.
Student::whereHas('courses.group', function ($query) use ($year) {
return $query->where('year', '=', $year);
})->where('active', '=', '1')->with(['courses.group' => function ($query) use ($year){
$query->where('year', '=', $year);
}])->get()
This query works to an extend, it returns only the students that have a group related to them with the group having the value of $year, it also returns the relations of courses that the student has, the problem is that it returns all the relations, and in those relations it only returns the groups that have the same year as $year. I need those courses relationships that return "null" to not be there. Any idea how can i solve this?, thanks ;)
Here's an example:
This is a student model
It returns with the courses relation and the groups relation.
Finally I ended up doing multiple joins, I dont know if it is less efficient than doing it with Eloquent methods. Here is the query if someone is interested.
Student::whereHas('courses.group')->where('active', '=', '1')
->join('courses', 'courses.id_student','=','students.id_student')
->join('groups', 'groups.id_group', '=', 'courses.id_group')
->join('degree', 'degree.id_degree', 'groups.id_degree')
->where('year', '=', $year)->get()
If someone knows a better, cleaner o more efficient method I am all ears :)

Eloquent query not returning the expected results from whereHas

Current relationships:
A Clinic hasMany Consultants
A Consultant belongsToMany Treatments (pivot table relationship)
A clinic may not have consultants, and a consultant may not have treatments. I only want to return clinics that has consultants, which has treatments, that includes a treatment that matches $treatment_id (hence the has existence check).
Current attempt (which returns 0 results):
clinic::whereHas('consultants.treatments', function ($query) use ($treatment_id) {
$query->where('treatment_id', $treatment_id);
})
->paginate(10);
I feel I require a whereHas within the whereHas function, to firstly determine if a consultant has treatments, but, if this logic is correct, how can I approach it within the function.
I am expecting 3 results to be returned, but currently it is 0.
How can I achieve this? Many thanks,.
Since it's many to many, it should be:
Clinic::whereHas('consultants.treatments', function ($query) use ($treatment_id) {
$query->where('id', $treatment_id);
})
->paginate(10);

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');

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

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.

Resources