How to Have Unique Index in MongoDB

Tahseen Tauseef Feb 02, 2024
  1. Unique Index in MongoDB
  2. Behavior of Unique Index in MongoDB
  3. Validate Unique Emails with Mongoose
How to Have Unique Index in MongoDB

In this tutorial, you’ll learn about unique indexes, including what they are and how to create them in MongoDB. In addition, the process of making a user’s email unique in MongoDB is briefly described.

The table of contents for this article is as follows:

  1. Unique indexes in MongoDB
  2. Create a unique index in MongoDB
  3. Behavior of unique indexes in MongoDB
  4. Validate Unique Emails with Mongoose

Unique Index in MongoDB

A unique index guarantees that the indexed fields do not contain duplicate values, ensuring that the indexed fields are unique. During the construction of a collection, MongoDB produces a unique index on the _id column by default.

Use the db.collection.createIndex() command to generate a unique index with the unique option set to true.

db.collection.createIndex( <key and index type specification>, { unique: true } )

Unique Index on a Single Field

Use the following procedure in mongosh to build a unique index on the user_id field of the members collection.

db.members.createIndex( { "user_id": 1 }, { unique: true } )

Unique Compound Index

On compound indexes, you may also impose a unique restriction. For example, MongoDB enforces uniqueness in combining index key values if you employ the unique constraint on a compound index.

Use the following operation in mongosh to build a unique index on the groupNumber, lastname, and firstname fields of the members collection.

db.members.createIndex( { groupNumber: 2, lastname: 1, firstname: 1 }, { unique: true } )

The index ensures that each combination of groupNumber, lastname, and firstname values are unique.

Consider the following collection with the following document.

{ _id: 1, a: [ { loc: "A", qty: 5 }, { qty: 10 } ] }

Make a unique compound multikey index on a.loc and a.qty.

db.collection.createIndex( { "a.loc": 1, "a.qty": 1 }, { unique: true } )

The following documents can be included in the collection because the index guarantees uniqueness for the combination of a.loc and a.qty values.

db.collection.insertMany( [
   { _id: 2, a: [ { loc: "A" }, { qty: 6 } ] },
   { _id: 3, a: [ { loc: "A", qty: 12 } ] }
] )

Behavior of Unique Index in MongoDB

Restrictions:

If the collection already includes data that would violate the index’s unique requirement, MongoDB will not be able to establish a unique index on the provided index field(s). On a hashed index, you can’t define a unique constraint.

Use Replica Sets and Sharded Clusters to Create Unique Index

Using a rolling operation to build a unique index on replica sets and sharded clusters necessitates stopping all writes to the collection throughout the procedure.

Do not use the rolling operation if you cannot stop all writes to the collection during the procedure. Instead, create a one-of-a-kind index for the collection by issuing:

  1. db.collection.createIndex() on the primary for a replica set
  2. db.collection.createIndex() on the mongos for a sharded cluster

Unique Constraint Across Separate Documents

The one-of-a-kind requirement applies to each document in the collection. The unique index prevents the indexed key from having the same value in different documents.

Because the limitation only applies to separate documents, a document can have an array of items that result in repeating index key values for a unique multikey index as long as the index key values for the document do not duplicate those for another document. The repeated index entry is only entered into the index once in this scenario.

For example, a collection containing the following documents.

{ _id: 1, a: [ { loc: "A", qty: 6 }, { qty: 10 } ] }
{ _id: 2, a: [ { loc: "A" }, { qty: 7 } ] }
{ _id: 3, a: [ { loc: "A", qty: 12 } ] }

Make a unique compound multikey index on a.loc and a.qty.

db.collection.createIndex( { "a.loc": 1, "a.qty": 1 }, { unique: true } )

If no other document in that collection has an index key value of {"a.loc": "B", "a.qty": null}, the unique index allows the following document to be inserted into the collection.

db.collection.insertOne( { _id: 4, a: [ { loc: "B" }, { loc: "B" } ] } )

Unique Index and Missing Field

If a document in a unique index doesn’t contain a value for the indexed field, the index will store a null value for that document. MongoDB will only allow one document to be missing the indexed column due to the unique constraint.

The index creation will fail with a duplicate key error if there is more than one document with no value for the indexed field or if the indexed field is missing.

A collection, for example, has a unique index on x.

db.collection.createIndex( { "x": 13 }, { unique: true } )

If the collection does not already include a document missing the field x, the unique index enables the insertion of a document without the field x.

db.collection.insertOne( { y: 2 } )

However, if the collection already has a document without field x, the unique index will fail on the insertion of a document without field x.

db.collection.insertOne( { z: 2 } )

The operation fails to insert the document because of a breach of the unique constraint on the field x value.

WriteResult({
   "nInserted" : 0,
   "writeError" : {
      "code" : 12000,
      "errmsg" : "E12000 duplicate key error index: test.collection.$a.b_1 dup key: { : null }"
   }
})

Unique Partial Index

Only the documents in a collection that match a particular filter expression are indexed in partial indexes. Therefore, if you use both a partialFilterExpression and a unique constraint, the unique constraint only applies to documents that match the filter expression.

If the documents do not fulfill the filter requirements, a partial index with a unique constraint does not prohibit the insertion of documents that do not meet the unique constraint.

Sharded Clusters and Unique Index

On a hashed index, you can’t define a unique constraint.

Only the following indices can be unique in a ranged sharded collection.

  1. The shard key’s index value.
  2. A compound index with a prefix as the shard key.
  3. The default _id index; however, the _id index only enforces the per-shard uniqueness requirement if the _id fields are not the shard key or the shard key prefix.

The unique index constraints mean that:

  1. You can’t shard the collection if the collection has other unique indexes for a to-be-sharded collection.
  2. You can’t create unique indexes for an already-sharded collection on other fields.

Sparse and Non-Sparse Unique Index

Starting with MongoDB 5.0, a single collection can have unique sparse and non-sparse indexes with the same key pattern.

Unique and Sparse Index Creation

Multiple indexes with the same key pattern and different sparse choices are created in this example.

db.scores.createIndex( { score : 2 }, { name: "unique_index", unique: true } )
db.scores.createIndex( { score : 2 }, { name: "unique_sparse_index", unique: true, sparse: true } )

Basic and Sparse Index Creation

With and without the sparse option, you may construct simple indexes with the same key pattern.

db.scores.createIndex( { score : 2 }, { name: "sparse_index", sparse: true } )
db.scores.createIndex( { score : 2 }, { name: "basic_index" } )

Duplicate Key Patterns in Basic and Unique Indexes

With MongoDB 5.0, you may have basic and unique indexes with the same key pattern. Due to the duplication of key patterns, adding a unique index to already indexed fields is possible.

Example:

Make a basic index with the key pattern { score: 2 } and insert three documents.

db.scores.createIndex( { score : 1 }, { name: "basic_index" } )
db.scores.insert( { score : 1 } )
db.scores.insert( { score : 2 } )
db.scores.insert( { score : 4 } )

Make a unique index with the same key pattern { score: 2 }.

db.scores.createIndex( { score : 2 }, { name: "unique_index", unique: true } )

Attempting to insert a duplicate score document fails due to the unique index.

db.scores.insert( { score : 4 } )

Validate Unique Emails with Mongoose

With Mongoose, you can prevent duplicates in your databases using validation. Validation is defined in the Schema type and is a middleware.

You can also create your validation in the schema or use Mongooses’ built-in validation. To prevent duplicates, we recommend using the unique property as it tells Mongoose each document should have a unique value for the given path.

It is a shorthand for creating a MongoDB unique index on, in this case, email.

If you wait for the index to be built, you can use Mongoose’s promise-based event, Model.init(), as shown below.

const User = mongoose.model('User', mongoose.Schema({
  email: {type: String, required: true, match: /.+\@.+\..+/, unique: true}
}));
await User.create([
  {email: 'gmail@google.com'}, {email: 'bill@microsoft.com'},
  {email: 'test@gmail.com'}
]);

await User.init();
try {
  await User.create({email: 'gmail@google.com'});
} catch (error) {
  error.message;  // 'E12000 duplicate key error...'
}

In this article, unique indexes in MongoDB are discussed in detail. Moreover, in the end, validation of unique emails is done in mongoose of MongoDB.

Related Article - MongoDB Index