Laravel Group By relationship column - php

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.

Related

Laravel WithSum / WithCount Relationships not bringing results

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.

Laravel eloquent restrict withCount with whereHas or another

I have 3 models Supplier, Purchase and PurchaseDetail.
To join the Supplier model with PurchaseDetail through Purchase I have created a hasManyThrough relation.
hasManyThrough inside Supplier model:
public function detail_purchases(){
return $this->hasManyThrough(PurchaseDetail::class, Purchase::class);
}
The relationship works well and I can count the quantities purchased from sellers as follows:
$collectors = Supplier::withCount(['detail_purchases as qty_sold' => function($query) {
return $query->select(\DB::raw('SUM(qty)'))
->where('unit', '=', 'kg');
}])
->where('supplier_type','=','persona_natural')
->orderBy('qty_sold','desc')
->get();
SQL Query output:
select `suppliers`.*, (
select SUM(qty)
from `purchase_details`
inner join `purchases` on `purchases`.`id` = `purchase_details`.`purchase_id`
where `suppliers`.`id` = `purchases`.`supplier_id`
and `unit` = 'kg'
and `purchases`.`deleted_at` is null
) as `qty_sold`
from `suppliers`
where `supplier_type` = 'persona_natural'
order by `qty_sold` desc;
Output rows:
My problem is that this query is bringing me sellers that I did not make purchases from them, I don't know why they infiltrate the query if it is assumed that the hasManyThrough relationship only joins those who are registered in Purchase or made purchases from them.
Also the Supplier model has another relation called purchases:
public function purchases() {
return $this->hasMany(Purchase::class, 'supplier_id');
}
And the model Purchase has a relation hasMany with PurchaseDetail :
public function details(){
return $this->hasMany(PurchaseDetail::class, 'purchase_id');
}
Updated
Using whereHas now I can get all the suppliers that I did purchases however the qty_sold is not appearing in the results:
$collectors = Supplier::whereHas('purchases', function($query){
$query->withCount(['details as qty_sold' => function($query){
$query->select(\DB::raw('SUM(qty)'))
->where('unit', '=', $this->unit);
}]);
})
->where('supplier_type','=','persona_natural')
->get();
This selection is important because I want to know how many kgs of all products I purchased.
Thanks #Tony
In your updated query can you move the withCount out of the subquery? So you would have Supplier::withCount(...)->whereHas('purchases')..
Ok, I fixed it, adding first the whereHas directive the I used the hasManyThrough
relation with withCount. Of this way only suppliers that has purchases are selection.
$collectors = Supplier::whereHas('purchases')
->withCount([
'detail_purchases as qty_sold' => function($query){
$query->select(\DB::raw('SUM(qty)'))
->where('unit', '=', 'kg');
}
])
->where('supplier_type','=', 'persona_natural')
->get();

Not able to fetch data from primary table in laravel

I have 3 tables i.e category, sub category and products. I have passed category id to sub category and then sub category id to product so that i can get the whole data as per requirements. The problem in i an able to get the data from the secondary tables that has joins statement not from the primary table.Here is my code of query. Any help from you would be appreciated
public function showProduct()
{
$data = DB::table('category')
->join('sub_category','category.id',"=",'sub_category.category_id')
->join('product','sub_category.id', '=', 'product.sub_category_id')
->get();
return $this->success('date' ,$data);
}
use leftjoin
$data = DB::table('category')
->leftjoin('sub_category','category.id',"=",'sub_category.category_id')
->leftjoin('product','sub_category.id', '=', 'product.sub_category_id')
->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 join two tables for ruled out records

In Laravel, how can I reject a record in a query based on a value with one of the table with which this table has relations?
For example, I have the Products table and the Categories table. The Categories table has a one-to-many relationship, one category can have many products. The categories table are is_visible is_deleted. How to make inquiries on the Products table so that it rejects products that belong to the category that has and set fields is_visible = false or is_deleted = true ?
I tried something like this:
$products = ProductTable::join('product_category_tables', 'product_category_tables.id', '=', 'product_tables.id')
->where('product_category_tables.is_visible', '=', true)
->where('product_category_tables.is_deleted', '=', false)
->where('product_tables.is_visible', '=', true)
->where('product_tables.is_deleted', '=', false)
->paginate(50);
But from this query I have only one record. I can't make this query on Category table becouse I want get only 25/50/100 products for paginate.
If you are using Laravel Modal(Eloquent) and have defined the one to many relationship properly in both Product and Category Model. then you can achieve this by the following query:
$products = Product::where("is_visible", true)
->where("is_deleted", false)
->whereHas('category', function($categories){
$categories->where("is_visible", true)
->where("is_deleted", false);
})
->get();
In Product model you should have defined the relationship as bellow:
public function category()
{
//From your mentioned query I am seeing that
//product table's id is actually categories table's id(which should not be like this though),
//so the relationship is
return $this->belongsTo(Category::class, 'id');
}

Categories