skip to Main Content

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?

2

Answers


  1. 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"}]}}}}}}}])
    
    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search