我已经尝试使它运行一段时间了,但是我不知道自己在做什么错。
我有两个这样的模式
const paymentSchema = new Schema({
year_month: {
type: String,
required: true
},
status: {
type: Boolean,
required: true
}
});
const testSchema = new Schema({
name: {
type: String,
required: true
},
payments: [{
type: paymentSchema,
required: false,
}]
});
然后我要更新现有值,或者如果该值不可用,我想将其添加到数组中。
假设我在数据库中有以下值:
[
{
"_id": "5e90ae0e0ed9974174e92826",
"name": "User 1",
"payments": [
{
"_id": "5e90c3fb79bba9571ae58a66",
"year_month": "2020_02",
"status": false
}
]
}
]
现在,我想使用此代码将year_month 2020_02的状态更改为true,并且它可以工作:
testSchema.findOneAndUpdate(
{
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_02' }}
},
{ $set: {
'payments.$': {
year_month: '2020_02',
status: false
}
}
},
{
new: true,
upsert: true
}
).then( result => {
response.send(result);
});
当我尝试这样做时出现问题
testSchema.findOneAndUpdate(
{
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_03' }}
},
{ $set: {
'payments.$': {
year_month: '2020_03',
status: false
}
},
},
{
new: true,
upsert: true
}
).then( result => {
response.send(result);
});
我是从upsert得到此消息的...
(node:8481) UnhandledPromiseRejectionWarning: MongoError: The positional operator did not find the match needed from the query.
at Connection.<anonymous> (/home/vedran/Documents/Projekt/node_modules/mongodb/lib/core/connection/pool.js:466:61)
at Connection.emit (events.js:223:5)
at Connection.EventEmitter.emit (domain.js:475:20)
at processMessage (/home/vedran/Documents/Projekt/node_modules/mongodb/lib/core/connection/connection.js:384:10)
at TLSSocket.<anonymous> (/home/vedran/Documents/Projekt/node_modules/mongodb/lib/core/connection/connection.js:553:15)
at TLSSocket.emit (events.js:223:5)
at TLSSocket.EventEmitter.emit (domain.js:475:20)
at addChunk (_stream_readable.js:309:12)
at readableAddChunk (_stream_readable.js:290:11)
at TLSSocket.Readable.push (_stream_readable.js:224:10)
at TLSWrap.onStreamRead (internal/stream_base_commons.js:181:23)
(node:8481) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:8481) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
根据文档Mongoose.findOneAndUpdate(),这应该可以工作,但是我犯了一些错误,我无法弄清楚到底是什么。 我知道匹配查询是问题所在,但我不确定如何更改它,以便应用upsert。
最后我像这样解决了它:
testSchema.findOneAndUpdate(
{
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_03' }}
},
{
$set: {
'payments.$': {
year_month: '2020_02',
status: false
}
}
},
{new: true}
).then( success => {
// response === null if no match is found
if( success ) {
response.send(success);
} else {
testSchema.findOneAndUpdate(
{ _id: '5e90ae0e0ed9974174e92826' },
{
$push: {
'payments': request.body
}
},
{new: true}
).then(success => {
response.send(success);
});
}
},
error => {
response.send(error);
}
);
但是我在这里提出了两个请求,这可能会导致竞赛条件问题。 1.更新和 2.如果不存在则添加
我想知道是否有更好的方法可以使它使用upsert并避免出现竞争情况。
还有一个不错的简短tutorial on mongoose page,它描述了findOneAndUpdate的更新,但是它不包含数组,这可能会使我的问题复杂化。
基于joe和prasad_的答复的最终解决方案。 实际上,一旦您花时间了解这里发生的情况,事情就没那么复杂了。
testSchema.findOneAndUpdate(
{ "_id": customerId },
[{
$set: {
payments: {
$cond: [
{
$gt: [
{
$size: {
$filter: {
input: "$payments",
cond: {
$eq: [
"$$this.year_month",
testData.payments.year_month
]
}
}
}
},
0
]
},
{
$reduce: {
input: "$payments",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[{
$cond: [
{ $eq: ["$$this.year_month", testData.payments.year_month] },
{ $mergeObjects: ["$$this", { status: testData.payments.status }] },
"$$this"
]
}]
]
}
}
},
{
$concatArrays: [
"$payments",
[testData.payments]
]
}
]
}
}
}],
{ new: true }
).then(
success => {
response.send(success);
},
error => {
response.send(error);
}
);
答案 0 :(得分:1)
主要问题是findOneAndUpdate
确实按照其名称所暗示的那样工作。它使用提供的过滤器执行find
,如果找到匹配项,则将更新应用于第一个匹配的文档。
如果集合仅包含此文档:
[
{
"_id": "5e90ae0e0ed9974174e92826",
"payments": [
{
"year_month": "2020_02",
"status": false
}
]
}
]
最初的查找部分基本上是
.find({
_id: '5e90ae0e0ed9974174e92826',
payments: { $elemMatch: { year_month: '2020_03' }}
})
这不匹配任何内容,并且由于upsert设置为true,因此fineOneAndUpdate尝试创建全新的文档。即使它能够通过不匹配的位置运算符创建数组,它要添加的文档也会是:
{
"_id": "5e90ae0e0ed9974174e92826",
"payments": [
{
"year_month": "2020_03",
"status": false
}
]
}
这是不正确的,并且由于重复的_id
值而无法插入。
如果您使用的是MongoDB 4.2,则可以使用聚合管道作为findAndUpdate
的第二个参数来检查数组中是否有您感兴趣的元素,并在缺少该元素时添加它。
下面是一种不太漂亮的方法。 findOneAndUpdate将与_id匹配,并且管道将:
-检查数组中的任何元素是否与所需的year_month
相匹配
-如果是这样,则$ reduce数组以更新该元素中的状态字段
-如果没有,请添加一个新元素
-将结果分配回payments
.findOneAndUpdate(
{ "_id": "5e90ae0e0ed9974174e92826" },
[{$set: {
payments: {$cond:[
{$gt:[
{$size:
{$filter:{
input:"$payments",
cond:{$eq:["$$this.year_month","2020_03"]}
}}},
1
]},
{$reduce:{
input:"$payments",
initialValue:[],
in:{$concatArrays:[
"$$value",
[{$cond:[
{$eq:["$$this.j",3]},
{$mergeObjects:["$$this",{status:true}]},
"$$this"
]}]
]}
}},
{$concatArrays:[
"$payments",
[{year_month:"2020_03", status:true}]
]}
]}
}}]
)
答案 1 :(得分:1)
考虑两个输入文件。将插入第一个(year_month: '2020_03'
数组中不存在payments
)。使用第二个更新运行更新时,它将更新数组中现有子文档的status
。
更新操作仅对MongoDB 4.2或更高版本有效,因为它使用管道进行更新。
INPUT_DOC = { year_month: '2020_03', status: false } // this will get inserted into the array
INPUT_DOC = { year_month: '2020_02', status: true } // updates the sub-document
db.collection.findOneAndUpdate(
{
_id: "5e90ae0e0ed9974174e92826"
},
[
{
$set: {
payments: {
$reduce: {
input: "$payments",
initialValue: { payments: [], update: false },
in: {
$cond: [ { $eq: [ "$$this.year_month", INPUT_DOC.year_month ] },
{
payments: {
$concatArrays: [
[ { _id: "$$this._id", year_month: "$$this.year_month", status: INPUT_DOC.status } ],
"$$value.payments"
]
},
update: true
},
{
payments: {
$concatArrays: [ [ "$$this" ], "$$value.payments" ]
},
update: "$$value.update"
}
]
}
}
}
}
},
{
$set: {
payments: {
$cond: [ { $eq: [ "$payments.update", false ] },
{ $concatArrays: [ [ INPUT_DOC ], "$payments.payments" ] },
{ $concatArrays: [ [ ], "$payments.payments" ] }
]
}
}
}
],
{
new: true,
upsert: true
}
)