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).

whoami - fakeFaceTrueSoul
  • 17,086
  • 6
  • 32
  • 46
yavg
  • 2,761
  • 7
  • 45
  • 115
  • *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.. – whoami - fakeFaceTrueSoul Jun 16 '20 at 18:28
  • @whoami you're right! Sorry. – yavg Jun 16 '20 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. – yavg Jun 16 '20 at 18:38
  • 1
    Does this answer your question? [Stop Mongoose from creating \_id property for sub-document array items](https://stackoverflow.com/questions/17254008/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 !! – whoami - fakeFaceTrueSoul Jun 16 '20 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. – yavg Jun 16 '20 at 19:00
  • @whoami I updated my question a bit. the problem is still the same, hope you can guide me please. – yavg Jun 16 '20 at 19:22

1 Answers1

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.

whoami - fakeFaceTrueSoul
  • 17,086
  • 6
  • 32
  • 46
  • if i wanted to do the same but to add for the first time, would it work the same way? – yavg Jun 16 '20 at 19:41
  • Are you sure it works with `updateOne`? with this code it never updates. – yavg Jun 16 '20 at 20:03
  • @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 :: (https://mongoplayground.net/p/k6wWVBWZU4W), *with this code it never updates* ? Any error ? Did you replace `db.collection` with your collection name ? – whoami - fakeFaceTrueSoul Jun 16 '20 at 20:03
  • 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` – yavg Jun 16 '20 at 20:10
  • I just modified my data to make a purchase between 2 Strings. but still do not update my data. In your live code it works, but something strange happens when I try to update, it doesn't update (obviously I try to enter a different value). – yavg Jun 16 '20 at 20:20
  • @yavg : Did you test it on DB directly ? Does it able to find a matching doc with filter ? – whoami - fakeFaceTrueSoul Jun 16 '20 at 20:27
  • https://i.imgur.com/LZztkQR.jpg I tried to do it with the example code – yavg Jun 16 '20 at 20:35
  • I am getting this error: https://i.imgur.com/wq4lfKO.jpg – yavg Jun 16 '20 at 20:42
  • @yavg : I've tested it and it's working as expected. Try using `.update()` instead of `.updateOne()`, I believe that's an issue with robo3T interpreting aggregation pipeline in updates.. – whoami - fakeFaceTrueSoul Jun 16 '20 at 21:10
  • 1
    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 – yavg Jun 16 '20 at 21:31
  • Is there a way to show each result? something like console.log () in javascript? – yavg Jun 17 '20 at 01:43
  • @yavg : What do you mean by each result ? – whoami - fakeFaceTrueSoul Jun 17 '20 at 02:28
  • https://i.imgur.com/5HksknG.jpg In my real example I have a problem ... it never updates me. I want to keep track of what is happening, I do not know if there is a strategic way of storing or showing the values that I have marked in the image (it is the same logic of your answer but with different fields) – yavg Jun 17 '20 at 03:02
  • @yavg : You can also use `.findOneAndUpdate()` instead of `.update()`. here as you're filtering on `_id` you're using a string !! but I would guess `_id` in actual DB document is of type `ObjectId()`, you need to convert in filter part as well like what you're doing in update part.. – whoami - fakeFaceTrueSoul Jun 17 '20 at 16:49
  • Thank you very much for listening to my questions, I appreciate it very much. in my real code it is too strange. It never updates anything. that's why I was asking you if there is a way to keep track of the values of `$this` and `$value` I have checked several times and I can't explain why it doesn't update me – yavg Jun 18 '20 at 02:29
  • I don't know how to debug inside an `aggregate`. for example in javascript we use `console.log` to know what is shown in a loop and things like that. with this one can understand where the problem is. – yavg Jun 18 '20 at 02:34
  • @yavg : you need to check filter in update query ‘_id’ is getting any doc from collection or not ! Also in your ‘$cond’ in ‘$in’ isn’t that ‘$equipos.equipo_id’ ?? I believe ’s’ is missing.. moreover there is no ‘$push’ in aggregation pipeline of updates – whoami - fakeFaceTrueSoul Jun 18 '20 at 07:14