Otacilio Oliveira
Otacilio Oliveira

Reputation: 686

Unique array value in Mongo

I'm having a hard time to find a way to make a collection index work the way I need. That collection has an array that will contain two elements, and no other array can have these two elements (in any order):

db.collection.insert(users : [1,2] // should be valid
db.collection.insert(users : [2,3] // should be valid
db.collection.insert(users : [1,3] // should be valid
db.collection.insert(users : [3,2] // should be invalid, since there's another array with that same value.

But, if I use db.collection.createIndex({users:1}, {unique: true}), it won't allow me to have two arrays with a common element:

db.collection.insert(users : [1,2] // valid
db.collection.insert(users : [2,3] // invalid, since 2 is already on another document

One of the solutions I tried was to make the array one level deeper. Creating the very same index, but adding documents a little different would make it almost the way I need, but it would still allow two arrays to have the same value in the reverse orders:

db.chat.insert({ users : { people :  [1,2] }}) // valid
db.chat.insert({ users : { people :  [2,3] }}) // valid
db.chat.insert({ users : { people :  [2,1] }}) // valid, but it should be invalid, since there's another document with [1,2] array value.
db.chat.insert({ users : { people :  [1,2] }}) // invalid

Is there a way to achieve this on a index level?

Upvotes: 2

Views: 1251

Answers (3)

Dmitry
Dmitry

Reputation: 1716

The mongodb doesn't create indexes on the entire array. But...

We want one atomic operation insert or update, and guarantee uniqueness of the array's content? Then, we need to calculate one feature which is the same for all permutations of the array's items, and create an unique index for it.

One way would be to sort array items (solves permutation problem) and concatenate them (creates one feature). The example, in javascript:

function index(arr) {
  return arr.sort().join();
}

users1 = [1, 2], usersIndex1 = index(users1); // "1,2"
users2 = [2, 1], usersIndex2 = index(users2); // "1,2"

// Create the index
db.collection.ensureIndex({usersIndex: 1}, {unique: true});
// 
db.collection.insert({users: users1, usersIndex: usersIndex1}); // Ok
db.collection.insert({users: users2, usersIndex: usersIndex2}); // Error

If the arrays are long, you can apply a hash function on the strings, minimizing the size of the collection. Though, it comes with a price of possible collisions.

Upvotes: 2

Sachin
Sachin

Reputation: 2922

You should first find the records and If no record available then insert that

db.chat.findOne({users: {$all: [3,2]}})
      .then(function(doc){
            if(doc){
              return res.json('already exists');            
            } else {
               db.chat.insert({users: [3,2]})
            }
     })
     .catch(next);

Upvotes: 0

Andi Giga
Andi Giga

Reputation: 4182

You have to write a custom validation in the pre save hook:

Coffee Version

Pre Save Hook

chatSchema.pre('save', (next) ->
   data = @
   @.constructor.findOne {}, (err, doc) ->
      return next err if err?
      return next "Duplicate" if customValidation(data, doc) == false
      return next()
)

Custom Validation

customValidation = (oldDoc, newDoc)->
  #whatever you need
  e.g. return !lodash.equal(oldDoc, newDoc)

Js Version

var customValidation;

chatSchema.pre('save', function(next) {
  var data;
  data = this;
  return this.constructor.findOne({}, function(err, doc) {
    if (err != null) {
      return next(err);
    }
    if (customValidation(data, doc) === false) {
      return next("Duplicate");
    }
    
    return next();
    
  });
});

customValidation = function(oldDoc, newDoc) {
  return !lodash.equal(oldDoc, newDoc);
};

Upvotes: 1

Related Questions