Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine how many points a user has collected using aggregate

I have this Schema structure

const QuizQuestionSchema = new Schema({
  course: { type: String, required: true, trim: true },
  point: { type: Number, required: true, trim: true, min: 1, max: 20 },
  question: { type: String, required: true, trim: true },
  codeBoard: { type: String, default: null, nullable: true },
  answers: { type: [String], required: true, trim: true },
  correctAnswer: { type: String, required: true, trim: true }
});

from client I get array id of documents and selected answer [{id, answer}...]. I need to find out how many points the user has collected, comparing correctAnswer with answer if match add score. how can this be done with an aggregate ?

example

client

[
  { id: "61bc09994da5e9ffe47fccb9", answer: "1 2 4 3 5" },
  { id: "61bc0af14da5e9ffe47fccbb", answer: "1 4 3 2" },
  ...
];

server documents

  {
    _id: new ObjectId("61bc09994da5e9ffe47fccb9"),
    course: 'JavaScript',
    point: 10,
    question: 'What will we see in the console ?',
    codeBoard: '...',
    answers: [ '1 2 4 3 5', '1 5 4 3 2', '2 1 4 3 5', '1 5 3 4 2' ],
    correctAnswer: '2 1 4 3 5',
    __v: 0
  },
  {
    _id: new ObjectId("61bc0af14da5e9ffe47fccbb"),
    course: 'JavaScript',
    point: 10,
    question: 'What will we see in the console ?',
    codeBoard: '...',
    answers: [ '1 4 3 2', '1 2 4 3', '1 2 3 4', '1 4 2 3' ],
    correctAnswer: '1 4 3 2',
    __v: 0
  }
like image 968
Silicum Silium Avatar asked Oct 26 '22 09:10

Silicum Silium


2 Answers

If the client side is sending each question id in String with the chosen answer you should map that array to:

  {
    id: new ObjectId("61bc09994da5e9ffe47fccb9"),
    correctAnswer: "2 1 4 3 5"
  }

Remenber to define ObjectId with:

const ObjectId = require('mongoose').Types.ObjectId;

Then add the full array inside an $or operator and finally group every match adding the points.

db.collection.aggregate({
  "$match": {
    $or: [
      {
        id: new ObjectId("61bc09994da5e9ffe47fccb9"),
        correctAnswer: "2 1 4 3 5"
      },
      {
        id: new ObjectId("61bc0af14da5e9ffe47fccbb"),
        correctAnswer: "1 4 3 2"
      },
      . . .
    ]
  }
},
{
  "$group": {
    "_id": null, // This means that every document is grouped, because there is no condition to group by
    "points": { 
      $sum: "$point"
    }
  }
})
like image 143
Rubén Vega Avatar answered Oct 29 '22 15:10

Rubén Vega


You can achieve this with a pretty straight forward pipeline, using $lookup, like so:

db.users.aggregate([
  {
    $match: {
      user_id: 1
    }
  },
  {
    "$lookup": {
      "from": "quiz",
      let: {
        quizId: "$id",
        userAnswer: "$answer"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $and: [
                {
                  $eq: [
                    "$$quizId",
                    "$_id"
                  ]
                },
                {
                  $eq: [
                    "$$userAnswer",
                    "$correctAnswer"
                  ]
                }
              ]
            }
          }
        }
      ],
      "as": "matched"
    }
  },
  {
    $group: {
      _id: null,
      total: {
        $sum: 1
      },
      correct: {
        $sum: {
          $size: "$matched"
        }
      }
    }
  }
])

Mongo Playground

You didn't give exact input and output required so this will require some minor changes.

like image 20
Tom Slabbaert Avatar answered Oct 29 '22 16:10

Tom Slabbaert