Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

t0berius's avatar
Level 13

calculate average value inside SQL query

I'm struggeling with the calculation of an average value inside an SQL / eloquent query.

I would like to be able to sort the users by feeback rating, but as you can see rating is calculated from already loaded models by using an accessor, is there a way I can sort directly by SQL (eloquent) query?

Accessor:

public function getAverageUserRatingInPercentageAttribute()
{
    return $this->feedback_count ? intval(($this->positive_feedback_count * 100) / $this->feedback_count) : 0;
}

I already tried this in SQL:

	User::withCount([
        'sellerFeedbacks as positive_feedback_count' => function ($query) {
            $query->onlyPositive();
        },
        'sellerFeedbacks as feedback_count' => function ($query) {
            $query->onlyPositive()
                ->orWhere(function ($query) {
                    $query->onlyNegative();
                });
        },
    ])
        ->selectRaw('CASE WHEN feedback_count > 0 THEN (positive_feedback_count * 100 / feedback_count) ELSE 0 END as average_user_rating_percentage')
        ->orderBy('average_user_rating_percentage', 'desc')
        ->get();

Sadly this throws Column not found: 1054 Unknown column 'feedback_count' in 'field list'.

I can verify both required fields are available on the model when I dump the model (without using selecrRaw() and orderBy('average_user_rating_percentage', 'desc')).

Any idea how I can do this kind of calculation on SQL level and sort by the average feedback value?

0 likes
6 replies
tisuchi's avatar

@t0berius How about this?

User::withCount([
    'sellerFeedbacks as positive_feedback_count' => function ($query) {
        $query->onlyPositive();
    },
    'sellerFeedbacks as feedback_count' => function ($query) {
        $query->onlyPositive()
            ->orWhere(function ($query) {
                $query->onlyNegative();
            });
    },
])
->selectRaw('users.*, CASE WHEN feedback_count > 0 THEN (positive_feedback_count * 100 / feedback_count) ELSE 0 END as average_user_rating_percentage')
->orderBy('average_user_rating_percentage', 'desc')
->get();
psrz's avatar

@tisuchi

I don't think that's supposed to work. The fields feedback_count and positive_feedback_count don't exist until the query is solved

You're going to have to use a cte to get those results and then join that one with users

$cte = User::query()
        ->select('id as user_id')
        ->withCount([
            'sellerFeedbacks as positive_feedback_count' => function ($query) {
                $query->onlyPositive();
            },
            'sellerFeedbacks as feedback_count' => function ($query) {
                $query->onlyPositive()
                    ->orWhere(function ($query) {
                        $query->onlyNegative();
                    });
            },
        ]);

$users = User::query()
        ->select(['users.*', 'cte.*'])
        ->joinSub($cte, 'cte', function($join) {
            $join->on('users.id', '=', 'cte.user_id');
        })
        ->selectRaw('CASE WHEN feedback_count > 0 THEN (positive_feedback_count * 100 / feedback_count) ELSE 0 END as average_user_rating_percentage')
        ->orderByRaw('(CASE WHEN feedback_count > 0 THEN (positive_feedback_count * 100 / feedback_count) ELSE 0 END) desc')
        ->get();

Now feedback_count and positive_feedback_count do exist because they are being provided by the cte alias, which is solved first by the database

The orderBy has to be an expression as well because of the same reason you can't directly access the feedback fields

IIRC, this was possible in mysql older versions, on non-strict mode, where you could reference on the where or order by a field you were "calculating" as some expression on the select clause. But don't quote me on this. I might be misremembering...

I doubt this will work as copy/paste thing since I don't have the tables but that's the idea.

t0berius's avatar
Level 13

@psrz I'll give this a shot asap, normally I use paginate(), this would be used on second query only I think?

psrz's avatar

@t0berius

Correct. Paginate executes the query so that should be done on the outer one. It's only one round trip to the database

t0berius's avatar
Level 13

@psrz There is no chance to get this into one query from my point of view, since the *_count fields cannot be loades inside the same query which performs calculation, is this correct?

t0berius's avatar
Level 13

@tisuchi thanks for your reply first, same problem as above, throwing SQLSTATE[42S22]: Column not found: 1054 Unknown column 'feedback_count' in 'field list'

I'm unsure why this occurs, the *count attributes should be already available at this state of execution of SQL query?

Please or to participate in this conversation.