I needed to run a quick and (I thought) trivial query to update some incorrect data. I’m trying to update the very first entry of an array in a document.
Note: My posted queries run against
db
instead of a collection, and use$documents
to generate test data in the pipline so you can evaluate them straight away. No need to create a collection and seed it with test data.
I thought this would be utterly trivial:
db.aggregate([
{ $documents: [{ values: [-354, 52, 234, 1] }] },
{ $set: { 'values.0': 12 } }
]);
However the result of that is bizarre:
{
values: [
{
'0': 12
},
{
'0': 12
},
{
'0': 12
},
{
'0': 12
}
]
}
I finally managed to get it working, but with a convoluted query with some interesting caveats:
db.aggregate([
{ $documents: [{ values: [-354, 52, 234, 1] }] },
{
$set: {
values: {
$concatArrays: [[12], { $slice: ['$values', 1, 2147483647] }],
}7
},
},
]);
This gives the correct result:
{
values: [
12,
52,
234,
1
]
}
I must first build a single value array for the new first value. Then concatenate the values in the array, skipping the first. MongoDB doesn’t seem to have a proper operator for that, so as a tenuous stand-in, I’m using $slice
with the start parameter set to 1, and the number of entries to 2³¹−1, which is the maximum value for that parameter. It could be that there are more array entries than that. But at that point I’m pretty you’re going to run into bigger problems, since many more operation parameters are 32-bit integers, and all kinds of operations will fail.
Now this seems utterly ridiculous to me, I think I must be doing something simple wrong. There’s no way this janky method is the normal way to to update an array value with a known index in MongoDB.
Since I couldn’t find a method that worked online, I’ll post this here, and hope someone can tell me how it’s really supposed to be done.
2
Answers
The
{ $set: { 'values.x': ... } }
syntax works only inupdate
but not in aggregation pipeline.More precisely, you mistake Aggregation Stage $set (aggregation) and Field Update Operator $set
Array Expression Operators are not as complete as in Javascript Array. You may use $function and implement your requirement in Javascript.
I would use
Operator
$reduce
would be even worse, see Performance issue for $reduce vs. $mapWith JavaScript it would be
You have test if it performs better than native aggregation pipeline functions.
There are 2 alternatives that I can think of to achieve what you want, though I doubt that they will be simpler than your current approach.
$concatArrays
, but use$lastN
to construct "remaining" part of the array(i.e. other than the first entry). The size of "remaining" array is calculated through$subtract
1 from$size
ofvalues
.Mongo Playground
$unwind
withincludeArrayIndex
. Perform conditional replacement based on the index value and$group
to revert the original structure.Mongo Playground