Reputation: 119
I am attempting to turn this
{
"fooList" : [
{
"bar" : {
"baz" : 100
}
},
{
"bar" : {
"baz" : 200
}
}
]
},
{
"fooList" : [
{
"bar" : {
"baz" : 300
}
},
{
"bar" : {
"baz" : 400
}
}
]
}
into this:
{
"fooList" : [
{
"baz" : 100,
"bar" : {
"baz" : 100
}
},
{
"baz" : 200,
"bar" : {
"baz" : 200
}
}
]
},
{
"fooList" : [
{
"baz" : 300,
"bar" : {
"baz" : 300
}
},
{
"baz" : 400,
"bar" : {
"baz" : 400
}
}
]
}
As you can see, I really just copy baz
and its value out of bar
, but my issue is that is occurs within an array.
db.getCollection(<collection_name>).updateMany(
{},
{ $set: { 'fooList.$[element].baz' : '$fooList.$[element].bar.baz' } },
{ upsert: true ,
arrayFilters: [{'element.bar' : { $exists : true }}]
}
)
But this will only set the string $fooList.$[element].bar.baz
into baz, seen here through the results
[
{
"_id": ObjectId("5a934e000102030405000000"),
"fooList": [
{
"bar": {
"baz": 100
},
"baz": "$fooList.$[element].bar.baz"
}
]
}
]
Can anyone tell me what I might be doing wrong, or if this is even possible? Thanks
Upvotes: 0
Views: 48
Reputation: 10697
You can do it using aggregation pipeline in the update in mongoDB 4.2+ as follow:
db.collection.update({},
[
{
$set: {
fooList: {
$map: {
input: "$fooList",
in: {
$mergeObjects: [
"$$this",
{
$cond: [
{
$ne: [
"$$this.bar.baz",
undefined
]
},
{
baz: "$$this.bar.baz"
},
{}
]
}
]
}
}
}
}
}
],
{
multi: true
})
Explained:
Unfortunatelly arrayFilters are not possible when you use update with aggregation pipeline ...
For earlier mongodb versions it will look something like this:
db.collection.find({}).forEach(function(d){ var newfooList=[];d.fooList.forEach(function(s){ s["baz"]=s["bar"]["baz"]; printjson(s);newfooList.push(s); }); d["fooList"]=newfooList ; db.collection.save(d) })
Upvotes: 1
Reputation: 7568
Here is a variation that demonstrates the v4.4 "merge onto self" capability that turns aggregate
into an update
. This is a useful approach when you want to process all the docs because it eliminates the "no filter" ({}
) and {multi:true}
bits required for update
.
db.foo.aggregate([
// The $map/$mergeObjects is the SAME as the answer above, just with a little
// more compact format. It is import to $project here, NOT $addFields, because
// we seek to limit the pipeline to just _id (comes along automatically) and
// the fooList:
{$project: {
fooList: {$map: {
input: "$fooList",
in: {$mergeObjects: [
"$$this",
{$cond: [
{$ne: ["$$this.bar.baz", undefined]},
{baz: "$$this.bar.baz"},
{} // else empty object
]}
]}
}}
}}
// ...and now, using _id as the key (fast!), merge fooList back into doc:
,{$merge: {
into: "foo",
on: [ "_id" ],
whenMatched: "merge",
whenNotMatched: "fail"
}}
]);
Upvotes: 1