简体   繁体   中英

Laravel eloquent order by sum of hasMany or by join

I would like to order my products by the those who do have the most likes. I have one products table, one likes table and one users table.

This is how my likes table looks like: id, user_id, product_id, value

The value can either be 1 or -1 which is equal to one like or one dislike.

In my products model I have defined those relationships:

public function likes()
{
    return $this->belongsToMany('App\Models\User', 'products_likes', 'product_id', 'user_id')->withPivot('value')->withTimestamps();
}

public function productLikes()
{
    return $this->hasMany('App\Models\ProductLike');
}

However, now I would like to get the first 20 products which have the most sum of likes. This is how I tried to solve this, however, I cannot solve my problem...

$products = Product::select('products.*', DB::raw('SUM(products_likes.value) as sumLikes'))
            ->join('products_likes', 'products_likes.product_id', '=', 'products.id')
            ->orderBy('sumLikes', 'DESC')
            ->get();

However, this results in a weird group by sql problem:

QLSTATE[42000]: Syntax error or access violation: 1140 Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal if there is no GROUP BY clause (SQL: select `products`.*, SUM(products_likes.value) as sumLikes from `products` inner join `products_likes` on `products_likes`.`product_id` = `products`.`id` order by `sumLikes` desc) 

Does anyone knows how I can archive this? I want to have my database to perform the ordering job and not Laravel. I have found some solutions here as well but they suggest to perform an all() and with("likes") query and use sortByDesc after the query got executed ( here ). But this brings down performance.

Kind regards

From laravel 5.2 onwards, you can use the withCount method to add a 'likes_count' subquery column, which can be customized like this:

Product::query()
    ->withCount(['likes as sumLikes' => function ($query) {
        return $query->select(\DB::raw('SUM(products_likes.value) AS sumLikes'));
    }])
    ->orderBy('sumLikes', 'DESC')
;

A similar result can be achieved on Laravel 5.1 or older versions by writing the subquery yourself:

$subquery = ProductLike::query()
    ->selectRaw('SUM(product_likes.value) as sumLikes')
    ->whereRaw('product_likes.product_id = products.id')
;
Product::query()
    ->addSelect(DB::raw($subquery->toSql()))
    ->orderBy('sumLikes', 'DESC')
;

Another way would be using joins, like you were attempting to, but you need to GROUP BY the products for it to work.

Product::query()
    ->addSelect(DB::raw('SUM(products_likes.value) as sumLikes'))
    ->join('products_likes', 'products_likes.product_id', '=', 'products.id')
    ->groupBy('products.id')
    ->orderBy('sumLikes', 'DESC')
;

You may need to specify the columns in your select, and group by each one of them, depending on your Database Engine.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM