How can I pull out all my products and average the reviews on the product in Laravel?

Viewed 38
$product = DB::table('product_list')->join('product_categories', 'product_list.product_category', '=', 'product_categories.id')
    ->join('product_types', 'product_list.product_type', '=', 'product_types.id')
    ->where('product_list.id', (int) $productId)
    ->get()
    ->first();

Which gets the correct product that is passed through the route

Route::get('/products/{productId}', 'ProductController@single')->name('Single Product');

The issue is that I have a product_reviews table which has a relation to the product_list table.

public function up()
{
    Schema::create('product_reviews', function(Blueprint $table) {
        $table->increments('id');

        $table->integer('user_id')->unsigned();
        $table->integer('product_id')->unsigned();

        $table->string('review')->default('No comment was made on this product.');
        $table->integer('stars')->default(0);

        $table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
        $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));

        $table->index(array('user_id', 'product_id'));

        $table->foreign('user_id')->references('id')->on('users');
        $table->foreign('product_id')->references('id')->on('product_list');
    });
}

The users reviews are stored in a separate table which the product_id relates to the id of the $product I have got from the DB.

I am trying to get the AVG() rating from all these stars based on the product ID. I have tried

$stars = DB::table('product_reviews')->selectRaw('AVG(stars)')
    ->whereColumn('product_id', 'product_list.id');

$product = DB::table('product_list')->join('product_categories', 'product_list.product_category', '=', 'product_categories.id')
    ->join('product_types', 'product_list.product_type', '=', 'product_types.id')
    ->selectSub($stars, 'stars_avg')
    ->where('product_list.id', (int) $productId)
    ->get()
    ->first();

But I am receiving this output

{#631 ▼
  +"stars_avg": 5.0000
}

Rather than all my product information, with an extra added stars_avg column. How can I bring out all my inner joins with this extra column on top?

1 Answers

I managed to fix this by first querying my data before selecting the subquery. I also added ROUND() to the query to stop null encounters and to round the value to the nearest 10.

$stars = DB::table('product_reviews')->selectRaw('ROUND(AVG(stars))')
        ->whereColumn('product_id', 'product_list.id');

$product = DB::table('product_list')->join('product_categories', 'product_list.product_category', '=', 'product_categories.id')
        ->select('*')
        ->selectSub($stars, 'stars_avg')
        ->join('product_types', 'product_list.product_type', '=', 'product_types.id')
        ->where('product_list.id', (int) $productId)
        ->take(1)
        ->get()
        ->first();

This now gives me my desired output of:

{#634 ▼
  +"id": 3
  +"cost": "150.00"
  +"product_category": 1
  +"product_type": 3
  +"product_score": 0
  +"created_at": "2019-01-16 16:34:29"
  +"updated_at": "2019-01-16 16:34:29"
  +"rating": 0
  +"down_payment": "10.00"
  +"title": "Static"
  +"price_start": "50.00"
  +"price_stop": "150.00"
  +"theme": "Custom"
  +"pages": 4
  +"rbac": 0
  +"portal": 0
  +"external_software": 0
  +"company_supplier": "Iezon Solutions"
  +"stars_avg": "5"
}
Related