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

sheldonscott's avatar

Dynamic Eloquent Query

As is par for the course for me, I am drawing a blank (or maybe overthinking) something that is probably not too difficult to solve.

Setting the stage

I have a table that contains student assessment data. It looks something like this (excuse the mess; Laracasts never formats this properly for me, even after using Git-flavored code blocks):

id  /  123
user_id  /  28
room_id  /  11
student_id  /  62
subject_id  /  23
unit_id  /  203
outcome_id  /  850
assessed_level  /  4
created_at  /  2018-02-21 21:08:50
updated_at  /  2018-02-21 21:08:50

(repeated x however many assessments have been done)

Further explanation

Each teacher (user_id) can teach multiple rooms (room_id) and have any number of students (student_id) within those rooms. They can assess those students any number of times (assessed_level) for any of the subjects that they teach (subject_id). [The unit_id and outcome_id are not part of the differentiation in this particular view.]

What I want to do

What I want to do for one view is output the number of students (count) assessed at a 4 (assessed level) for each of the subjects (subject_id) over a period of time (created_at).

An example

For example, let's say a teacher has assessed their class of students 4 times in science. Each student earned a score (between 1 and 4) each of these 4 times. I want to add together and average out the score for each student in this subject.

Johnny = 4, 4, 3, 4 (average: 3.75, rounded up to 4)

Sally = 1, 2, 3, 4 (average: 2.5, rounded up to 3)

Johnny = 4, 4, 3, 3 (average: 3.5, rounded up to 4)

In this case, I would have 2 students who earned a 4 in science and 1 student who has earned a 3 in science for the given time period.

How would I write this programmatically? I have put all of the required data (I think) into one table so that the Eloquent call can be super straightforward, but I am scratching my head as to how to actually write it dynamically (scaling).

0 likes
4 replies
sheldonscott's avatar

Thanks for the link, @jlrdw. That solution is pointing to hasManyThrough, but I am trying to filter data stored in only one table. (Or am I wrong about that?)

I think what I need is some kind of groupBy + groupBy statement...

Basically, I want to get an avg for each of the students who performed at each assessed_level for each subject. (All of this data is found in one table.)

Here's what my table looks like (as I said, they never format right for me on this forum):


| id  | user_id | room_id | student_id | subject_id | unit_id | outcome_id | assessed_level |
|-----|---------|---------|------------|------------|---------|------------|----------------|
| 200 |    28   |    50   |     69     |     23     |   203   |    849     |        4       |
| 201 |    28   |    50   |     76     |     23     |   203   |    849     |        2       |
| 202 |    28   |    50   |     64     |     23     |   203   |    849     |        3       |
| 203 |    28   |    50   |     69     |     23     |   203   |    852     |        3       |
| 204 |    28   |    50   |     76     |     23     |   203   |    852     |        3       |
| 205 |    28   |    50   |     64     |     23     |   203   |    852     |        1       |
| 206 |    28   |    50   |     69     |     23     |   203   |    848     |        4       |
| 207 |    28   |    50   |     76     |     23     |   203   |    848     |        3       |
| 208 |    28   |    50   |     64     |     23     |   203   |    848     |        3       |

If I could output this the way I want, it might come out like:

student 69 would average out to a score of 4 (4 + 3 + 4 = 11 / 3 = 3.6 [ceil = 4]) student 76 would average out to a score of 3 (2 + 3 + 3 = 8 / 3 = 2.6 [ceil = 3]) student 64 would average out to a score of 3 (3 + 1 + 3 = 7 / 3 = 2.3 [ceil = 3])

So, I would have 1 student at a 4 and 2 students at a 3.

Hope that makes sense...

sheldonscott's avatar

Okay, I think I have moved the ball forward a little bit.

I have used the following statement:

 $assessment_data = DB::table('student_assessments') 
      ->join('subjects', 'student_assessments.subject_id', '=', 'subjects.id')
      ->select('subjects.name as sub_name', 'student_assessments.assessed_level as ass_lvl', DB::raw('count(*) as stud_cnt')) 
      ->where('student_assessments.user_id', Auth::id()) 
      ->where('student_assessments.room_id', $selected_room->id) 
      ->groupBy('student_assessments.subject_id','student_assessments.assessed_level') 
      ->get();

When I dd this, wrapped in json_encode, this is what I am presented with:

"[{"sub_name":"Physical Education","ass_lvl":2,"stud_cnt":1},{"sub_name":"Science","ass_lvl":1,"stud_cnt":6},{"sub_name":"Science","ass_lvl":2,"stud_cnt":15},{"sub_name":"Science","ass_lvl":3,"stud_cnt":28},{"sub_name":"Science","ass_lvl":4,"stud_cnt":11}]"

How would I go about separating each of the subjects into it's own JSON array (multidimensional array)? Or, am I on the right path?

Ideally, I would rather see that xx% of students were a Level 1 in Science, yy% were a Level 2, etc. Baby steps...

sheldonscott's avatar

I realized I was going down the wrong path a bit with how I was thinking about the above queries. So I started over and came up with this:

for($x=0; $x<count($students_of_room); $x++) {
    $assessment_data[] = DB::table('student_assessments') 
        ->join('subjects', 'student_assessments.subject_id', '=', 'subjects.id')
        ->select('student_assessments.student_id as student_id', 'subjects.name as subject_name', DB::raw('round(avg(student_assessments.assessed_level)) AS average')) 
        ->where('student_assessments.user_id', Auth::id()) 
        ->where('student_assessments.room_id', $selected_room->id) 
        ->where('student_assessments.student_id', $students_of_room[$x]->id) 
        ->groupBy('student_assessments.subject_id')
        ->get();
            }

Now my output looks like this:

[{"student_id":69,"subject_name":"Science","average":"4"}]
[{"student_id":66,"subject_name":"Science","average":"3"}]
[{"student_id":76,"subject_name":"Science","average":"2"}]
[{"student_id":67,"subject_name":"Science","average":"3"}]
[{"student_id":64,"subject_name":"Science","average":"3"}]
[{"student_id":68,"subject_name":"Science","average":"2"}]
[{"student_id":75,"subject_name":"Science","average":"3"}]
[{"student_id":73,"subject_name":"Science","average":"3"}]
[{"student_id":74,"subject_name":"Science","average":"2"}]
[{"student_id":70,"subject_name":"Science","average":"3"}]
[{"student_id":65,"subject_name":"Science","average":"3"}]
[{"student_id":62,"subject_name":"Science","average":"2"}]
[{"student_id":63,"subject_name":"Science","average":"3"}]
[{"student_id":71,"subject_name":"Science","average":"3"}]
[{"student_id":72,"subject_name":"Science","average":"1"}]

So, it's getting the average performance of each student in a subject and it all calculates out right according to a check against the data in my DB.

Next step: I would love to group/count the number of students from this query who are at a '4', the number who are at a '3', the number who are at a '2', and the number who are at a '1'.

Given the above output, that should tell me there is 1 student at a 4, 9 students at a 3, 4 students at a 2, and 1 student at a 1.

Any ideas how I might achieve this?

Please or to participate in this conversation.