2

this is my schema:

 new Schema({
    code: { type: String },
    toy_array: [
      {
        date:{
        type:Date(),
        default: new Date()
       }
        toy:{ type:String }
    ]
   }

this is my db:

{
  "code": "Toystore A",
  "toy_array": [
    {
      _id:"xxxxx", // automatic
      "toy": "buzz"
    },
    {
      _id:"xxxxx", // automatic
      "toy": "pope"
    }
  ]
},
{
  "code": "Toystore B",
  "toy_array": [
    {
       _id:"xxxxx", // automatic
      "toy": "jessie"
    }
  ]
}

I am trying to update an object. In this case I want to update the document with code: 'ToystoreA' and add an array of subdocuments to the array named toy_array if the toys does not exists in the array.

for example if I try to do this:

db.mydb.findOneAndUpdate({
  code: 'ToystoreA,
  /*toy_array: {
    $not: {
      $elemMatch: {
        toy: [{"toy":'woddy'},{"toy":"buzz"}],
      },
    },
  },*/
},
{
  $addToSet: {
    toy_array: {
      $each: [{"toy":'woddy'},{"toy":"buzz"}],
    },
  },
},
{
  new: false,
}
})

they are added and is what I want to avoid.

how can I do it?

[
  {
    "code": "Toystore A",
    "toy_array": [
      {
        "toy": "buzz"
      },
      {
        "toy": "pope"
      }
    ]
  },
  {
    "code": "Toystore B",
    "toy_array": [
      {
        "toy": "jessie"
      }
    ]
  }
]

In this example [{"toy":'woddy'},{"toy":"buzz"}] it should only be added 'woddy' because 'buzz' is already in the array.

Note:when I insert a new toy an insertion date is also inserted, in addition to an _id (it is normal for me).

6
  • In this example [{"toy":'woddy'},{"toy":"buzz"}] it should only be added 'pope' because 'buzz' is already in the array. --> Are you sure about this statement ? Do you mean woddy instead of pope ? If yes, your code should work as expected.. Commented Jun 16, 2020 at 18:28
  • @whoami you're right! Sorry. Commented Jun 16, 2020 at 18:33
  • @whoami it adds so many elements of objects enter without doing validation. I'll update the code. each time an insert is made a unique _id is generated automatically for each toy, so for this reason I think that each new element seems to be unique. Commented Jun 16, 2020 at 18:38
  • 1
    Does this answer your question? Stop Mongoose from creating _id property for sub-document array items Yes $addFields is failing because each object has unique _id I guess you might be using mongoose if yes, then you can change your schema like mentioned in that link !! Commented Jun 16, 2020 at 18:52
  • @whoami actually I do want it to be generated, also in my real code a date is also generated automatically. so i keep looking for a way to insert an object if it doesn't exist. in this case enter a toy if toy is unique. Commented Jun 16, 2020 at 19:00

1 Answer 1

2

As you're using $addToSet on an object it's failing for your use case for a reason :

Let's say if your document look like this :

    {
      _id: 123, // automatically generated
      "toy": "buzz"
    },
    {
      _id: 456, // automatically generated
      "toy": "pope"
    }

and input is :

[{_id: 789, "toy":'woddy'},{_id: 098, "toy":"buzz"}]

Here while comparing two objects {_id: 098, "toy":"buzz"} & {_id: 123, "toy":"buzz"} - $addToSet consider these are different and you can't use $addToSet on a field (toy) in an object. So try below query on MongoDB version >= 4.2.

Query :

db.collection.updateOne({"_id" : "Toystore A"},[{
    $addFields: {
      toy_array: {
        $reduce: {
          input: inputArrayOfObjects,
          initialValue: "$toy_array", // taking existing `toy_array` as initial value
          in: {
            $cond: [
              { $in: [ "$$this.toy", "$toy_array.toy" ] }, // check if each new toy exists in existing arrays of toys
              "$$value", // If yes, just return accumulator array
              { $concatArrays: [ [ "$$this" ], "$$value" ] } // If No, push new toy object into accumulator
            ]
          }
        }
      }
    }
  }])

Test : aggregation pipeline test url : mongoplayground

Ref : $reduce

Note :

You don't need to mention { new: false } as .findOneAndUpdate() return old doc by default, if you need new one then you've to do { new: true }. Also if anyone can get rid of _id's from schema of array objects then you can just use $addToSet as OP was doing earlier (Assume if _id is only unique field), check this stop-mongoose-from-creating-id-property-for-sub-document-array-items.

Sign up to request clarification or add additional context in comments.

17 Comments

if i wanted to do the same but to add for the first time, would it work the same way?
Are you sure it works with updateOne? with this code it never updates.
@yavg : In that case (first time) if you don't have toy_array itself you need to add a prior $addFields stage in update operation like this :: (mongoplayground.net/p/k6wWVBWZU4W), with this code it never updates ? Any error ? Did you replace db.collection with your collection name ?
Yeah right. that's what i did. I have a suspicion. in my actual example each array position contains a country_id property whose value is an` ObjectId` belonging to an id from another collection. The comparison is between a String and an ObjectId . for example arrayfilter.id_country == db.country_id is done. I'm not sure if it's not compared because db.country_id is an` ObjectId`
In this example, if it works only with .update, in my real code I don't know why it doesn't work .. I think there must be some comparison problem between ObjectsId. thanks, again
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.