简体   繁体   English

基于两个不同的 collections 在 mongoose 中查找、分组和计算

[英]Lookup, group and calculate in mongoose based on two different collections

I am having a hard time understanding aggregation.我很难理解聚合。 What I am trying to do is to find all records from one collection and then lookup another collection and do some calculation on it, then return response with everything consolidated.我要做的是从一个集合中查找所有记录,然后查找另一个集合并对其进行一些计算,然后返回所有合并的响应。

Exam Schema考试模式

const examSchema = new mongoose.Schema({
    examName: {
        type: String,
        trim: true,
        required: [true, "Exam name is missing"],
        maxlength: [60, "Max exam name length is 60 characters"],
    },
    duration: {
        type: Number,
        trim: true,
        default: 0,
        min: [0, "Minimum exam duration is zero minutes"],
    },
}, {
    timestamps: true,
});

module.exports = mongoose.model("Exam", examSchema);  

Question Schema问题模式

const questionSchema = new mongoose.Schema({
    _refExamId: [{
        type: mongoose.Schema.Types.ObjectId,
        ref: "Exam",
    }],
    title: {
        type: String,
        trim: true,
        required: [true, "Question is missing"],
    },
    positiveMarks: {
        type: Number,
        required: [true, "Marks for the question is missing"],
        default: 0,
    },
}, {
    timestamps: true,
});

module.exports = mongoose.model("Question", questionSchema);  

_refExamId here is an array as there are chances that a single question might be used in multiple exams. _refExamId这里是一个数组,因为一个问题可能会在多个考试中使用。

So, what I would like is to query the exams collection and get all the exams and its related questions and the total marks count of each exam.因此,我想要查询考试集合并获取所有考试及其相关问题以及每门考试的总分。

Desired Output所需 Output

{
    exam_name: "Demo exam 1",
    total_time: 30,
    total_marks: 50,
    questions: [{ title: "Question 1" }, { title: "Question 2" }]
},
{
    exam_name: "Demo exam 2",
    total_time: 0,
    total_marks: 0,
    questions: []
}
.
.
.
    

What I have done我做了什么

const exams = await examModel.aggregate([
    {
        $lookup: {
            from: "questions",
            localField: "_id",
            foreignField: "_refExamId",
            as: "questions"
        },
    }
]);

console.log(exams);

Good, you're working towards the right path.很好,你正在朝着正确的道路努力。

The following pipeline would give you the desired output.以下管道将为您提供所需的 output。

const result = await Exam.aggregate([
  {
    $lookup: {
      from: 'questions',
      localField: '_id',
      foreignField: '_refExamId',
      as: 'questions'
    }
  },
  {
    $project: {
      exam_name: '$examName',
      total_time: '$duration',
      total_marks: {
        $reduce: {
          input: '$questions',
          initialValue: 0,
          in: { $add: ['$$value', '$$this.positiveMarks'] }
        }
      },
      questions: {
        $map: {
          input: '$questions',
          as: 'question',
          in: { title: '$$question.title' }
        }
      }
    }
  }
]);

Here's a full reproduction script using Node.js, MongoDB, and mongoose:这是使用 Node.js、MongoDB 和 mongoose 的完整复制脚本:

65198206.js 65198206.js

'use strict';
const mongoose = require('mongoose');
const { Schema } = mongoose;


run().catch(console.error);

async function run () {
  await mongoose.connect('mongodb://localhost:27017/test', {
    useNewUrlParser: true,
    useUnifiedTopology: true
  });

  await mongoose.connection.dropDatabase();


  const examSchema = new Schema({
    examName: {
      type: String,
      trim: true,
      required: [true, 'Exam name is missing'],
      maxlength: [60, 'Max exam name length is 60 characters']
    },
    duration: {
      type: Number,
      trim: true,
      default: 0,
      min: [0, 'Minimum exam duration is zero minutes']
    }
  }, {
    timestamps: true
  });

  const Exam = mongoose.model('Exam', examSchema);


  const questionSchema = new Schema({
    _refExamId: [{
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Exam'
    }],
    title: {
      type: String,
      trim: true,
      required: [true, 'Question is missing']
    },
    positiveMarks: {
      type: Number,
      required: [true, 'Marks for the question is missing'],
      default: 0
    }
  }, {
    timestamps: true
  });


  const Question = mongoose.model('Question', questionSchema);



  const exams = await Exam.create([
    { examName: 'A', duration: 70 },
    { examName: 'B', duration: 90 }
  ]);
  await Question.create([
    {
      _refExamId: [
        exams[0]._id, exams[1]._id
      ],
      title: 'Question a',
      positiveMarks: 10
    },
    {
      _refExamId: [
        exams[0]._id, exams[1]._id
      ],
      title: 'Question b',
      positiveMarks: 20
    },
    {
      _refExamId: [
        exams[0]._id, exams[1]._id
      ],
      title: 'Question c',
      positiveMarks: 30
    }
  ]);

  const result = await Exam.aggregate([
    {
      $lookup: {
        from: 'questions',
        localField: '_id',
        foreignField: '_refExamId',
        as: 'questions'
      }
    },
    {
      $project: {
        exam_name: '$examName',
        total_time: '$duration',
        total_marks: {
          $reduce: {
            input: '$questions',
            initialValue: 0,
            in: { $add: ['$$value', '$$this.positiveMarks'] }
          }
        },
        questions: {
          $map: {
            input: '$questions',
            as: 'question',
            in: { title: '$$question.title' }
          }
        }
      }
    }
  ]);

  console.log(JSON.stringify(result, null, 2));
}

Output Output

$ node 65198206.js
[
  {
    "_id": "5fcf6ea22bd478354c14ad42",
    "exam_name": "A",
    "total_time": 70,
    "total_marks": 60,
    "questions": [
      {
        "title": "Question a"
      },
      {
        "title": "Question b"
      },
      {
        "title": "Question c"
      }
    ]
  },
  {
    "_id": "5fcf6ea22bd478354c14ad43",
    "exam_name": "B",
    "total_time": 90,
    "total_marks": 60,
    "questions": [
      {
        "title": "Question a"
      },
      {
        "title": "Question b"
      },
      {
        "title": "Question c"
      }
    ]
  }
]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM