This method employs the use of $concatArrays
and $slice
when the value
array is present and meets the required minimum size. In cases where these conditions are not met, it resorts to using $zip
and $map
to fill in any missing values. This approach offers the advantage of avoiding the use of $map
when the specified criteria are satisfied.
- It's worth noting that
$map
is not necessarily a "slow" operation, especially in this context or the one that was discussed in the comments.
- Comparatively, document retrieval for the update process is significantly slower.
- I have shared a separate answer which consistently utilizes
$map
, similar to the approach mentioned in cmghess's comment, but incorporates concatArrays and slice.
- If achieving nano-level performance gains by avoiding the use of $map is crucial, then implementing multiple conditions with
$switch-case
would be recommended, as it allows for the optimization of each specific case outlined below (existence, size validation, null value, etc.).
This Update Pipeline addresses the following scenarios:
[
{ "_id": "curveId exists and is larger", "curveId": 12, "values": [1, 2, 3, 7, 8, 9] },
{ "_id": "curveId exists and values is exact", "curveId": 12, "values": [1, 2, 3, 4] },
{ "_id": "curveId exists but values is short by 1", "curveId": 12, "values": [1, 2, 3] },
{ "_id": "curveId exists but values is short by 2", "curveId": 12, "values": [1, 2] },
{ "_id": "curveId exists but values is empty", "curveId": 12, "values": [] },
{ "_id": "curveId exists but values is null", "curveId": 12, "values": null },
{ "_id": "curveId exists but values does not exist", "curveId": 12 },
{ "_id": "curveId 99 doesn't exist" }
]
Update pipeline with aggregation expressions:
db.collection.update({ curveId: 12 },
[
{
$set: {
values: {
$let: {
vars: {
// index & new value to set
idx: 3,
new_val: 1000
},
in: {
$cond: {
if: {
$and: [
{ $isArray: "$values" },
{ $lte: [ "$$idx", { $size: "$values" }] } // "lte" is correct here
]
},
then: {
$concatArrays: [
{ $slice: ["$values", "$$idx"] },
["$$new_val"],
{
$slice: [
"$values",
{ $add: ["$$idx", 1] },
{ $add: [{ $size: "$values" }, 1] }
]
}
]
},
else: {
$let: {
vars: {
vals_nulls: {
$map: {
input: {
$zip: {
inputs: [
{ $ifNull: ["$values", []] },
{ $range: [0, "$$idx"] }
],
useLongestLength: true
}
},
in: { $first: "$$this" }
}
}
},
in: {
$concatArrays: [
{ $slice: ["$$vals_nulls", "$$idx"] },
["$$new_val"],
{
$slice: [
"$$vals_nulls",
{ $add: ["$$idx", 1 ] },
{ $add: [{ $size: "$$vals_nulls" }, 1] }
]
}
]
}
}
}
}
}
}
}
}
}
],
{ upsert: true, multi: true }
)
Note:
- The
new_value
and index
for setting purposes only require placement in the initial $set -> values -> $let
segment
- All subsequent references to these values are addressed as variables.
- If available, utilize your language's
let
parameter rather than this approach; mongoose and playground do not offer this feature.
multi: true
is solely utilized to demonstrate all update scenarios in a single execution.
- To observe the
upsert
behavior when the document is absent, use curveId: 99
(any value other than 12) in the query.
- Take note of the repetition that occurs within the
else
segment involving $values
and $$vals_nulls
.
Mongo Playground
Mongo Playground with index=0