Home > database >  MongoDB pull first matching nested array item
MongoDB pull first matching nested array item

Time:05-11

I have the following documents...

{ "_id": 2, "name": "Jane Doe", "phones": [ { "type": "Mobile", "digits": [ { "val": 1 }, { "val": 2 } ] }, { "type": "Mobile", "digits": [ { "val": 3 }, { "val": 4 } ] }, { "type": "Land", "digits": [ { "val": 5 }, { "val": 6 } ] } ] }
{ "_id": 1, "name": "John Doe", "phones": [ { "type": "Land", "digits": [ { "val": 1 }, { "val": 2 } ] }, { "type": "Mobile", "digits": [ { "val": 0 }, { "val": 3 }, { "val": 4 } ] }, { "type": "Mobile", "digits": [ { "val": 3 }, { "val": 4 }, { "val": 9 } ] } ] }

...and the following MongoDB query...

db.getCollection("persons").updateOne({"name": "John Doe"},
{
    "$pull":
    {
        "phones.$[condition1].digits":
        {
            "val: { $in: [ 3, 4 ] }
        }
    }
},
{
    arrayFilters:
    [
        { "condition1.type": "Mobile" }
    ]
})

My problem is that the query removes the last two elements of the array: "phones" of the second document (John Doe) and I want to remove only the first one (and not the last one that have a "9" among the digits). How I can delete only the first matching nested array item?

CodePudding user response:

Query

  • pipeline update
  • reduce on phones, starting with {"phones": [], "found": false}
  • if [3,4] subset of digits.val and not found => ignore it
    else keep it (concat arrays to add the member)
  • $getField to get the phones from the reduced {"phones" : [...]}

*$pull removes all elements that satisfy the condition, maybe there is a way with update operators and not pipeline update, but this works if you dont find more compact way

*alternative to reduce, could be 2 filters, one to keep the values that dont contain the [3,4] and one to keep those that contain, from those that contained, and then concat those arrays removing only one of those that contain the [3,4]

Playmongo

update(
{"name": {"$eq": "John Doe"}},
[{"$set": 
   {"phones": 
     {"$getField": 
       {"field": "phones",
        "input": 
         {"$reduce": 
           {"input": "$phones",
            "initialValue": {"phones": [], "found": false},
            "in": 
             {"$cond": 
               [{"$and": 
                   [{"$not": ["$$value.found"]},
                     {"$setIsSubset": [[3, 4], "$$this.digits.val"]}]},
                 {"phones": "$$value.phones", "found": true},
                 {"phones": {"$concatArrays": ["$$value.phones", ["$$this"]]},
                  "found": "$$value.found"}]}}}}}}}])

CodePudding user response:

I have no real sense of motivation for this update, so I am unsure about the details of the logic. I think I have taken the OP's words and partial demonstration literally and I've implemented an update pipeline to fix the stated problem. Given the number of possibilities, this may not be what you are looking for. My pipeline is very similar to the @Takis answer, but the logic is slightly different and therefore the output is different. I look forward to the OP's comments/questions to identify/clarify any discrepancies and/or ambiguities.

db.collection.update({
  "name": "John Doe"
},
[
  {
    "$set": {
      "phones": {
        "$getField": {
          "field": "phones",
          "input": {
            "$reduce": {
              "input": "$phones",
              "initialValue": { "phones": [], "pullDone": false },
              "in": {
                "$cond": [
                  {
                    "$and": [
                      { "$eq": [ "$$this.type", "Mobile" ] },
                      { "$not": "$$value.pullDone" }
                    ]
                  },
                  {
                    "pullDone": true,
                    "phones": {
                      "$concatArrays": [
                        "$$value.phones",
                        [
                          {
                            "$mergeObjects": [
                              "$$this",
                              {
                                "digits": {
                                  "$filter": {
                                    "input": "$$this.digits",
                                    "as": "digit",
                                    "cond": {
                                      "$not": [ { "$in": [ "$$digit.val", [ 3, 4 ] ] } ]
                                    }
                                  }
                                }
                              }
                            ]
                          }
                        ]
                      ]
                    }
                  },
                  {
                    "pullDone": "$$value.pullDone",
                    "phones": {
                      "$concatArrays": [ "$$value.phones", [ "$$this" ] ]
                    }
                  }
                ]
              }
            }
          }
        }
      }
    }
  }
])

Try it on mongoplayground.net.

  • Related