如果它不存在,我如何添加一个新的子文档,然后将另一个子文档推到它下面
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 次数据库?
我知道它太长了,但我用我糟糕的英语无法直截了当。对不起。
两种方法:
就像您已经做过的那样,多个查询。
在第一个查询中,您必须检查子文档是否存在,如果存在,则在第二个查询中更新它,否则在第二个查询中创建包含第一项的子文档。
在聚合管道中使用 $out
如果多次往返只是问题,请检查 $out 聚合管道方法,该方法允许将聚合输出写入集合。在那里,您可以首先匹配您的文档,检查子文档是否存在,使用 $filter 后跟 $cond。准备好数据后,使用 $out 将其写回集合。
注意:聚合管道是比 findOneAndUpdate IMO 更昂贵的操作,因此请确保测试 2 种查询方法和单一聚合方法的平均延迟,并根据您的情况决定哪种方法更快。
PS:很抱歉没有提供示例,我只是不太了解如何为您创建一个工作示例。您可以参考 mongoDB 文档了解详细信息。
https://docs.mongodb.com/manual/reference/operator/aggregation/out/
此外,在 MongoDB 中针对此特定用例正在进行 Jira 票证讨论。希望在即将到来的版本 MongoDB 中实现一些简单的解决方案
我在 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 次数据库?
我知道它太长了,但我用我糟糕的英语无法直截了当。对不起。
两种方法:
就像您已经做过的那样,多个查询。 在第一个查询中,您必须检查子文档是否存在,如果存在,则在第二个查询中更新它,否则在第二个查询中创建包含第一项的子文档。
在聚合管道中使用 $out 如果多次往返只是问题,请检查 $out 聚合管道方法,该方法允许将聚合输出写入集合。在那里,您可以首先匹配您的文档,检查子文档是否存在,使用 $filter 后跟 $cond。准备好数据后,使用 $out 将其写回集合。
注意:聚合管道是比 findOneAndUpdate IMO 更昂贵的操作,因此请确保测试 2 种查询方法和单一聚合方法的平均延迟,并根据您的情况决定哪种方法更快。
PS:很抱歉没有提供示例,我只是不太了解如何为您创建一个工作示例。您可以参考 mongoDB 文档了解详细信息。 https://docs.mongodb.com/manual/reference/operator/aggregation/out/
此外,在 MongoDB 中针对此特定用例正在进行 Jira 票证讨论。希望在即将到来的版本 MongoDB 中实现一些简单的解决方案