If you want to get more speed and get everything all at once, then you can try this query :
$products = Product::selectRaw('products.*, sum(line_items.qty) as qty_sum')
->join('line_items', 'line_items.product_id', '=', 'products.id')
->join('purchase_orders', 'purchase_orders.id', '=', 'line_items.purchase_order_id')
->where('purchase_orders.status', 'sold')
->groupBy('products.id')
->get();
But Your Database if MySql must accept whole grouping with one column that always matches with other columns.
But if you want to stick to the Laravel relations only then, take the following steps.
1- When your table name is plural, PK is id
, and FK is table name singular with _id
ex (table products(id), table line_items(product_id)), there is no need to specify Foreign key in the Relations.
2- Since it's a Many-to-Many case, you don't necessarily have to Define a Model for the Intermediate table line_items
, you can have it for special and fast queries, but you can handle CRUD on it via Laravel Pivot system. You can learn more about Pivot https://laravel.com/docs/5.7/eloquent-relationships#many-to-many
Retrieving Intermediate Table Columns
As you have already learned,
working with many-to-many relations requires the presence of an
intermediate table. Eloquent provides some very helpful ways of
interacting with this table. For example, let's assume our User object
has many Role objects that it is related to. After accessing this
relationship, we may access the intermediate table using the pivot
attribute on the models:
So your Models could look like this :
Product Model:
class Product extends Model
{
public function purchaseOrders()
{
return $this->belongsToMany('App\PurchaseOrder', 'line_items')->withPivot('qty')->withTimestamps();
}
public function soldPurchaseOrders()
{
return $this->belongsToMany('App\PurchaseOrder', 'line_items')
->where('status', 'sold')
->withPivot('qty')->withTimestamps();
}
}
PurchaseOrder Model:
class PurchaseOrder extends Model
{
public function products()
{
return $this->belongsToMany('App\Product', 'line_items')->withPivot('qty')->withTimestamps();
}
}
Use ->withPivot('field1', 'field2', 'etc')
, and ->withTimestamps()
if your pivot table has created_at
, updated_at
columns. And access pivot value with prefix pivot_
+attribute in query builder, and with pivot->
+attribute in results object method, ex : $products->first()->pivot->qty
.
If you still want to use LineItem model, then change Product relation to belongsTo
.
LineItem Model:
class LineItem extends Model
{
public function purchaseOrder()
{
return $this->belongsTo('App\PurchaseOrder');
}
public function product()
{
return $this->belongsTo('App\Product');
}
}
3- And finally to retrieve Products with SUM of qty use the following codes :
Use eager loading to autoload Purchase Orders for all Products all at once in 1 request to avoid N-Products + 1 requests to Database. https://laravel.com/docs/5.7/eloquent-relationships#eager-loading
$products = Product::with('soldPurchaseOrders')->get();
foreach($products as $product) {
$qtySum = $product->soldPurchaseOrders->sum('pivot.qty');
echo '<br/> Product '.$product->id.' - '.$product->name.' - '.$qtySum;
}
So at the end, Laravel will convert this with INNER JOIN query like the 1st example above, but only with fancy design and Pivot table.
EDIT------
After OP specify orders status as sold
, I've update the codes to use sold.
EDIT 2 -----
Replace $product->pivot->qty
by $qtySum
. Pivot quantity will be available after calling the soldPurchaseOrders
relations like :
foreach($product->soldPurchaseOrders as $order) {
echo '<br/>Order .'$order->id.' - '.$order->pivot->qty;
}
purchase_order_id
in both orders and lineitems table? – Iftikhar uddin