mongoDB / mongoose: eindeutig, wenn nicht null

102

Ich habe mich gefragt, ob es eine Möglichkeit gibt, einen eindeutigen Sammlungseintrag zu erzwingen, aber nur, wenn der Eintrag nicht null ist . e Beispielschema:

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

'email' ist in diesem Fall nicht erforderlich, aber wenn 'email' gespeichert ist, möchte ich sicherstellen, dass dieser Eintrag eindeutig ist (auf Datenbankebene).

Leere Einträge scheinen den Wert 'null' zu erhalten, sodass jeder Eintrag ohne E-Mail mit der Option 'einzigartig' abstürzt (wenn es einen anderen Benutzer ohne E-Mail gibt).

Im Moment löse ich es auf Anwendungsebene, würde aber gerne diese Datenbankabfrage speichern.

Vielen Dank

Ezmilhouse
quelle

Antworten:

167

Ab MongoDB v1.8 + können Sie das gewünschte Verhalten erzielen, indem Sie eindeutige Werte sicherstellen, aber mehrere Dokumente ohne Feld zulassen, indem Sie die sparseOption beim Definieren des Index auf true setzen. Wie in:

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

Oder in der Shell:

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

Beachten Sie, dass ein einzigartiger, spärlicher Index immer noch nicht möglich, mehreren Dokumente mit einem erlaubt emailFeld mit einem Wert von nullnur mehr Dokumenten ohne ein emailFeld.

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

JohnnyHK
quelle
18
Genial! Auf jeden Fall die beste Antwort für Neulinge wie mich nach 1.8! HINWEIS: Mongoose aktualisiert Ihren eindeutigen Index nicht so, dass er spärlich ist, wenn Sie Ihrem Schema nur ein spärliches: true hinzufügen. Sie müssen den Index löschen und erneut hinzufügen. Keine Ahnung, ob das erwartet wird oder ein Fehler.
Adam A
8
"Hinweis: Wenn der Index bereits in der Datenbank vorhanden ist, wird er nicht ersetzt." - mongoosejs.com/docs/2.7.x/docs/schematypes.html
damphat
Ich denke nicht, dass dies die Frage richtig beantwortet, da einige Dokumente ohne ein bestimmtes Feld nicht mit einigen Dokumenten mit einem Nullwert in diesem Feld identisch sind (die nicht eindeutig indiziert werden können).
Kako-Nawao
1
@ kako-nawao Das stimmt, es funktioniert nur für Dokumente ohne emailFeld, nicht dort, wo es tatsächlich einen Wert von hat null. Siehe aktualisierte Antwort.
JohnnyHK
2
Funktioniert nicht mit fehlenden Feldern. Möglicherweise wurde das Verhalten in späteren Versionen von Mongodb geändert. Die Antwort sollte aktualisiert werden.
Joniba
44

tl; dr

Ja, es ist möglich, mehrere Dokumente mit einem Feld zu nulldefinieren oder nicht zu definieren, während eindeutige "tatsächliche" Werte erzwungen werden.

Anforderungen :

  • MongoDB v3.2 +.
  • Kennen Sie Ihre konkreten Werttypen im Voraus (z. B. immer a stringoder objectwenn nicht null).

Wenn Sie nicht an den Details interessiert sind, können Sie zum implementationAbschnitt springen .

längere Version

Um die Antwort von @ Nolan zu ergänzen, können Sie ab MongoDB v3.2 einen teilweise eindeutigen Index mit einem Filterausdruck verwenden.

Der Teilfilterausdruck weist Einschränkungen auf. Es kann nur Folgendes enthalten:

  • Gleichheitsausdrücke (dh Feld: Wert oder Verwendung des $eqOperators),
  • $exists: true Ausdruck,
  • $gt, $gte, $lt, $lteAusdrücke,
  • $type Ausdrücke,
  • $and Bediener nur auf der obersten Ebene

Dies bedeutet, dass der triviale Ausdruck {"yourField"{$ne: null}}nicht verwendet werden kann.

Unter der Annahme, dass Ihr Feld immer denselben Typ verwendet , können Sie einen $typeAusdruck verwenden .

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

MongoDB v3.6 hat Unterstützung für die Angabe mehrerer möglicher Typen hinzugefügt, die als Array übergeben werden können:

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

Dies bedeutet, dass der Wert von einem von mehreren Typen sein kann, wenn dies nicht der Fall ist null.

Wenn wir also zulassen möchten, dass das emailFeld im folgenden Beispiel entweder stringoder beispielsweise binary dataWerte akzeptiert , wäre ein geeigneter $typeAusdruck:

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

Implementierung

Mungo

Sie können es in einem Mungo-Schema angeben:

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

oder fügen Sie es direkt der Sammlung hinzu (die den nativen node.js-Treiber verwendet):

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

native mongodb treiber

mit collection.createIndex

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

Mongodb Muschel

mit db.collection.createIndex:

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

Auf diese Weise können mehrere Datensätze mit einer nullE-Mail oder ohne E-Mail-Feld eingefügt werden, jedoch nicht mit derselben E-Mail-Zeichenfolge.

MasterAM
quelle
Tolle Antwort. Du bist ein Retter.
r3wt
Diese Antwort hat es auch für mich getan.
Emmanuel NK
1
Bei den meisten akzeptierten Antworten auf diese Frage müssen Sie sicherstellen, dass Sie für Ihre indizierten Schlüssel keine expliziten Nullwerte festlegen. Dass sie stattdessen undefiniert übergeben werden. Ich tat das und noch den Fehler bekommen (bei der Verwendung uniqueund sparse). Ich habe mein Schema mit dieser Antwort aktualisiert, meinen vorhandenen Index gelöscht und es hat wie ein Zauber funktioniert.
Phil
Für diese gestimmt, weil sie das Wissen und die möglichen Antworten basierend auf den häufigsten Szenarien liefert, die man in erster Linie auf diese SO-Antwort erhalten würde. Danke für die ausführliche Antwort! : +1:
anderer Codierer
6

Nur ein kurzes Update für diejenigen, die dieses Thema erforschen.

Die ausgewählte Antwort funktioniert, Sie können jedoch auch Teilindizes verwenden.

In Version 3.2 geändert: Ab MongoDB 3.2 bietet MongoDB die Möglichkeit, Teilindizes zu erstellen. Teilindizes bieten eine Obermenge der Funktionalität von Sparse-Indizes. Wenn Sie MongoDB 3.2 oder höher verwenden, sollten Teilindizes gegenüber spärlichen Indizes bevorzugt werden.

Weitere Informationen zu Teilindizes: https://docs.mongodb.com/manual/core/index-partial/

Nolan Garrido
quelle
2

Tatsächlich wird nur das erste Dokument, in dem "E-Mail" als Feld nicht vorhanden ist, erfolgreich gespeichert. Nachfolgende Speicherungen, bei denen "E-Mail" nicht vorhanden ist, schlagen fehl, während ein Fehler ausgegeben wird (siehe Codeausschnitt unten). Informationen dazu finden Sie in der offiziellen Dokumentation zu MongoDB in Bezug auf eindeutige Indizes und fehlende Schlüssel hier unter 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 Definition kann mit einem eindeutigen Index nur ein Wert nur einmal gespeichert werden. Wenn Sie null als einen solchen Wert betrachten, kann er nur einmal eingefügt werden! Sie sind in Ihrem Ansatz korrekt, indem Sie ihn auf Anwendungsebene sicherstellen und validieren. So kann es gemacht werden.

Vielleicht möchten Sie auch diese http://www.mongodb.org/display/DOCS/Querying+and+nulls lesen

Samyak Bhuta
quelle