mongoDB / mongoose: único si no es nulo

103

Me preguntaba si hay forma de forzar una entrada de colección única, pero solo si la entrada no es nula . e Esquema de muestra:

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

"email" en este caso no es obligatorio, pero si se guarda "email" quiero asegurarme de que esta entrada sea única (a nivel de base de datos).

Las entradas vacías parecen tener el valor 'nulo', por lo que cada entrada sin correo electrónico se bloquea con la opción 'única' (si hay un usuario diferente sin correo electrónico).

Ahora mismo lo estoy resolviendo a nivel de aplicación, pero me encantaría guardar esa consulta db.

Gracias

ezmilhouse
fuente

Respuestas:

168

A partir de MongoDB v1.8 +, puede obtener el comportamiento deseado de garantizar valores únicos pero permitiendo múltiples documentos sin el campo configurando la sparseopción en verdadero al definir el índice. Como en:

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

O en el caparazón:

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

Tenga en cuenta que un índice disperso único aún no permite varios documentos con un emailcampo con un valor de null, solo varios documentos sin un emailcampo.

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

JohnnyHK
fuente
18
¡Increíble! ¡Definitivamente la mejor respuesta para novatos como yo después de 1.8! NOTA: Mongoose no actualizará su índice único para que sea escaso si solo agrega un escaso: verdadero a su esquema. Tienes que soltar y volver a agregar el índice. No sé si eso es esperado o un error.
Adam A
8
"Nota: si el índice ya existe en la base de datos, no será reemplazado". - mongoosejs.com/docs/2.7.x/docs/schematypes.html
damphat
No creo que esto responda a la pregunta correctamente, ya que unos pocos documentos sin un campo específico no son lo mismo que unos pocos documentos con un valor nulo en ese campo (que no se puede indexar de forma única).
kako-nawao
1
@ kako-nawao Eso es cierto, solo funciona para documentos sin el emailcampo, no donde realmente tiene un valor de null. Ver respuesta actualizada.
JohnnyHK
2
No funciona con campos faltantes. Quizás el comportamiento se cambió en versiones posteriores de mongodb. La respuesta debe actualizarse.
Joniba
44

tl; dr

Sí, es posible tener varios documentos con un campo configurado nullo no definido, mientras se aplican valores "reales" únicos.

requisitos :

  • MongoDB v3.2 +.
  • Conocer sus tipos de valores concretos de antemano (por ejemplo, siempre a stringo objectcuándo no null).

Si no está interesado en los detalles, no dude en pasar a la implementationsección.

versión más larga

Para complementar la respuesta de @ Nolan, comenzando con MongoDB v3.2, puede usar un índice único parcial con una expresión de filtro.

La expresión de filtro parcial tiene limitaciones. Solo puede incluir lo siguiente:

  • expresiones de igualdad (es decir, campo: valor o usando el $eqoperador),
  • $exists: true expresión,
  • $gt, $gte, $lt, $lteExpresiones,
  • $type expresiones,
  • $and operador en el nivel superior solamente

Esto significa que {"yourField"{$ne: null}}no se puede utilizar la expresión trivial .

Sin embargo, asumiendo que su campo siempre usa el mismo tipo , puede usar una $typeexpresión .

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

MongoDB v3.6 agregó soporte para especificar múltiples tipos posibles, que se pueden pasar como una matriz:

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

lo que significa que permite que el valor sea de varios tipos múltiples cuando no null.

Por lo tanto, si queremos permitir que el emailcampo del ejemplo siguiente acepte valores stringo, digamos binary data, una $typeexpresión adecuada sería:

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

implementación

mangosta

Puede especificarlo en un esquema de mangosta:

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"}}
    }
  }
});

o agregarlo directamente a la colección (que usa el controlador nativo node.js):

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

driver nativo mongodb

utilizando collection.createIndex

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

cáscara de mongodb

usando db.collection.createIndex:

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

Esto permitirá insertar varios registros con un nullcorreo electrónico, o sin ningún campo de correo electrónico, pero no con la misma cadena de correo electrónico.

MasterAM
fuente
Respuesta impresionante. Eres un salvador.
r3wt
Esta respuesta también lo hizo por mí.
Emmanuel NK
1
La mayoría de las respuestas aceptadas para esta pregunta implican asegurarse de no establecer explícitamente valores nulos en sus claves indexadas. Que en cambio se pasen sin definir. Estaba haciendo eso y todavía obtenía el error (mientras usaba uniquey sparse). Actualicé mi esquema con esta respuesta, eliminé mi índice existente y funcionó como un encanto.
Phil
Votado por este, porque proporciona el conocimiento y las posibles respuestas basadas en los escenarios más comunes, uno terminaría en esta respuesta SO en primer lugar. ¡Gracias por la respuesta detallada! : +1:
codificador
6

Solo una actualización rápida para quienes investigan este tema.

La respuesta seleccionada funcionará, pero es posible que desee considerar el uso de índices parciales en su lugar.

Modificado en la versión 3.2: a partir de MongoDB 3.2, MongoDB ofrece la opción de crear índices parciales. Los índices parciales ofrecen un superconjunto de la funcionalidad de los índices dispersos. Si está utilizando MongoDB 3.2 o posterior, se deben preferir los índices parciales a los índices dispersos.

Más doco sobre índices parciales: https://docs.mongodb.com/manual/core/index-partial/

Nolan Garrido
fuente
2

En realidad, solo se guardará correctamente el primer documento en el que no exista "correo electrónico" como campo. Los guardados posteriores en los que el "correo electrónico" no esté presente fallarán y generarán un error (consulte el fragmento de código a continuación). Por esa razón, consulte la documentación oficial de MongoDB con respecto a los índices únicos y las claves perdidas aquí en 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"});

Por definición, el índice único solo puede permitir que un valor se almacene solo una vez. Si considera nulo como uno de esos valores, solo se puede insertar una vez. Tiene razón en su enfoque al asegurarlo y validarlo a nivel de aplicación. Así es como se puede hacer.

También te puede interesar leer este http://www.mongodb.org/display/DOCS/Querying+and+nulls

Samyak Bhuta
fuente