web-dev-qa-db-fra.com

mongoDB / mongoose: unique sinon nul

Je me demandais s'il y avait moyen de forcer une entrée de collection unique mais seulement si l'entrée n'est pas nulle. e Exemple de schéma:

var UsersSchema = new Schema({
    name  : {type: String, trim: true, index: true, required: true},
    email : {type: String, trim: true, index: true, unique: true}
});

'email' dans ce cas n'est pas requis mais si 'email' est enregistré, je veux m'assurer que cette entrée est unique (au niveau de la base de données).

Les entrées vides semblent obtenir la valeur "null", donc chaque entrée sans e-mail se bloque avec l'option "unique" (s'il existe un utilisateur différent sans e-mail).

En ce moment, je le résous au niveau de l'application, mais j'aimerais enregistrer cette requête db.

tHX

84
ezmilhouse

Depuis MongoDB v1.8 +, vous pouvez obtenir le comportement souhaité pour garantir des valeurs uniques mais autoriser plusieurs documents sans le champ en définissant l'option sparse sur true lors de la définition de l'index. Un péché:

email : {type: String, trim: true, index: true, unique: true, sparse: true}

Ou dans le Shell:

db.users.ensureIndex({email: 1}, {unique: true, sparse: true});

Notez qu'un index unique et clairsemé n'autorise toujours pas plusieurs documents avec un champ email avec une valeur de null , seulement plusieurs documents sans un champ email.

Voir http://docs.mongodb.org/manual/core/index-sparse/

139
JohnnyHK

tl; dr

Oui, il est possible d'avoir plusieurs documents avec un champ défini sur null ou non défini, tout en appliquant des valeurs "réelles" uniques.

exigences :

  • MongoDB v3.2 +.
  • Connaître à l'avance votre ou vos types de valeur concrets (par exemple, toujours un string ou object quand ce n'est pas null).

Si les détails ne vous intéressent pas, n'hésitez pas à passer à la section implementation.

version plus longue

Pour compléter la réponse de @ Nolan, à partir de MongoDB v3.2, vous pouvez utiliser un index unique partiel avec une expression de filtre.

L'expression de filtre partiel a des limites. Il ne peut comprendre que les éléments suivants:

  • expressions d'égalité (c.-à-d. champ: valeur ou en utilisant le $eq opérateur),
  • $exists: true expression,
  • $gt, $gte, $lt, $lte expressions,
  • $type expressions,
  • $and opérateur au niveau supérieur uniquement

Cela signifie que l'expression triviale {"yourField"{$ne: null}} Ne peut pas être utilisé.

Cependant, en supposant que votre champ utilise toujours le même type , vous pouvez utiliser un $type expression .

{ field: { $type: <BSON type number> | <String alias> } }

MongoDB v3.6 a ajouté la prise en charge de la spécification de plusieurs types possibles, qui peuvent être passés sous forme de tableau:

{ field: { $type: [ <BSON type1> , <BSON type2>, ... ] } }

ce qui signifie qu'il permet à la valeur d'être de n'importe quel nombre de plusieurs types lorsqu'elle n'est pas null.

Par conséquent, si nous voulons autoriser le champ email dans l'exemple ci-dessous à accepter string ou, disons, binary data valeurs, un $type l'expression serait:

{email: {$type: ["string", "binData"]}}

la mise en oeuvre

mangouste

Vous pouvez le spécifier dans un schéma mangouste:

const UsersSchema = new Schema({
  name: {type: String, trim: true, index: true, required: true},
  email: {
    type: String, trim: true, index: {
      unique: true,
      partialFilterExpression: {email: {$type: "string"}}
    }
  }
});

ou l'ajouter directement à la collection (qui utilise le pilote node.js natif):

User.collection.createIndex("email", {
  unique: true,
  partialFilterExpression: {
    "email": {
      $type: "string"
    }
  }
});

pilote natif mongodb

en utilisant collection.createIndex

db.collection('users').createIndex({
    "email": 1
  }, {
    unique: true,
    partialFilterExpression: {
      "email": {
        $type: "string"
      }
    }
  },
  function (err, results) {
    // ...
  }
);

mongodb Shell

en utilisant db.collection.createIndex :

db.users.createIndex({
  "email": 1
}, {
  unique: true, 
  partialFilterExpression: {
    "email": {$type: "string"}
  }
})

Cela permettra d'insérer plusieurs enregistrements avec un e-mail null, ou sans aucun champ e-mail, mais pas avec la même chaîne d'e-mail.

29
MasterAM

Juste une mise à jour rapide pour ceux qui recherchent ce sujet.

La réponse sélectionnée fonctionnera, mais vous voudrez peut-être envisager d'utiliser des index partiels à la place.

Modifié dans la version 3.2: à partir de MongoDB 3.2, MongoDB offre la possibilité de créer des index partiels. Les index partiels offrent un sur-ensemble des fonctionnalités des index clairsemés. Si vous utilisez MongoDB 3.2 ou version ultérieure, les index partiels doivent être préférés aux index clairsemés.

Plus de doco sur les index partiels: https://docs.mongodb.com/manual/core/index-partial/

4
Nolan Garrido

En fait, seul le premier document où "e-mail" en tant que champ n'existe pas sera enregistré avec succès. Les enregistrements ultérieurs dans lesquels "e-mail" n'est pas présent échoueront lors de l'envoi d'une erreur (voir l'extrait de code ci-dessous). Pour la raison, consultez la documentation officielle de MongoDB concernant les index uniques et les clés manquantes ici à http://www.mongodb.org/display/DOCS/Indexes#Indexes-UniqueIndexes .

  // NOTE: Code to executed in mongo console.

  db.things.ensureIndex({firstname: 1}, {unique: true});
  db.things.save({lastname: "Smith"});

  // Next operation will fail because of the unique index on firstname.
  db.things.save({lastname: "Jones"});

Par définition, un index unique ne peut autoriser qu'une seule valeur à être stockée une seule fois. Si vous considérez null comme une de ces valeurs, il ne peut être inséré qu'une seule fois! Vous avez raison dans votre approche en la garantissant et en la validant au niveau de l'application. Voilà comment cela peut se faire.

Vous pouvez également aimer lire ceci http://www.mongodb.org/display/DOCS/Querying+and+nulls

2
Samyak Bhuta