I am building an ecommerce website using Laravel 5.8 and have the following problem. I want to retrieve all products from a category and its child categories, but to be able to perform filtering queries afterwards in my code. For example price limits and quantitative availability on the products.
The store has categories, which have child categories in the same table.
Categories table (Simplified) - id|name|parent_id - Where if parent_id != 0, then the category is considered a child to a main category.
The products can be in more than one category and therefore I'm using a many to many relationship.
Products table (Simplified) - id|name...
Product categories table - id|product_id|category_id
My Products model look like this:
public function categories()
{
return $this->belongsToMany(
'App\CatalogCategory',
'catalog_product_categories',
'product_id',
'category_id'
);
}
And my Categories model:
public function allProducts()
{
return $this->belongsToMany(
'App\CatalogProduct',
'catalog_product_categories',
'category_id',
'product_id'
)
->where('is_active', 1)
->whereDate('active_from', '<=', Carbon::now('Europe/Sofia'))
->where(function ($query)
{
$query->whereDate('active_to', '>=', Carbon::now('Europe/Sofia'))
->orWhereNull('active_to');
})
->wherePivotIn('category_id', $this->allChildrenIds());;
}
Currently doing this, returns an empty collection:
$category = CatalogCategory::find(3);
dd($category->allProducts);
Okay I guess the problem is ->wherePivotIn('category_id', $this->allChildrenIds()).
You are trying to get products for a category - corresponding records can be identified by a row in pivot table which has this category's id with various other product_id.
But the wherePivotIn doesn't contain current category's id in $this->allChildrenIds() so no records are returned.
Define the relation without the wherePivotIn
public function allProducts()
{
return $this->belongsToMany(
'App\CatalogProduct',
'catalog_product_categories',
'category_id',
'product_id'
)
->where('is_active', 1)
->whereDate('active_from', '<=', Carbon::now('Europe/Sofia'))
->where(function ($query)
{
$query->whereDate('active_to', '>=', Carbon::now('Europe/Sofia'))
->orWhereNull('active_to');
});
}
Then to get all products for current category and all it's subcategories
$category = Category::with(['products', 'subcategories.products'])->get();
With this query the products associated with subcategories will be nested under each subcategory respectively.
Related
I have an orders table, an items table, and a pivot table called item_order which has two custom fields (price, quantity). The relationship between Order and Item is belongsToMany. I'm trying to return the count of all items with an id of 1, where the parent Order->status == 'received'. For the life of me, I can't figure out how to do this.
class Order extends Model
{
public function items()
{
return $this->belongsToMany(Item::class)->withPivot('price', 'quantity');
}
}
class Item extends Model
{
public function orders()
{
return $this->belongsToMany(Order::class)->withPivot('price', 'quantity');
}
}
Try this:
$total_quantity = Item::find(1) // getting the Item
->orders() // entering the relationship
->with('items') // eager loading related data
->where('status', '=', 'received') // constraining the relationship
->get() // getting the results: Collection of Order
->sum('pivot.quantity'); // sum the pivot field to return a single value.
The strategy here is to find the desired Item to then get the related Orders to this Item that has a 'received' status, to finally sum the pivot attribute in order to get a single value.
It should work.
Considering you know the id of the item, most performant way would be to query item_order table directly. I would create a pivot model for ItemOrder and define the belongsTo(Order::class) relationship and do this:
$sum = ItemOrder::where('item_id', $someItemId)
->whereHas('order', function ($q) {
return $q->where('status', 'received');
})
->sum('quantity');
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');
}
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.
I have three tables.
Categories
Products
Brands
I have a relation on my categories table to the products like so:
public function products()
{
return $this->belongsToMany('App\Product','product_sub_categories','subcategory_id','product_id');
}
I have a relation on my Products table to the brands like so:
public function manuf()
{
return $this->belongsTo('App\Brand','brand');
}
I'm querying the categories table to return products of that category by a certain brand.
For example.
I wan to see all products in Cars category with the brand Fiat.
I've tried the following but I feel Im missing something..
$search = 'fiat';
$products = $category->products()->where(function($query) use ($search){
$query->brand->name = $search;
})->get();
to return products of that category by a certain brand
I assume that you know brand ID and category ID and that products and categories have many to many relationship (since you're using belongsToMany) and product belongs to brand:
Product::where('brand_id', $brandId)
->whereHas('categories', function($q) use(categoryId) {
$q->where('id', $categoryId);
})
->get();
I have 3 Models... Category, Post, Vote
When viewing a category, I am showing an index of all Posts in that category. So I'm doing something like foreach ($category->posts as $post) in my view.
The question I have now is how can I order the posts based on the sum of votes they have?
I have standard relationships setup, so that a post hasMany votes.
You can do it either by defining a helper relation on the Post model and sorting the collection after the relation is loaded OR simply by joining the votes and ordering in the query.
1 RELATION
// Post model
public function votesSum()
{
return $this->hasOne('Vote')->selectRaw('post_id, sum(votes) as aggregate')->groupBy('post_id');
}
// then
$category->posts->load('votesSum'); // load relation on the collection
$category->posts->sortByDesc(function ($post) {
return $post->votesSum->aggregate;
});
// access votes sum like this:
$category->posts->first()->votesSum->aggregate;
2 JOIN
$category->load(['posts' => function ($q) {
$q->leftJoin('votes', 'votes.post_id', '=', 'posts.id')
->selectRaw('posts.*, sum(votes.votes) as votesSum')
->groupBy('posts.id')
->orderBy('votesSum', 'desc');
}]);
// then access votes sum:
$category->posts->first()->votesSum;
You can use scope for that:
// Post model
public function scopeOrderByVotes($query)
{
$query->leftJoin('comments','comments.post_id','=','posts.id')
->selectRaw('posts.*,sum(comments.id) as commentsSum')
->groupBy('posts.id')
->orderBy('commentsSum','desc');
}
// then
$category = Category::with(['posts' => function ($q) {
$q->orderByVotes();
}])->whereSlug($slug)->firstOrFail();