【问题标题】:Lookup, group and calculate in mongoose based on two different collections基于两个不同的集合在 mongoose 中查找、分组和计算
【发布时间】:2021-03-19 17:34:18
【问题描述】:

我很难理解聚合。我要做的是从一个集合中查找所有记录,然后查找另一个集合并对其进行一些计算,然后返回所有合并的响应。

考试架构

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);  

问题架构

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 这里是一个数组,因为一个问题可能会在多个考试中使用。

所以,我想要查询 exams 集合并获取所有考试及其相关问题以及每门考试的总分。

期望的输出

{
    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: []
}
.
.
.
    

我做了什么

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

console.log(exams);

【问题讨论】:

    标签: javascript mongodb mongoose aggregation-framework


    【解决方案1】:

    很好,你正在朝着正确的方向努力。

    以下管道将为您提供所需的输出。

    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' }
            }
          }
        }
      }
    ]);
    

    这是一个使用 Node.js、MongoDB 和 mongoose 的完整复制脚本:

    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));
    }
    

    输出

    $ 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"
          }
        ]
      }
    ]
    

    【讨论】:

    • 非常感谢您的帮助。只是好奇为什么我们不能使用$group 而不是$project
    • 因为您不需要对当前阶段的任何文档进行分组,所以您拥有所需输出所需的所有数据,您只需要重新格式化并进行一些计算。第二个$group 阶段可能会更慢。
    • 还有一个问题,我们如何获得匹配的$questions 的计数?
    • 您可以使用$size 运算符。在$project 阶段内添加以下属性。 questionsCount: { $size: '$questions' }
    • 我在添加另一个查找阶段后尝试使用计数,它看起来很复杂。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-19
    • 2020-05-31
    • 1970-01-01
    • 2016-05-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多