Laravel WithSum / WithCount Relationships not bringing results - php

I am trying to make a query using Laravel eloquent but at the moment I have not had good results.
My query is about the scope of relationships in Laravel. We have two tables:
table 1 : orders
table 2 : products in orders (depends on table 1)
We have a relationship in the model.
public function products()
{
return $this->hasMany(OrderProduct::class);
}
OrderProduct (detail of products in orders) has the following fields:
id
order_id
product_id
qty
line_total
What we are trying to achieve is a query that returns the sum of line_total when the product_id is 139.
We tried the following options without success in the controller:
$orderspaid = Order::with('products')
->where('customer_id', '=', Auth::id())
->where('status', '=', 'completed')
->withSum ('products','line_total')
->where('product_id', '=', '139')
->get();
Error: Column not found: 1054 Unknown column 'product_id'
$orderspaid = Order::withCount(['products as orderproducts' => function($query) {
$query->where('orderproducts.product_id', '=', 139)
->select(DB::raw('sum(line_total)'));
}])->get();
But with no success.
My main question is, it is possible to use sum(line_total) or withSum('products','line_total') to directly sum the amount of money that a particular product_id have?.
Additional Info: Tinker information displaying the relationship between orders and orderproducts.

You can try this one. I don't have those tables ready to test so I could be wrong
So basicly, the method being tried is that products with wanted id will be preloaded, in this case, it's 139. When withSum is called on products table, it will use eagerly products that have been specified beforehand.
$product_id = 139;
$orderspaid = Order::with(['products' => function ($query) use ($product_id) {
$query->where(`products.id`, $product_id);
}])
->where('customer_id', '=', Auth::id())
->where('status', '=', 'completed')
->withSum('products', 'line_total')
->get();
dd($orderspaid);
Tell me if that works for you.

Related

Laravel get one to many with limit by DB query builder

I have a products table that is connected through model_has_attachments with attachments table. I need to connect first attachment to each product record thought the query builder, but for some reason it just give me few records with model_has_attachments ids and rest is the null
my query builder look as:
$products = DB::table('products')->
leftJoin(DB::raw('(select `model_id`, `attachment_id` from model_has_attachments where model_has_attachments.model_id = id) as model_has_attachments'), 'model_has_attachments.model_id', 'products.id')->
leftJoin('attachments', 'model_has_attachments.attachment_id', '=', 'attachments.id')->
select('products.id', 'products.square', 'products.height', 'products.address', 'products.rooms', 'products.title', 'products.description', 'model_has_attachments.model_id as id_model', 'model_has_attachments.attachment_id')->
where([
['products.deleted_at', '=', null],
]);
I've tried to add limit = 1 in the DB::raw but it just give me the first record of the products table, not a joined table. Can you tell me why?
I also tried different approach, but it takes all the record of attachments which result duplicate products records if product has more than one attachment. I also have tried to add ->limit(1) at the end but it just ignores the method.
leftJoin('model_has_attachments', function ($join) {
$join->on('products.id', '=', 'model_has_attachments.model_id')->where('model_has_attachments.model_type', '=', Product::class);
})->
``
//try this
$products = Product::leftJoin('model_has_attachments', 'products.id', '=', 'model_has_attachments.model_id')
->leftJoin('attachments', 'attachments.id', '=', 'model_has_attachments.attachment_id')
->addSelect('products.*', 'attachments.id as attachment_id')
->where('attachments.is_active',1)
->get();

Return data from pivot table with whereIn

So I have Status class which has pivot table relationship with roles:
public function roles():
{
return $this->belongsToMany(Role::class, 'status_role', 'status_id', 'role_id');
}
This is how Status db table looks:
id title
1 status1
2 status2
3 status3
And then my pivot table which looks like this:
status_id role_id
1 2
2 2
And now I want to write query which returns statuses with role_id=2.
Basically it should return data like this: status1, status2 and not include status3.
What I have tryed:
$statuses = Status::query()
->leftJoin('status_role', function ($join) {
$join->on('statuses.id', '=', 'status_role.status_id')
->whereIn('status_role.role_id',[2]);
})
->get();
But now it returns all statuses (status1, status2, status3) it should be only (status1 and status2). How I need to change it?
This query will return all statuses attached to roles with id 2:
Status::query()->whereHas('roles', function($q){
$q->where('id', 2);
})->get();
It uses the whereHas method that can be useful when you need to query relationships.
It can do a lot more, you should check the documentation on this topic: https://laravel.com/docs/8.x/eloquent-relationships#querying-relationship-existence
Quick note: whereHas is the "Laravel preferred way" of doing what you are trying to achieve.
However, you should be able to also do it with this query, which is closer to your current code:
$statuses = Status::query()
->join('status_role', function ($join) {
$join
->on('statuses.id', '=', 'status_role.status_id')
->where('status_role.role_id',2);
})
->get();
// I replaced the leftJoin by join, which will exclude all results without roles (e.g. status id 3)
// or even simpler:
$statuses = Status::query()
->join('status_role', 'statuses.id', '=', 'status_role.status_id')
->where('status_role.role_id',2)
->get();

How to get parents data that only has a child data in laravel

I'm trying to get all the data from the parent that only has a child. Please see my code below.
$customers = Customer::with(['items' => function($query){
return $query->where('status', 2);
}])->get();
dd($customers);
But the code above returns all the customer. By the way, I'm using laravel 4.2.
Items Table:
Customer Table:
with() is for eager loading. That basically means, along the main model, Laravel will preload the relationship(s) you specify. This is especially helpful if you have a collection of models and you want to load a relation for all of them. Because with eager loading you run only one additional DB query instead of one for every model in the collection.
has() is to filter the selecting model based on a relationship. So it acts very similarly to a normal WHERE condition. If you just use has('relation') that means you only want to get the models that have at least one related model in this relation.
e.g :
$users = Customer::has('items')->get();
// only Customer that have at least one item are contained in the collection
whereHas() works basically the same as has() but allows you to specify additional filters for the related model to check.
e.g
$users = Customer::whereHas('items', function($q){
$q->where('status', 2);
})->get();
// only customer that have item status 2
Adding group by to calculating sum
this is another example from my code :
Customer::select(['customer.name', DB::raw('sum(sale.amount_to_pay) AS total_amount'), 'customer.id'])
->where('customer.store_id', User::storeId())
->join('sale', 'sale.customer_id', '=', 'customer.id')
->groupBy('customer.id', 'customer.name')
->orderBy('total_amount', 'desc')
->take($i)
->get()
in your case :
Customer::select(['customer_id', DB::raw('sum(quantity) AS total')])
->whereHas('items', function ($q) {
$q->where('status', 2);
})
->groupBy('customer_id')
->get();
whereHas() allow you to filter data or query for the related model in your case
those customer that have items and it status is 2
afetr getting data we are perform ->groupBy('customer_id')
The GROUP BY statement is often used with aggregate functions (COUNT, MAX, MIN, SUM, AVG) to group the result-set by one or more columns.
select(['customer_id', DB::raw('sum(quantity) AS total')]) this will select customer id and calculate the sum of quantity column
You should use whereHas not with to check child existence.
$customers = Customer::whereHas('items', function($query){
return $query->where('status', 2);
})->get();
dd($customers);
I assume you already defined proper relationship between Customer and Item.
You should try this:
$customers = Customer::whereHas('items', function($query){
$query->where('status', 2);
})->get();
dd($customers);
Customer::select(['items.customer_id',DB::raw('count(items.id) AS total_qty')])
->join('items', 'items.user_id', '=', 'customer.customer_id')
->groupBy('items.customer_id')
->havingRaw('total_qty > 2')
->get();
OR
$data=DB::select("select `items`.`customer_id`, count(items.id) AS total_qty
from `customers`
inner join `items`
on `items`.`customer_id` = `customers`.`customer_id`
group by `items`.`customer_id` having total_qty >= 2");
correct table name and column name.

Laravel Eloquent and Mysql join a table IF another join is null

Three main tables:
products
advertisers
locations
Two pivot tables:
advertisers_locations
products_locations
Relationships:
A product belongs to an advertiser and an advertiser has many locations (Locations it can ship products to)
A product can also have it own set of locations that override the advertiser locations (Some products have delivery restrictions)
What I need to do is:
Select all products
Check if products_locations table for product ID and join it.
If it does not exist then join the advertisers locations table
Is this possible to do in one query and using eloquent? Here's my code - struggling with the conditional:
public function scopeWhereShippableToLocation($query)
{
$location_id = session('location_id');
$query->where(function ($q) use ($location_id) {
$q->join('products_locations', 'products_locations.product_id', '=', 'products.id')
->where('products_locations.location_id', '=', $location_id);
});
$query->orWhere(function ($q) use ($location_id) {
$q->join('advertisers_locations', 'advertisers_locations.advertiser_id', '=', 'products.advertiser_id')
->where('advertisers_locations.location_id', '=', $location_id);
});
//dd($q->toSql());
return $query;
}
This is currently producing a MySQL error:
Column not found: 1054 Unknown column 'products_locations.location_id' in 'where clause' (SQL: select `products`.*,
I think I have a solution for you using eloquent, rather than the query builder. You need to check to see if the relationship exists, if not you need another query. This can be done using the following:
public function scopeWhereShippableToLocation($query)
{
$location_id = session('location_id');
// WhereHas check to see if a relationship exists, IE: The pivot table
// orWhereHas will be checked if the first where does not exist
$query->whereHas('products_locations', function ($q) use ($location_id) {
$q->where('location_id', $location_id);
})->orWhereHas('advertisers_locations', function ($q) use ($location_id) {
$q->where('location_id', $location_id);
});
return $query;
}
This should work providing that your Products, Advertisers and Locations relationship methods are set up.

Laravel Group By relationship column

I have Invoice_Detail model which handles all products and it's quantities, this model table invoice_details has item_id and qty columns.
The Invoice_Detail has a relation to Items model which holds all item's data there in its items table, which has item_id, name, category_id.
The Item model also has a relation to Category model which has all categories data in its categories table.
Question: I want to select top five categories from Invoice_Detail, how?
Here's what I did:
$topCategories = InvoiceDetail::selectRaw('SUM(qty) as qty')
->with(['item.category' => function($query){
$query->groupBy('id');
}])
->orderBy('qty', 'DESC')
->take(5)->get();
But didn't work !!
[{"qty":"11043","item":null}]
Category::select('categories.*',\DB::raw('sum("invoice_details"."qty") as "qty"'))
->leftJoin('items', 'items.category_id', '=', 'categories.id')
->leftJoin('invoice_details', 'invoice_details.item_id', '=', 'items.id')
->groupBy('categories.id')
->orderBy('qty','DESC')
->limit(5)
->get();
This will return you collection of top categories.
Tested on laravel 5.5 and PostgreSQL.
UPD:
To solve this without joins you can add to Categories model this:
public function invoiceDetails()
{
return $this->hasManyThrough(Invoice_Detail::class, Item::class);
}
And to select top 5 categories:
$top = Category::select()->with('invoiceDetails')
->get()->sortByDesc(function($item){
$item->invoiceDetails->sum('qty');
})->top(5);
But first solution with joins will work faster.

Categories