Laravel eloquent restrict withCount with whereHas or another - php

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

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 join three tables with relationships

How can I join three tables where the foreign ID of each table are part of another,
In the below code, I am trying to get all payments from PaymentsTable which have invoice_id, and InvoiceTables which have customer_id,
Here is DB relationships
Table : Customers
customer_id; Primary Key
Table: Invoices
invoice_id: Primary key
customer_id: Foreign Key, Customers
Table : Payments
payment_id: Primary Key
invoice_id: Foreign Key, Payments
$payments = SalesPayments::where('payment_amount', '!=' , NULL)
->join('sales_invoices', 'sales_invoices.invoice_id', '=', 'sales_payments.invoice_id')
->join('payment_options', 'payment_options.paymentoption_id', '=', 'sales_payments.paymentoption_id')
//->join('customers', 'customers.customer_id', '=', 'sales_payments.invoice_id')
->get();
Below Relationship has been tried in Payments model,
public function customers()
{
return $this->hasManyThrough(Customers::class, SalesInvoices::class, 'payment_id' ,'invoice_id', 'payment_id', 'customer_id' );
}
Now with each payment row, I want to get customer information as well, So How can I get it?
As you have not defined relationships, we have to go with Query builder, Update your joins as below
$payments = DB::table('sales_payments')
->join('sales_invoices', 'sales_invoices.invoice_id', '=', 'sales_payments.invoice_id')
->join('customers', 'sales_invoices.customer_id', '=', 'customers.customer_id')
->join('payment_options', 'payment_options.paymentoption_id', '=', 'sales_payments.paymentoption_id')
->select('sales_payments.*', 'sales_invoices.*', 'customers.*', 'payment_options.*')
->get();
Then you can add where clause.
Lets assume that you need all payment information with its invoices and customer details related to that payment.
If you are Looking for the above Response then You can Simply Retrieve Data with Nested Eager Loading
Like
$payments=SalesPayments::with('salesInvoice.customer')->where(Your condition)->get();
In Payments Model
public function SalesInvoice(){
return $this->BelongsTo(Your sales model class)
}
In SalesInvoice Model
public function customer(){
return $this->BelongsTo(Your user model class)
}

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

Laravel Eloquent Inner Join

This is the query I currently using for Inner Join in my laravel app:
public function getReservationListAPI()
{
$id = JWTAuth::parseToken()->authenticate()->id;
$result = DB::table('pporders AS ord')
->join('products AS pd', 'ord.product_id', '=', 'pd.id')
->select('ord.*')
->where('pd.user_id',$id)
->get();
dd($result);
}
How can I wrote this query in Eloquent form? Thanks!!
EDIT
Relationship:
Product hasMany Order
Order belongsTo Product
User hasMany Product
Product belongsTo User
It depends on your relation, but something like this:
Pporders::with(['product' => function($q) use($id) {
$q->where('user_id', $id);
}])->get();
But it won't make the same sql query what you described, but gives the same result.

Categories