Mongoose unique index on subdocument

13,509

Solution 1

Long story short: Mongo doesn't support unique indexes for subdocuments, although it allows creating them...

Solution 2

This comes up in google so I thought I'd add an alternative to using an index to achieve unique key constraint like functionality in subdocuments, hope that's OK.

I'm not terribly familiar with Mongoose so it's just a mongo console update:

var foo = { _id: 'some value' }; //Your new subdoc here

db.yourCollection.update(
{ '_id': 'your query here', 'myArray._id': { '$ne': foo._id } },
{ '$push': { myArray: { foo } })

With documents looking like:

{
  _id: '...',
  myArray: [{_id:'your schema here'}, {...}, ...]
}

The key being that you ensure update will not return a document to update (i.e. the find part) if your subdocument key already exists.

Share:
13,509

Related videos on Youtube

Sebastian Nowak
Author by

Sebastian Nowak

Updated on September 20, 2022

Comments

  • Sebastian Nowak
    Sebastian Nowak over 1 year

    Let's say I have a simple schema:

    var testSchema = new mongoose.Schema({
        map: { type: [ mongoose.Schema.Types.Mixed ], default: [] },
        ...possibly something else
    });
    

    Now let's ensure that pairs (_id, map._id) are unique.

    testSchema.index({ _id: 1, 'map._id': 1 }, { unique: true });
    

    Quick check using db.test.getIndexes() shows that it was created.

    {
        "v" : 1,
        "unique" : true,
        "key" : {
            "_id" : 1,
            "map._id" : 1
        },
        "name" : "_id_1_map._id_1",
        "ns" : "test.test",
        "background" : true,
        "safe" : null
    }
    

    The problem is, this index is ignored and I can easily create multiple subdocuments with the same map._id. I can easily execute following query multiple times:

    db.maps.update({ _id: ObjectId("some valid id") }, { $push: { map: { '_id': 'asd' } } });
    

    and end up with following:

    {
        "_id": ObjectId("some valid id"),
        "map": [
            {
                "_id": "asd" 
            },
            {
                "_id": "asd" 
            },
            {
                "_id": "asd" 
            }
        ]
    }
    

    What's going on here? Why can I push conflicting subdocuments?

    • Adam Lockhart
      Adam Lockhart
      It is bad practice to use anything other than ObjectId for _id. Why would you want to do that?
  • Sebastian Nowak
    Sebastian Nowak over 9 years
    Subdocument's _id isn't an ObjectId when it's declared as array of Mixed. Also it doesn't answer the question.
  • Sebastian Nowak
    Sebastian Nowak over 9 years
    ...which means that unique indexing for subdocuments isn't possible in the way explained in the question, and that's what I wrote.
  • John John
    John John about 4 years
    you can use $addToSet operator that adds a value to an array unless the value is already present, in which case $addToSet does nothing to that array.
  • Tobi
    Tobi over 3 years
    but doesnt this run over the entire set of documents of the model? because exactly once the id is matched and then, if this myArray.id already exists, the query tries to find a document where the id as well as myArray.id match. Seems pretty wasteful
  • phuhgh
    phuhgh over 3 years
    RE collection scans, use indexes. $addToSet at the time of writing did not understand object equality so would always add regardless. Depending on your use case this approach may well be overkill now.