Reuqirment
My DB has a table named categories and also a table named products where products table has a row named category_id which make the relationship with categories table. So I wants to show random 7 categories from my categories table and also I needs to show 6 products from each category too.
Framework
Laravel 5.3
What I have done
To fetch 8 random categories
This code work as I wants so no issue with this code segment
$masterCategoryList = Category::where('parent_id', 0)
->where('id', '!=',0)
->inRandomOrder()
->take(8)
->get();
Fetch 6 random products for each selected random Master Category List id $masterCategoryList
So what I have done is
$productsMatcheToMasterCategory = [];
for($a = 0; $a<8; $a++){
$productsMatcheToMasterCategory =
Product::where('category_id',$masterCategoryList[$a]['id'])
->inRandomOrder()
->take(6)
->get();
}
But this only output 6 products which related one category when I use dd($productsMatcheToMasterCategory)after the loop.Like below
Category 01
- Product 01
- Product 02
My expected out put is something like below
Category 01
- Product 01
- Product 02
Category 02
- Product 01
- Product 02
Could anyone please tell me why does this happen.
Thanks
This is because you are just replacing the value inside the array. It is just creating the array with index 0.
$productsMatcheToMasterCategory = [];
for($a = 0; $a<8; $a++){
$output =
Product::where('category_id',$masterCategoryList[$a]['id'])
->inRandomOrder()
->take(6)
->get();
array_push($productsMatcheToMasterCategory, $output)
}
In the way you are doing it, you are overriding the value you previously assigned to the variable. To avoid that, add an empty pair of brackets to the variable to indicate that you assign each result of each query in the loop to a new element in the array.
$masterCategoryList = Category::where('parent_id', 0)
->where('id', '!=',0)
->inRandomOrder()
->take(8)
->get();
$productsMatcheToMasterCategory = [];
for($a = 0; $a < 8; $a ++) {
// here note the $productsMatcheToMasterCategory [] = ...
$productsMatcheToMasterCategory [] = Product::where('category_id',$masterCategoryList[$a]['id'])
->inRandomOrder()
->take(6)
->get();
}
but in that way you have 9 queries, 1 to get the eight categories and 8 to get the products for each category.
Another approach I can think of is by defining the relationships in the models:
class Category extends Model
{
public function products()
{
return $this->hasMany(Product::class, 'category_id');
}
}
class Product extends Model
{
public function category()
{
return $this->belongsTo(Category::class);
}
}
and getting the related products by the method with() constraining the eager loads, wich results in just one query:
$collection = Category::where('parent_id', 0)
->where('id', '!=',0)
->inRandomOrder()
->take(8)
->with([
'products' => function($query) {
$query->inRandomOrder()
->take(6);
},
])
->get();
$array = $collection->toArray();
Related
Let's say I have Category model with hasMany relation to Product model. Product has color column.
I want to return all categories with all colors that exists in this category, so I tried:
return Category::with(['products' => function ($query) {
$query->distinct('color');
}])->get();
Thanks to this I could later foreach or pluck category->products to get unique colors list. I know I can just get all products in every category, and then filter unique colors, but by doing this I would have to query for example 1000 products per category, instead just 5, which is unnecessary resource heavy. That's why I'm trying to do this on SQL level not PHP.
But this code does not work. There are no errors, it just still returns all products with duplicated colors. Why?
Edit:
Not sure why but my code works if I add select() with used columns before discrinct, and then distinct is making unique rows by all choosed columns. No "color" param required in distinct. Not sure why it works that way, need to dive deeper into SQL docs.
Have you tried this code? Somehow, this will reduce your unnecessary query.
$categories = Category::with([
'products'=> fn($q) => $q->select(['category_id', 'color'])->distinct('color')
])
->select('id') // select required column in categories table
->whereHas('products')
->get();
$colors = $categories->map(function($category) {
return $category->products->pluck('color');
})->toArray();
$color = [];
for ($i=0; $i < count($colors); $i++) {
$color = array_merge($color, $colors[$i]);
}
$uniqueColor = array_unique($color);
return $categories;
I am building a store, where I have to display to the user all products in a given category and all other products that are contained in the subsequent subcategories of the currently accessed one. The categories have the N+1 problem since there can be infinite subcategories. I want to be able to filter trough these products and also to be able to paginate them.
This is my categories model:
class CatalogCategory extends Model
{
public function parent()
{
return $this->belongsTo('App/CatalogCategory','parent_id');
}
public function children()
{
return $this->hasMany($this,'parent_id')
->orderBy('order_place','ASC')
->with('children');
}
/*
* Return products, that belong just to the parent category.
*/
public function products()
{
return $this->hasMany('App\CatalogProduct','parent_id')
->where('is_active', 1)
->whereDate('active_from', '<=', Carbon::now('Europe/Sofia'))
->orderBy('created_at','DESC');
}
/*
* Return all products contained in the parent category and its children categories.
*/
public function all_products()
{
$products = $this->products;
foreach ($this->children as $child) {
$products = $products->merge($child->all_products());
}
return $products;
}
}
The all_products() method returns all of the products, that I want, but since it's a collection i'm unable to paginate or filter through it. My question is if there is a better way to retrieve the products and how to retrieve them so, that i can query them for filtering and paginate them?
You could use nested set technique to store categories.
Nested set technique allows to retrieve all descendants or ancestors for a certain node in hierarchical structures in one query.
You could try this package: https://github.com/lazychaser/laravel-nestedset. Imho it's the best implentation of nested set in laravel.
Installation and configuring will cost you 10 min.
After that you could retrieve your products something like this:
public function products($slug)
{
//first query: retrieving current category
$category = CatalogCategory
::where('slug', $slug)
->first();
//second query: retrieving all category descendants and self ids.
$categoryIds = $category
->descendants
->pluck('id')
->push($category->id);
//third query: retrieving all products.
$products = CatalogProduct
::whereIn('parent_id', $categoryIds)
->where('is_active', 1)
->whereDate('active_from', '<=', Carbon::now('Europe/Sofia'))
->orderBy('created_at', 'desc');
->paginate(50);
return view('path_to_view', compact('products', 'category'));
}
i'm working for a car rental booking system
so my plan is when a customer search if there is a available car in chosen Car Model,
first : get the cars id's already taken for the date chosen by customer
second : get all the cars except the not availavle cars,
$collection = Booking::whereDate('dropoffday', '>' ,$date1)
->whereDate('pickupday' ,'<', $date2)
->get(['car_id'])
;
if ($collection->isEmpty()) {
$cars = Car::with('marques.images')->get();
return Response::json($cars);
}
$taken = [];
foreach ($collection as $car) {
$id = $car->car_id;
array_push($taken,$id);
}
$cars = Car::with('marque.images')->except($taken);
return Response::json($cars);
}
how must i rewrite this line
$cars = Car::with('marque.images')->except($available);
to get all cars with relationship except not available cars
If your relations are set up correctly you can probably use the whereDoesntHave method like this:
$cars = Car::with('marque.images')->whereDoesntHave('bookings', function ($query) use ($date1, $date2) {
$query->whereDate('dropoffday', '>' ,$date1)
->whereDate('pickupday' ,'<', $date2);
})->get();
return Response::json($cars);
I'm trying to make a query to the database to retrieve a list of favorite products by category
In the category there is an attribute called sortcode
after that I have a $categoryids : [3242,1231,6343,1232]
and products
$products = Product::find()->where(['category'=>$categoryids])->all();
But the result was not as I expected, item in $products sort by index
Now I want all product in category 3242 should be ranked first, then to 1231 ...
How do I get the results I want?
Sorry for my bad English!
Thanks in advance and have a nice day !!
try to use where in condition
$products = Product::find()
->where(['in','category',$categoryids])
->orderBy('category DESC')
->all();
or if you want to sort it by category's shortcode you should join with categorys table, not tested yet but should works :
$products = Product::find()
->where(['in','category',$categoryids])
->joinWith(['categorys' => function ($query) {
$query->orderBy('shortcode');
}])
->all();
don't dorget to add categorys relations in your Product's model.
public function getCategorys()
{
return $this->hasOne(Category::className(), ['id' => 'category']);
}
Refer Yii2 orderBy()
$products = Product::find()
->where(['category'=>$categoryids])
->orderBy(['here_your_category_id' => SORT_ASC])
->all();
I'm trying to get 5 posts for each category so I did a little search and ends up here Getting n Posts per category
But I'm getting a weird Call to undefined relationship on model when using with scope but it all works fine If I don't use a scope. Here is the Category Model
//Relationship with posts
public function posts(){
return $this->hasMany('App\Post');
}
scopeNPerGroup
public function scopeNPerGroup($query, $group, $n = 10)
{
// queried table
$table = ($this->getTable());
// initialize MySQL variables inline
$query->from( \DB::raw("(SELECT #rank:=0, #group:=0) as vars, {$table}") );
// if no columns already selected, let's select *
if ( ! $query->getQuery()->columns)
{
$query->select("{$table}.*");
}
// make sure column aliases are unique
$groupAlias = 'group_'.md5(time());
$rankAlias = 'rank_'.md5(time());
// apply mysql variables
$query->addSelect(\DB::raw(
"#rank := IF(#group = {$group}, #rank+1, 1) as {$rankAlias}, #group := {$group} as {$groupAlias}"
));
// make sure first order clause is the group order
$query->getQuery()->orders = (array) $query->getQuery()->orders;
array_unshift($query->getQuery()->orders, ['column' => $group, 'direction' => 'asc']);
// prepare subquery
$subQuery = $query->toSql();
// prepare new main base Query\Builder
$newBase = $this->newQuery()
->from(\DB::raw("({$subQuery}) as {$table}"))
->mergeBindings($query->getQuery())
->where($rankAlias, '<=', $n)
->getQuery();
// replace underlying builder to get rid of previous clauses
$query->setQuery($newBase);
}
Calling Npergroup with relation
public function latestposts()
{
return $this->posts()->latest()->nPerGroup('category_id', 5);
}
Post Model Relationship
//Post belongs to Category
public function category(){
return $this->belongsTo('App\Category');
}
In my category controller I'm calling latestposts through
$categories = Category::with('latestposts')->get();
But I'm getting the error: Call to undefined relationship on model
What I want is:
Get the N number of posts per each category but I'm completely lost at this point. Any help would be appreciated
Reference:
Tweaking Eloquent relations – how to get N related models per parent ?
I am giving this answer based on your purpose that you want 5 posts per category.
So you have Category Model and Post Model.
And in Category Model you have relation with Post model like this
//Relationship with posts
public function posts(){
return $this->hasMany('App\Post');
}
And in Post Model you have relation with Category model like this
//Post belongs to Category
public function category(){
return $this->belongsTo('App\Category');
}
I show your question you have done SQL queries.
Instead of that, You can use two approaches
1) Give condition while eagar loading
$categories = Category::with(['posts' => function ($query) {
$query->orderBy('created_at', 'desc')->take(5);
}])->get();
Note: This approach will only work when you take only one result of parent child using first() method.
To get n number of posts per category Use this.
First, you can retrieve all categories with
$categories = Category::all();
Then you can use foreach loop and in all $category you have to give assign new attribute in it like here latestposts,
foreach ($categories as $category)
{
$category->latestposts = $category->posts()->orderBy('created_at','desc')->take(5)->get();
}
After this foreach loop you will get latest 5 posts in all categories.
Try this in your code and comment your queries and reviews.