Hello guys please bear with me here. I’m using PostgreSQL, Sequelize, Express, and NodeJS to create a backend. I’m wondering if these lines of raw query code can be implemented using Sequelize Model findAll function.
First of all, what I am trying to do here is to calculate the total score of these students. Here are some tables and their relations.
                 Student                               Level
| student_id |    name    | level_id |       | level_id | level_name |
|:----------:|:----------:|:--------:|       |:--------:|:----------:|
|      1     |    John    |     1    |   >   |     1    |   Rookie   |
|      2     |    Jane    |     2    |       |     2    |   Expert   |
                    v
              StudentQuiz
|   quiz_id  | student_id |   score  |
|:----------:|:----------:|:--------:|
|      1     |      1     |    40    |
|      1     |      1     |    100   |
|      2     |      1     |    80    |
|      1     |      2     |    100   |
|      2     |      2     |    100   |
If I run line of codes below.
   SELECT table2.student_id,
          s.canvasser_name,
          l.level_name,
          table2.total_score
   FROM (SELECT table1.student_id,
                sum(table1.max_score) total_score
         FROM (SELECT sq.student_id,
               max(sq.score) max_score
               FROM public.StudentQuiz sq
               GROUP BY sq.quiz_id, sq.student_id) table1
         GROUP BY table1.student_id) table2
   INNER JOIN public.Student s
           ON s.student_id = table2.student_id
   INNER JOIN public.Level l
           ON l.level_id = s.level_id
   ORDER BY table2.total_score DESC
   LIMIT 10;
I will get something like this.
| student_id | name | level | total_score | |:----------:|:----------:|:--------:|:--------------:| | 1 | John | Rookie | 180 | | 2 | Jane | Expert | 200 |
Please note that I’m selecting the highest score if more than one quiz with the same id found.
Anyway, I want to implement it using sequelize built in function. What I’ve been trying to do is something like this.
    const result = await StudentQuiz.findAll({
      attributes: ['studentId', [sequelize.fn('sum', sequelize.fn('max', sequelize.col('score'))), 'totalPrice'], 'quizId'],
      group: 'studentId',
      include: [
        {
          model: Student,
          include: [{
            model: Level
          }],
        },
      ],
      offset: 0,
      limit: 10
    });
The code above throws an error message which is “aggregate function calls cannot be nested”.
Any kind of help will be appreciated. Thank you.
P.S. I know i can use sequelize.query() function to use the first code block shown, but that’s not the point.
Advertisement
Answer
I find the solution without using any raw query, though I need to get two tables, that is StudentQuiz and Student that coupled by Level. Here is my answer.
    // Finding maximum score and group it based on studentId and quizId
    const maxScoreList = await StudentQuiz.findAll({
      attributes: ['studentId', 'quizId', [sequelize.fn('max', sequelize.col('score')), 'maxScore']],
      group: ['studentId', 'quizId'],
      order: ['studentId', 'quizId'],
      raw: true
    });
    // Calculating total score for the same student for each quiz recorded
    const scoreArray = [maxScoreList.shift()];
    let index = 0;
    const unfilteredStudentId = maxScoreList.map((item) => {
      if (scoreArray[index].studentId !== item.studentId) {
        scoreArray.push(item);
        index += 1;
      }
      scoreArray[index].maxScore += item.maxScore;
      return item.studentId;
    });
    // Filtering studentId that show up more than one time
    const extractedStudentId = [...new Set(unfilteredStudentId)];
    
    // Finding student based on studentId inside extractedStudentId array
    const student = await Student.findAll({
      where: { id: extractedStudentId },
      attributes: ['id', 'canvasserId', 'canvasserName', 'canvasserImageUrl'],
      include: {
        model: Level,
        attributes: [['level_name', 'level'], ['icon_url', 'level_image_url']]
      },
      order: ['id'],
      raw: true,
      nest: true
    });
    // Combining total score list to student list
    const rankList = student.map((item, idx) => ({ ...item, totalScore: scoreArray[idx] }));
With this much complexity, I agree that using raw query by far the best approach for this case.