英文:
How to only update one match of an array filter in mongodb
问题
问题:
我只需要更新可用空位数组中id为"empty"的一个文档。我的先前查询会更新所有匹配的子文档中id为"empty"的内容,这是不好的,如下所示的示例。所以我决定使用聚合,以便我可以添加一个限制阶段,这样我只能更新一个项目,但后来发现我不能使用聚合来更新原始文档。这只留下了使用数组过滤器的选项,只更新其匹配项中的一个/第一个。这种可能吗?我觉得一定有办法只在数组过滤器中更新一个匹配项,如果没有,这绝对是应该添加的。
我的代码:
此代码会更新所有带"empty"的对象
const client = await clientPromise;
const db = client.db();
const query = await db.collection('events').updateOne({
_id: new ObjectId("6398c34ca67dbe3286452f23"),
createdBy: new ObjectId("636c1778f1d09191074f9690"),
"weights.weight": 12
},
{
$set: {
"weights.$.spotsAvailable.$[el2]": {
"name": "Wayne Wrestler",
"userId": new ObjectId("636c1778f1d09191074f9690")
}
}
},
{
arrayFilters: [{ "el2": { "userId": "empty" } }]
})
示例文档:
事件:
{
"_id": {
"$oid": "6398c34ca67dbe3286452f23"
},
"name": "test",
"createdBy": {
"$oid": "636c1778f1d09191074f9690"
},
"description": "testing",
"date": {
"$date": {
"$numberLong": "1645488000000"
}
},
"location": {
"type": "Point",
"coordinates": [
0,
0
]
},
"weights": [
{
"spotsAvailable": [
{
"name": "empty",
"userId": "empty"
},
{
"name": "empty",
"userId": "empty"
},
{
"name": "empty",
"userId": "empty"
}
],
"weight": 12
},
{
"spotsAvailable": [
{
"name": "Wayne Wrestler",
"userId": {
"$oid": "636c1778f1d09191074f9690"
}
},
{
"name": "Wayne Wrestler",
"userId": {
"$oid": "636c1778f1d09191074f9690"
}
}
],
"weight": 15
}
],
"eventApplicants": [
{
"userId": {
"$oid": "636c1778f1d09191074f9690"
},
"name": "Wayne Wrestler",
"weight": 12
}
]
}
用户:
{
"_id": {
"$oid": "636c1778f1d09191074f9690"
},
"name": "Wayne Wrestler",
"email": "wakywayne80@gmail.com",
"image": "https://lh3.googleusercontent.com/a/ALm5wu32gXjDIRxncjjQA9I4Yl-sjFH5EWsTlmvdM_0kiw=s96-c",
"emailVerified": {
"$date": {
"$numberLong": "1670864727212"
}
},
"createdEvents": [
{
"createdEventName": "test",
"createdEventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"createdEventDescription": "testing",
"createdEventWeights": [
{
"weight": "12",
"filled": [
false,
false,
false
]
},
{
"weight": "15",
"filled": [
false,
false
]
}
],
"createdEventId": {
"$oid": "6398c34ca67dbe3286452f23"
}
}
],
"userSignedUpEvents": [],
"availableWeights": [
1,
123
],
"signedUpEvents": [
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "637ec484ac2d675b30590b47"
},
"eventName": "Maybe?",
"eventDate": {
"$date": {
"$numberLong": "1672272000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "638d5274628db2a7bf61df49"
},
"eventName": "Eva's",
"eventDate": {
"$date": {
"$numberLong": "1698019200000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398a922abb5c168ede595fb"
},
"eventName": "Nikko's event",
"eventDate": {
"$date": {
"$numberLong": "1670976000000"
}
},
"accepted": false
},
{
"eventId": {
<details>
<summary>英文:</summary>
## Problem:
I need to only update one document in the spots available array that has an id of "empty". My previous query was updating all matching sub documents with "empty" as the id; which is no good *Example Below*. So I decided to use aggregation so that I could add a limit stage so that I could only update one item, but come to find out I cannot update the original document with an aggregation. **This leaves the only option to use an array filter that only updates one/first of its matches.** Is this possible? I feel like there has to be a way to only update one match on an array filter and if there isn't this is definitely something that should be added.
## My code:
*This code updates every object with "empty"*
```javascript
const client = await clientPromise;
const db = client.db();
// const query = db.collection('events').aggregate(agg);
const query = await db.collection('events').updateOne({
_id: new ObjectId("6398c34ca67dbe3286452f23"),
createdBy: new ObjectId("636c1778f1d09191074f9690"),
"weights.weight": 12
},
{
$set: {
"weights.$.spotsAvailable.$[el2]": {
"name": "Wayne Wrestler",
"userId": new ObjectId("636c1778f1d09191074f9690")
}
}
},
{
arrayFilters: [{ "el2": { "userId": "empty" } }]
})
Example documents:
Event:
{
"_id": {
"$oid": "6398c34ca67dbe3286452f23"
},
"name": "test",
"createdBy": {
"$oid": "636c1778f1d09191074f9690"
},
"description": "testing",
"date": {
"$date": {
"$numberLong": "1645488000000"
}
},
"location": {
"type": "Point",
"coordinates": [
0,
0
]
},
"weights": [
{
"spotsAvailable": [
{
"name": "empty",
"userId": "empty"
},
{
"name": "empty",
"userId": "empty"
},
{
"name": "empty",
"userId": "empty"
}
],
"weight": 12
},
{
"spotsAvailable": [
{
// only one of these should've been updated, but both were
"name": "Wayne Wrestler",
"userId": {
"$oid": "636c1778f1d09191074f9690"
}
},
{
"name": "Wayne Wrestler",
"userId": {
"$oid": "636c1778f1d09191074f9690"
}
}
],
"weight": 15
}
],
"eventApplicants": [
{
"userId": {
"$oid": "636c1778f1d09191074f9690"
},
"name": "Wayne Wrestler",
"weight": 12
}
]
}
User:
{
"_id": {
"$oid": "636c1778f1d09191074f9690"
},
"name": "Wayne Wrestler",
"email": "wakywayne80@gmail.com",
"image": "https://lh3.googleusercontent.com/a/ALm5wu32gXjDIRxncjjQA9I4Yl-sjFH5EWsTlmvdM_0kiw=s96-c",
"emailVerified": {
"$date": {
"$numberLong": "1670864727212"
}
},
"createdEvents": [
{
"createdEventName": "test",
"createdEventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"createdEventDescription": "testing",
"createdEventWeights": [
{
"weight": "12",
"filled": [
false,
false,
false
]
},
{
"weight": "15",
"filled": [
false,
false
]
}
],
"createdEventId": {
"$oid": "6398c34ca67dbe3286452f23"
}
}
],
"userSignedUpEvents": [],
"availableWeights": [
1,
123
],
"signedUpEvents": [
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "637ec484ac2d675b30590b47"
},
"eventName": "Maybe?",
"eventDate": {
"$date": {
"$numberLong": "1672272000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "638d5274628db2a7bf61df49"
},
"eventName": "Eva's",
"eventDate": {
"$date": {
"$numberLong": "1698019200000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "636c722f67642c30dc5ffc30"
},
"eventName": "Utah",
"eventDate": {
"$date": {
"$numberLong": "1667913330000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398a922abb5c168ede595fb"
},
"eventName": "Nikko's event",
"eventDate": {
"$date": {
"$numberLong": "1670976000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398a922abb5c168ede595fb"
},
"eventName": "Nikko's event",
"eventDate": {
"$date": {
"$numberLong": "1670976000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398c34ca67dbe3286452f23"
},
"eventName": "test",
"eventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398c34ca67dbe3286452f23"
},
"eventName": "test",
"eventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398c34ca67dbe3286452f23"
},
"eventName": "test",
"eventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"accepted": false
},
{
"eventId": {
"$oid": "6398c34ca67dbe3286452f23"
},
"eventName": "test",
"eventDate": {
"$date": {
"$numberLong": "1645488000000"
}
},
"accepted": false
}
]
}
I have tried:
- Pluging in variables without the
new ObjectId
syntax - Plugin in variables with the
new ObjectId
syntax - Using the exact same hardcoded values that I got from copying the aggregation code from compass for the node driver
All of these either don't work or result in every subdocument with "empty" getting filled
答案1
得分: 2
以下是您要翻译的内容:
"一种选择是使用管道进行更新:
由于这是一个双重嵌套数组,最好分两步进行 - 内部和外部
- 首先创建要在
weights
数组中替换的“外部”项目,并将其命名为newItem
。可以使用$reduce
来计算它,这允许我们在循环中操作内部数组。 - 使用
$map
和$cond
替换weights
数组中的相关项目
db.collection.update(
{_id: ObjectId("6398c34ca67dbe3286452f23"), "weights.weight": 12},
[
{$set: {
newItem: {$reduce: {
input: {$getField: {
input: {$first: {
$filter: {
input: "$weights",
as: "item",
cond: {$eq: ["$$item.weight", 12]}
}
}},
field: "spotsAvailable"
}},
initialValue: [],
in: {$concatArrays: [
"$$value",
{$cond: [
{$and: [
{$eq: ["$$this.userId", "empty"]},
{$not: {$in: [ObjectId("636c1778f1d09191074f9690"), "$$value.userId"]}}
]},
[{
name: "Wayne Wrestler",
userId: ObjectId("636c1778f1d09191074f9690")
}],
["$$this"]
]}
]}
}}
}},
{$set: {
weights: {$map: {
input: "$weights",
in: {$cond: [
{$eq: ["$$this.weight", 12]},
{$mergeObjects: [
"$$this",
{spotsAvailable: "$newItem"}
]},
"$$this"
]}
}},
newItem: "$$REMOVE"
}}
])
在 playground 示例 上查看它是如何工作的。"
英文:
One option is to use update with pipeline:
Since this is a double nested array, it is easier to do it in two steps - internal and external
- First create the "external" item to replace in
weights
array and call itnewItem
. It is calculated using$reduce
which allow us to manipulate the internal array while looping on it. - Replace the relevant item on
weights
array with ournewItem
using$map
with$cond
db.collection.update(
{_id: ObjectId("6398c34ca67dbe3286452f23"), "weights.weight": 12},
[
{$set: {
newItem: {$reduce: {
input: {$getField: {
input: {$first: {
$filter: {
input: "$weights",
as: "item",
cond: {$eq: ["$$item.weight", 12]}
}
}},
field: "spotsAvailable"
}},
initialValue: [],
in: {$concatArrays: [
"$$value",
{$cond: [
{$and: [
{$eq: ["$$this.userId", "empty"]},
{$not: {$in: [ObjectId("636c1778f1d09191074f9690"), "$$value.userId"]}}
]},
[{
name: "Wayne Wrestler",
userId: ObjectId("636c1778f1d09191074f9690")
}],
["$$this"]
]}
]}
}}
}},
{$set: {
weights: {$map: {
input: "$weights",
in: {$cond: [
{$eq: ["$$this.weight", 12]},
{$mergeObjects: [
"$$this",
{spotsAvailable: "$newItem"}
]},
"$$this"
]}
}},
newItem: "$$REMOVE"
}}
])
See how it works on the playground example
答案2
得分: 1
你可以首先对weights
进行$unwind
以便更容易处理。使用$reduce
迭代weights.spotsAvailable
数组,并使用一个复合对象来存储结果和一个指示是否已更新的标志。最后,使用结果进行$merge
回到原始文档中。
英文:
You can first $unwind
the weights
for easier processing first. Use $reduce
to iterate through the weights.spotsAvailable
array and use a compound object to store the result and a flag to indicate whether it is updated or not. Finally use the result to $merge
back to the original document.
db.collection.aggregate([
{
$match: {
"_id": ObjectId("6398c34ca67dbe3286452f23"),
createdBy: ObjectId("636c1778f1d09191074f9690"),
"weights.weight": 12,
"weights.spotsAvailable.userId": "empty"
}
},
{
"$unwind": "$weights"
},
{
"$addFields": {
"results": {
"$reduce": {
"input": "$weights.spotsAvailable",
"initialValue": {
result: [],
updated: false
},
"in": {
"$cond": {
"if": {
$and: [
{
$eq: [
false,
"$$value.updated"
]
},
{
$eq: [
"empty",
"$$this.userId"
]
}
]
},
"then": {
result: {
"$concatArrays": [
"$$value.result",
[
{
"name": "Wayne Wrestler",
"userId": ObjectId("636c1778f1d09191074f9690")
}
]
]
},
updated: true
},
"else": {
result: {
"$concatArrays": [
"$$value.result",
[
"$$this"
]
]
},
updated: "$$value.updated"
}
}
}
}
}
}
},
{
$set: {
"weights.spotsAvailable": "$results.result",
"results": "$$REMOVE"
}
},
{
$group: {
_id: "$_id",
"name": {
$first: "$name"
},
"createdBy": {
$first: "$createdBy"
},
"description": {
$first: "$description"
},
"date": {
$first: "$date"
},
"location": {
$first: "$location"
},
"weights": {
$push: "$weights"
},
"eventApplicants": {
$first: "$eventApplicants"
}
}
},
{
"$merge": {
"into": "collection",
"on": "_id"
}
}
])
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论