How to order by an average rating - laravel

I am trying to order my products by the average of the ratings given in reviews of the product.
here are my tables:
Product : id, productname, price, approved
reviews: id, review, rating, product_id_fk
How would I change this to return on the highest average rating to the lowest?
$products = Product::where('approved', '=', 1)->orderBy('productname');
return view('pages.search')->with('products', $products)

Create a new table called product_review_ratings containing
product_id
avg_rating
Create a scheduled command that loops through all products and sum avrage rating. Save to product_review_ratings table.
In the ProductReviewRating Model add a relationship to Product
class ProductReviewRating extends Model
{
public function product() {
return $this->belongsTo(Product::class);
}
}
Now you can sort by rating.`
$collection = ProductReviewRating::orderBy('avg_rating', 'desc')->get();
And in view
#foreach ($collection as $rating)
{$rating->product->name} - {$rating->avg_rating} <br>
#endforeach
Example of scheduled command (assuming that you have reviews relation in product model)
foreach (Product::all() as $product) {
ProductReviewRating::updateOrCreate(['product_id' => $product->id],[
'product_id' => $product->id,
'avg_rating' => $product->reviews->pluck('rating')->avg()
]);
}

The easy way (no scheduled command needed) is to compute the avg() over the reviews and join the result with products:
$products = DB::table('reviews')
->join('products', 'reviews.product_id', '=', 'products.id')
->select(DB::raw('avg(rating) as average, products.*'))
->groupBy('product_id')
->orderBy('average', 'desc')
->get();
make this a method in your products model, or better yet, products Repository.

The accepted answers is quite outdated now.
There is a builtin withAvg eloquent function that can do the required job.
These methods will place a {relation}_{function}_{column} attribute on your resulting models
So to get the average of the ratings from reviews table:
Product::where('approved', '=', 1)->withAvg('reviews', 'rating')->orderBy('reviews_avg_rating');
Now you can use the product rating as easy as : $product->reviews_avg_rating
The resulted sql will be using subquery to get it on every product:
select `products`.*, (select avg(`reviews`.`rating`) from `reviews` where `products`.`id` = `reviews`.`product_id`) as `reviews_avg_rating` from `products`

Related

Laravel eloquent many to many relation deep conditions

I have two Model named Product and Category. Admin can change the status of Product and Category.
Here Product and Category has many to many relations. So that, all Product can belongs to multiple Category
Now at my user panel,
I want to show that all Product whose all Category's status is active.
you can do this in your product model
public function categories(){
return $this->belongsToMany(Category::class, CategoryProduct::class, 'product_id', 'category_id');
//->withPivot([]); if you need anything from the pivot table for any reason you can add the column names in the array
}
this is assuming that the pivot table have the columns product_id and category_id
$products = Product::with('categories')
->whereHas('categories', function ($query) {
$query->where('categories.status', 'active');
})
->get();
inside the where the word categories is the table name
The solution is:
$products = Product::with('category')
->whereHas('category', function ($query) {
$query->where('status', 'active'); // use your column & condition
})
->get();
It works for me, got all the products that have all the categories active. First time I thought complicated so I didn't solve.
$products = Product::whereDoesntHave('catagories',function($category){
$category->where('status','!=',1);
});

How do I query data from a pivot table in Laravel 7 [duplicate]

Searching from pivot table using laravel.
Here is my table structure:
Product
id
name
Categories
id
name
product_category (Pivot table)
id
category_id
product_id
//products can have multiple categories
Product model:
public function categories(){
return $this->belongsToMany(Category::class, 'product_category');
}
What is the best way to search all products by category id?
Currently I am doing this way, and it seems not an efficient way:
//Controller
$categories = product_category::where('category_id',1)->get();
Now I have to loop through categories and then get Products and pass it to views? Any idea how to do this in an efficient way?
For this you could use the whereHas() method:
$categoryId = 1;
$products = Product::whereHas('categories', function ($query) use($categoryId) {
$query->where('id', $categoryId);
})->get();
The above will return all products that are in the Category where the id is equal to $categoryId.
You can eager load products for a given category. Try:
$category = Category::with('products')->where('category_id',1)->find(1);
When you do this, only 2 database queries will be executed: one for loading the category, and one for loading related products.
Then in your Blade view you can do:
#foreach($category->products as $product
{{ $product->name }}
#endforeach
You can use this inside your method in controller..this only works when $request->$query(search) have value .then here we use wereHas for get the relationship of model and with->() using for get pivot table values
->when($request->query('search'), function ($query)use($request) {
$q= $request->query('search');
return $query->whereHas('relation name', function (Builder $query) use ($q) {
$query->with('pivot table name.column name')
->where('pivot table name.column name', 'like', "%{$q}%")
});})

How to count the orders of one product?

I have products , orders and third pivot table product_order(product_id , order_id), I need to count the orders of the product based on created_at
my shut:
public function product_chart(Product $product)
{
$orders = $product->whereHas('orders', function ($query) {
$query->whereMonth('created_at', '04');
})->count();
dd($orders);
}
The output gives the number of products :(
of course, I have a relation in a product called (orders) and in Order called (products)
So I need to count the orders of the product based on the created_at of order!
That's because you are counting the products.
You can simply do this:
$ordersCount = $product->orders()->count();
But since you are applying some conditions, you need to append the where clause:
$ordersCount = $product->orders()->whereMonth('created_at', '04')->count();
Also there's a simpler way doing it according to Laravel Documentation:
use Illuminate\Database\Eloquent\Builder;
$posts = App\Post::withCount(['votes', 'comments' => function (Builder $query) {
$query->where('content', 'like', 'foo%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;

best approach to search from pivot table using laravel

Searching from pivot table using laravel.
Here is my table structure:
Product
id
name
Categories
id
name
product_category (Pivot table)
id
category_id
product_id
//products can have multiple categories
Product model:
public function categories(){
return $this->belongsToMany(Category::class, 'product_category');
}
What is the best way to search all products by category id?
Currently I am doing this way, and it seems not an efficient way:
//Controller
$categories = product_category::where('category_id',1)->get();
Now I have to loop through categories and then get Products and pass it to views? Any idea how to do this in an efficient way?
For this you could use the whereHas() method:
$categoryId = 1;
$products = Product::whereHas('categories', function ($query) use($categoryId) {
$query->where('id', $categoryId);
})->get();
The above will return all products that are in the Category where the id is equal to $categoryId.
You can eager load products for a given category. Try:
$category = Category::with('products')->where('category_id',1)->find(1);
When you do this, only 2 database queries will be executed: one for loading the category, and one for loading related products.
Then in your Blade view you can do:
#foreach($category->products as $product
{{ $product->name }}
#endforeach
You can use this inside your method in controller..this only works when $request->$query(search) have value .then here we use wereHas for get the relationship of model and with->() using for get pivot table values
->when($request->query('search'), function ($query)use($request) {
$q= $request->query('search');
return $query->whereHas('relation name', function (Builder $query) use ($q) {
$query->with('pivot table name.column name')
->where('pivot table name.column name', 'like', "%{$q}%")
});})

How to order by another table join by eager loading in Laravel

I got product and stocks table;
products
id int
name varchar
created_at timestamp
stocks
id int
name varchar
product_id varchar
created_at timestamp
Product Model
public function validStock() {
return $this->hasMany('Stock')->where('quantity', '>', 10);
}
If both have created_at, how to order by stocks's created_at, I've tried two methods and it's not work
Product::with('validStock')->orderBy('validStock.created_at', 'DESC');
Product::with(array('validStock' => function($q) {
$q->orderBy('created_at', 'DESC');
}));
Instead of sorting after retrieving all data (which is impossible to do in an efficient way when paginating results) you can use a join.
(This answer is based on this one.)
Product::with('validStock')
->join('stocks', 'stocks.product_id', '=', 'products.id')
->select('products.*') // Avoid selecting everything from the stocks table
->orderBy('stocks.created_at', 'DESC')
->get();
The only thing I don't like about this is that it takes away some of the database abstraction, in that you have to write your table names here.
Note that I haven't tried this with a hasMany relationship in this direction, as you have it in your example (selecting products, and each product has many stocks). I've tried only with the hasMany in the other direction (eg selecting stocks, each of which has exactly one product).
You can not apply an order while querying an eagerly loaded relationship in Laravel. You can order the Collections after the query has been performed.
$products = Product::with(array('validStock'))
->get()
->each(function ($product)
{
$product->validStock = $product->validStock
->sortBy(function ($validStock)
{
return $validStock->created_at;
})
->reverse();
});
You should return $q in the closure:
Product::with(array('validStock' => function($q) {
return $q->orderBy('created_at', 'DESC');
}));

Resources