如果它不存在,我如何添加一个新的子文档,然后将另一个子文档推到它下面

How do I add a new Subdocument if it's not exist already, then push another subdocument under it

我在 mongoose 上有一个名为 user 的模式,该模式有 lastExams 属性,如下所示:

lastExams: [{
       lecture:  {
           type: String,
           required: true
       },
       exams: [{
        examID: {type: [mongoose.Schema.Types.ObjectId], ref: Exam, required: true},
        resultID: {type: [mongoose.Schema.Types.ObjectId], ref: Result, required: true},
        date: {type: Date, required: true},
        result: {}
    }]}]

有了这个,我想保留用户每节课参加的最后 10 次考试。所以每次考试后,我想检查相应的 'lastExams.lecture' 子文档是否已经存在,如果存在,则将结果推送到 lastExams.$.exams 数组。否则,使用 exams 数组的第一个元素更新该子文档。

例如,这是一个没有任何检查的用户文档;

  user: {
    name: { firstName: '***', lastName: '***' },
      email: '****@****.***',
      photo: 'https://****.jpg',
      role: 0,
      status: true,
      program: {
        _id: 6017b829c878b5bf117dfb92,
        dID: '***',
        eID: '****',
        pID: '****',
        programName: '****',
        __v: 0
      },
      lectures: [
        {
        some data
        }
      ],
      currentExams: [
         some data
      ],
      lastExams: []
    }}

如果用户发送了 math-1 讲座的考试数据,由于没有带有该讲座名称的考试,我需要更新该文档以使用户文档变为如下;

user: {
  name: {
    firstName: '***',
    lastName: '***'
  },
  email: '****@****.***',
  photo: 'https://****.jpg',
  role: 0,
  status: true,
  program: {
    _id: 6017 b829c878b5bf117dfb92,
    dID: '***',
    eID: '****',
    pID: '****',
    programName: '****',
    __v: 0
  },
  lectures: [{
    some data
  }],
  currentExams: [
    some data
  ],
  lastExams: [{
    lecture: 'math-1',
    exams: [ 
      examID: 601 ba71e62c3d45a4f10f080,
      resultID: '602c09b2148214693694b16c',
      date: 2021 - 02 - 16 T18: 06: 42.559 Z,
      result: {
        corrects: 11,
        wrongs: 9,
        empties: 0,
        net: 8.75,
        score: 43.75,
        qLite: [
         'some question objects' 
        ]
      }
    ]
  }]
}
}

我可以这样做;

 User.findOneAndUpdate({email: result.user}, {$addToSet: {'lastExams': {
                       lecture: result.lecture,
                       exams: [{
                        examID: doc.examID, // btw, idk why, these id's saving to database as arrays
                        resultID: doc.id,
                        date: doc.createdAt,
                        result: doc.results
                      }]
                     }}})

但是因为这每次都会添加具有相同讲座价值的新子文档。我必须首先手动检查是否存在具有该讲座值的子文档。如果不是这样,运行 上面的代码,否则,为了将考试数据推送到那个讲座子文档,我使用下面的代码;

User.findOneAndUpdate({email: result.user, 'lastExams.lecture': result.lecture }, {$addToSet: {'lastExams.$.exams': {
   examID: doc.examID,
   resultID: doc.id,
   date: doc.createdAt,
   result: doc.results
  }}})

所以,我必须先进行 User.find() 查询以查看该讲座是否已经存在,如果 exams.lengt 是 10,则弹出一个项目。然后决定使用哪种User.find要使用的 OneAndUpdate()。

您认为有什么方法可以在单个查询中完成这个过程吗?无需为每次考试保存 2-3 次数据库?

我知道它太长了,但我用我糟糕的英语无法直截了当。对不起。

两种方法:

  1. 就像您已经做过的那样,多个查询。 在第一个查询中,您必须检查子文档是否存在,如果存在,则在第二个查询中更新它,否则在第二个查询中创建包含第一项的子文档。

  2. 在聚合管道中使用 $out 如果多次往返只是问题,请检查 $out 聚合管道方法,该方法允许将聚合输出写入集合。在那里,您可以首先匹配您的文档,检查子文档是否存在,使用 $filter 后跟 $cond。准备好数据后,使用 $out 将其写回集合。

注意:聚合管道是比 findOneAndUpdate IMO 更昂贵的操作,因此请确保测试 2 种查询方法和单一聚合方法的平均延迟,并根据您的情况决定哪种方法更快。

PS:很抱歉没有提供示例,我只是不太了解如何为您创建一个工作示例。您可以参考 mongoDB 文档了解详细信息。 https://docs.mongodb.com/manual/reference/operator/aggregation/out/

此外,在 MongoDB 中针对此特定用例正在进行 Jira 票证讨论。希望在即将到来的版本 MongoDB 中实现一些简单的解决方案