80

If I had a schema like this:

var person = new Schema({
  firstName:  String,
  lastName: String,
});

I'd like to ensure that there is only one document with the same firstName and lastName.

How can I accomplish this?

JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
martinpaulucci
  • 2,322
  • 5
  • 24
  • 28

6 Answers6

138

You can define a unique compound index using an index call on your schema:

person.index({ firstName: 1, lastName: 1}, { unique: true });
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • 4
    @chovy You don't need to drop the collection, but you do need to drop any existing compound index on the fields. – JohnnyHK Jan 29 '15 at 05:58
  • 3
    I did not have an existing compound index. But I was able to add duplicates just fine (repeatedly), even after restarting my app server. It didn't take until I dropped the collection. – chovy Jan 29 '15 at 06:23
  • 2
    This worked for me on the first try, without having to drop or modify the collection in any other way – Dsel Aug 20 '15 at 18:10
  • Mongoose makes available a `Model.ensureIndexes([cb])` method which "Sends `ensureIndex` commands to mongo for each index declared in the schema." This may help with the issues of getting the index to stick, though I have read that you should be careful about using the `ensureIndex` command in production – Patrick Fowler Sep 23 '15 at 23:57
  • 4
    The index will only be created if the unique requirements are met. This explains why @chovy had to drop his collection (could have just delete the duplicates) – Ronen Teva Jul 07 '16 at 03:23
  • @RonenTeva is there anyway to list the dups? – chovy Jul 07 '16 at 17:57
  • @chovy sure, posted it as a new question: [How to list and remove duplicate values using mongoose](http://stackoverflow.com/questions/38252491/how-to-list-duplicate-values-using-mongoose/38252492) – Ronen Teva Jul 07 '16 at 18:12
  • 3
    To determine what duplicates are preventing the index from sticking, you could do: `Model.on('index', function(err) { console.log(err) });` – kenneth koontz Aug 11 '16 at 18:18
  • 1
    Is this unique only for (firstName+lastName) or is it unique for (lastName+firstName) as well? – Puneet Dec 04 '16 at 17:47
  • When I use this in model, how to set in route: if (unique compound index) { console.log('unique compound index is set'); } ? – Nenad M Mar 30 '18 at 17:01
  • you can add a name for your index, with a unique index name, it will always work. ```person.index({firstName: 1, lastName: 1}, {name: 'unique_index_name', unique: true})``` – ganjim May 05 '18 at 13:06
  • Out of curiosity - what `1` means here? – Dominik Krzywiecki Oct 04 '19 at 19:09
  • @DominikKrzywiecki 1 = ascending, -1 = descending order – JohnnyHK Oct 04 '19 at 21:36
  • I am using MongoDB Atlas and I had to first drop the collection. – kmoser Dec 22 '21 at 22:10
42

Fun little thing I only recently discovered through experimentation with Mongoose. I have the following schema for example:

const ShapesSchema = new mongoose.Schema({
  name: { type: String, required: true },
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
})

ShapesSchema.index({ name: 1, user: 1 }, { unique: true })

mongoose.model('Shapes', ShapesSchema)

The idea was to create a compound index that was unique on name and user together. This way a user could create as many shapes as they wanted as long as each of their shapes had a distinct name. And the inverse was supposed to be true as well - shapes could have the same name as long as they had different users. It didn't work out like that for me.

What I noticed was that aside from the index on _id, three other index entries were created. One each for name, user, and name_user all set to be unique. I made a modification to the schema and included unique: false to each of the fields I was using for the compound index and suddenly it all worked as expected. What I ended up with was:

const ShapesSchema = new mongoose.Schema({
  name: { type: String, required: true, unique: false },
  user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', unique: false }
})

ShapesSchema.index({ name: 1, user: 1 }, { unique: true })

mongoose.model('Shapes', ShapesSchema)

Looking at the indexes that were created as a result I still see the three indexes - name, user, and name_user. But the difference is that the first two are not set to be unique where the last one, the compound, is. Now my use case of multiple distinct shapes per user, or identically named shapes with different users works like a champ.

ldapmonkey
  • 511
  • 5
  • 6
  • 2
    This helped me immensely. I also needed to add `mongoose.connect(uri, { useCreateIndex: true });` per the docs here: https://mongoosejs.com/docs/guide.html#indexes – PhysRex Apr 18 '20 at 23:11
3

defining your schema like this


var person = new Schema({
firstName:  String,
lastName: String,
index: true,
unique: true, 

});

or


person.index({ firstName: 1, lastName: 1}, { unique: true });

Deeksha Sharma
  • 3,199
  • 1
  • 19
  • 16
1
const personSchema = new Schema({ firstName:  String, lastName: String });
const person = mongoose.model('recipients', personSchema);
person.createIndexes();

You might need to get rid of all duplicates in the collection or do it the faster and easy way :

Edit your Code, Drop the Collection and then restart Mongo.

Akintunde
  • 3,525
  • 1
  • 21
  • 23
0

I haven't tried this, but using a unique index should do the trick.

db.person.ensureIndex( { "firstname": 1, "lastname": 1 }, { unique: true } )
-3

You can define Your Schema like this.

const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const bcrypt = require("bcryptjs");

    const userSchema = new Schema({
      firstName: {
        trim: true,
        type: String,
        required: [true, "firstName is required!"],
        validate(value) {
          if (value.length < 2) {
            throw new Error("firstName is invalid!");
          }
        }
      },
      lastName: {
        trim: true,
        type: String,
        required: [true, "lastName is required!"],
        validate(value) {
          if (value.length < 2) {
            throw new Error("lastName is invalid!");
          }
        }
      },
      username: {
        unique: [true, "Username already available"],
        type: String,
        required: [true, "Username is required"],
        validate(value) {
          if (value.length < 10) {
            throw new Error("Username is invalid!");
          }
        }
      },
      mobile: {
        unique: [true, "Mobile Number alraedy available"],
        type: String,
        required: [true, "Mobile Number is required"],
        validate(value) {
          if (value.length !== 10) {
            throw new Error("Mobile Number is invalid!");
          }
        }
      },
      password: {
        type: String,
        required: [true, "Password is required"],
        validate(value) {
          if (value.length < 8) {
            throw new Error("Password is invalid!");
          }
        }
      },
      gender: {
        type: String
      },
      dob: {
        type: Date,
        default: Date.now()
      },
      address: {
        street: {
          type: String
        },
        city: {
          trim: true,
          type: String
        },
        pin: {
          trim: true,
          type: Number,
          validate(value) {
            if (!(value >= 100000 && value <= 999999)) {
              throw new Error("Pin is invalid!");
            }
          }
        }
      }
      date: { type: Date, default: Date.now }
    });
Ankit Kumar Rajpoot
  • 5,188
  • 2
  • 38
  • 32