mongoDB / mongoose: unico se non nullo


103

Mi chiedevo se esiste un modo per forzare una voce di raccolta univoca ma solo se la voce non è nulla . e Schema di esempio:

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

"email" in questo caso non è richiesto, ma se "email" viene salvato voglio assicurarmi che questa voce sia univoca (a livello di database).

Le voci vuote sembrano ottenere il valore "null", quindi ogni voce senza e-mail va in crash con l'opzione "unico" (se c'è un utente diverso senza e-mail).

In questo momento lo sto risolvendo a livello di applicazione, ma mi piacerebbe salvare quella query db.

grazie

Risposte:


168

A partire da MongoDB v1.8 + è possibile ottenere il comportamento desiderato garantendo valori univoci ma consentendo più documenti senza il campo impostando l' sparseopzione su true durante la definizione dell'indice. Come in:

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

O nella shell:

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

Tieni presente che un indice univoco e scarso non consente ancora più documenti con un emailcampo con un valore di null, solo più documenti senza un emailcampo.

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


18
Eccezionale! Sicuramente la migliore risposta per i principianti come me dopo 1.8! NOTA: Mongoose non aggiornerà il tuo indice univoco in modo che sia sparse se aggiungi solo sparse: true al tuo schema. Devi eliminare e aggiungere nuovamente l'indice. Non so se è previsto o un bug.
Adam A,

8
"Nota: se l'indice esiste già sul db, non verrà sostituito." - mongoosejs.com/docs/2.7.x/docs/schematypes.html
damphat

Non penso che questo risponda correttamente alla domanda, poiché alcuni documenti senza un campo specifico non sono gli stessi di alcuni documenti con un valore nullo su quel campo (che non può essere indicizzato in modo univoco).
kako-nawao

1
@ kako-nawao È vero, funziona solo per documenti senza emailcampo, non dove in realtà ha un valore null. Vedi risposta aggiornata.
JohnnyHK

2
Non funziona con i campi mancanti. Forse il comportamento è stato modificato nelle versioni successive di mongodb. La risposta dovrebbe essere aggiornata.
joniba

44

tl; dr

Sì, è possibile avere più documenti con un campo impostato su nullo non definito, applicando valori "effettivi" univoci.

requisiti :

  • MongoDB v3.2 +.
  • Conoscere in anticipo il proprio tipo di valore concreto (ad esempio, sempre a stringo objectquando no null).

Se non sei interessato ai dettagli, non esitare a passare alla implementationsezione.

versione più lunga

Per integrare la risposta di @ Nolan, a partire da MongoDB v3.2 puoi utilizzare un indice univoco parziale con un'espressione di filtro.

L'espressione del filtro parziale ha dei limiti. Può includere solo quanto segue:

  • espressioni di uguaglianza (ad es. campo: valore o utilizzando l' $eqoperatore),
  • $exists: true espressione,
  • $gt, $gte, $lt, $lteEspressioni,
  • $type espressioni,
  • $and solo operatore di primo livello

Ciò significa che l'espressione banale {"yourField"{$ne: null}}non può essere utilizzata.

Tuttavia, supponendo che il campo utilizzi sempre lo stesso tipo , è possibile utilizzare $typeun'espressione .

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

MongoDB v3.6 ha aggiunto il supporto per specificare più tipi possibili, che possono essere passati come un array:

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

il che significa che consente al valore di essere di un numero qualsiasi di più tipi quando non lo è null.

Pertanto, se vogliamo consentire al emailcampo nell'esempio seguente di accettare uno stringo, diciamo, binary datavalori, $typeun'espressione appropriata sarebbe:

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

implementazione

mangusta

Puoi specificarlo in uno schema mongoose:

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 aggiungilo direttamente alla raccolta (che utilizza il driver node.js nativo):

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

driver mongodb nativo

utilizzando collection.createIndex

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

mongodb shell

utilizzando db.collection.createIndex:

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

Ciò consentirà di inserire più record con nullun'e-mail o senza un campo e-mail, ma non con la stessa stringa e-mail.


Risposta fantastica. Sei un salvatore.
r3wt

Questa risposta lo ha fatto anche per me.
Emmanuel NK

1
La maggior parte delle risposte accettate per questa domanda implica la verifica di non impostare esplicitamente valori nulli per le chiavi indicizzate. Che invece siano passati indefiniti. Lo stavo facendo e continuavo a ricevere l'errore (durante l'utilizzo uniquee sparse). Ho aggiornato il mio schema con questa risposta, ho abbandonato il mio indice esistente e ha funzionato a meraviglia.
Phil

Votato per questo, perché fornisce le conoscenze e le possibili risposte in base agli scenari più comuni si finirebbe per questa risposta SO in primo luogo. Grazie per la risposta dettagliata! : +1:
codificatore il

6

Solo un rapido aggiornamento a coloro che ricercano questo argomento.

La risposta selezionata funzionerà, ma potresti prendere in considerazione l'utilizzo di indici parziali.

Modificato nella versione 3.2: a partire da MongoDB 3.2, MongoDB offre la possibilità di creare indici parziali. Gli indici parziali offrono un superset delle funzionalità degli indici sparsi. Se stai usando MongoDB 3.2 o versioni successive, gli indici parziali dovrebbero essere preferiti rispetto agli indici sparsi.

Ulteriori documenti sugli indici parziali: https://docs.mongodb.com/manual/core/index-partial/


2

In realtà, solo il primo documento in cui "email" come campo non esiste verrà salvato correttamente. I salvataggi successivi in ​​cui "email" non è presente non andranno a buon fine dando un errore (vedere lo snippet di codice di seguito). Per il motivo, guarda la documentazione ufficiale di MongoDB riguardo agli indici univoci e alle chiavi mancanti qui su 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"});

Per definizione un indice univoco può consentire la memorizzazione di un valore solo una volta. Se consideri null come uno di questi valori, può essere inserito solo una volta! Hai corretto il tuo approccio garantendolo e convalidandolo a livello di applicazione. È così che si può fare.

Potrebbe piacerti anche leggere questo http://www.mongodb.org/display/DOCS/Querying+and+nulls

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.