0

I have a collection with documents having the following format

{
    name: "A",
    details : {
        matchA: {
            comment: "Hello",
            score: 5
        },
        matchI: {
            score: 10
        },
        lastMatch:{
        score: 5
        }
    }
},
{
    name: "B",
    details : {
        match2: {
            score: 5
        },
        match7: {
            score: 10
        },
        firstMatch:{
        score: 5
        }
    }
}

I don't immediatly know the name of the keys that are children of details, they don't follow a known format, there can be different amounts etc.

I would like to write a query which will update the children in such a manner that any subdocument with a score less than 5, gets a new field added (say lowScore: true).

I've looked around a bit and I found $ and $elemMatch, but those only work on arrays. Is there an equivalent for subdocuments? Is there some way of doing it using the aggregation pipeline?

user2110845
  • 385
  • 8
  • 19

1 Answers1

1

I don't think you can do that using a normal update(). There is a way through the aggregation framework which itself, however, cannot alter any persisted data. So you will need to loop through the results and update your documents individually like e.g. here: Aggregation with update in mongoDB

This is the required query to transform your data into what you need for the subsequent update:

collection.aggregate({
    $addFields: {
        "details": {
            $objectToArray: "$details" // transform "details" into uniform array of key-value pairs
        }
    }
}, {
    $unwind: "$details" // flatten the array created above
}, {
    $match: {
        "details.v.score": {
            $lt: 10 // filter out anything that's not relevant to us
            // (please note that I used some other filter than the one you wanted "score less than 5" to get some results using your sample data
        },
        "details.v.lowScore": { // this filter is not really required but it seems to make sense to check for the presence of the field that you want to create in case you run the query repeatedly
            $exists: false
        }
    }
}, {
    $project: {
        "fieldsToUpdate": "$details.k" // ...by populating the "details" array again
    }
})

Running this query returns:

/* 1 */
{
    "_id" : ObjectId("59cc0b6afab2f8c9e1404641"),
    "fieldsToUpdate" : "matchA"
}

/* 2 */
{
    "_id" : ObjectId("59cc0b6afab2f8c9e1404641"),
    "fieldsToUpdate" : "lastMatch"
}

/* 3 */
{
    "_id" : ObjectId("59cc0b6afab2f8c9e1404643"),
    "fieldsToUpdate" : "match2"
}

/* 4 */
{
    "_id" : ObjectId("59cc0b6afab2f8c9e1404643"),
    "fieldsToUpdate" : "firstMatch"
}

You could then $set your new field "lowScore" using a cursor as described in the linked answer above.

dnickless
  • 10,733
  • 1
  • 19
  • 34