mongoDB / mongoose: unikalne, jeśli nie null

103

Zastanawiałem się, czy istnieje sposób na wymuszenie unikalnego wpisu kolekcji, ale tylko wtedy, gdy wpis nie jest pusty . e Przykładowy schemat:

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

„e-mail” w tym przypadku nie jest wymagany, ale jeśli „e-mail” jest zapisany, chcę się upewnić, że ten wpis jest unikalny (na poziomie bazy danych).

Puste wpisy wydają się mieć wartość „null”, więc każdy wpis bez wiadomości e-mail ulega awarii z opcją „unikalny” (jeśli jest inny użytkownik bez adresu e-mail).

Obecnie rozwiązuję to na poziomie aplikacji, ale chciałbym zapisać to zapytanie db.

dzięki

ezmilhouse
źródło

Odpowiedzi:

169

Począwszy od MongoDB v1.8 +, możesz uzyskać pożądane zachowanie polegające na zapewnieniu unikalnych wartości, ale zezwalając na wiele dokumentów bez pola, ustawiając sparseopcję na true podczas definiowania indeksu. Jak w:

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

Lub w powłoce:

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

Zauważ, że wyjątkowy, rzadki wskaźnik nadal nie pozwala wielu dokumentów przy użyciu emailpola z wartością z nulltylko kilku docs bez w emailpolu.

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

JohnnyHK
źródło
18
Niesamowite! Zdecydowanie najlepsza odpowiedź dla nowicjuszy takich jak ja po 1.8! UWAGA: Mongoose nie zaktualizuje Twojego unikalnego indeksu, aby był rzadki, jeśli po prostu dodasz sparse: true do swojego schematu. Musisz usunąć i ponownie dodać indeks. Nie wiem, czy jest to oczekiwane, czy błąd.
Adam A,
8
„Uwaga: jeśli indeks już istnieje w bazie danych, nie zostanie zastąpiony”. - mongoosejs.com/docs/2.7.x/docs/schematypes.html
damphat
Nie sądzę, aby to odpowiadało na pytanie poprawnie, ponieważ kilka dokumentów bez określonego pola to nie to samo, co kilka dokumentów z wartością zerową w tym polu (których nie można indeksować w sposób unikalny).
kako-nawao
1
@ kako-nawao To prawda, działa tylko w przypadku dokumentów bez tego emailpola, a nie tam, gdzie w rzeczywistości ono ma wartość null. Zobacz zaktualizowaną odpowiedź.
JohnnyHK
2
Nie działa z brakującymi polami. Być może zachowanie zostało zmienione w późniejszych wersjach mongodb. Odpowiedź powinna zostać zaktualizowana.
joniba,
44

tl; dr

Tak, możliwe jest posiadanie wielu dokumentów z polem ustawionym nulllub niezdefiniowanym, przy wymuszaniu unikalnych „rzeczywistych” wartości.

wymagania :

  • MongoDB v3.2 +.
  • Znajomość z wyprzedzeniem swoich konkretnych typów wartości (np. Zawsze stringlub objectkiedy nie null).

Jeśli nie interesują Cię szczegóły, możesz przejść do implementationsekcji.

dłuższa wersja

Aby uzupełnić odpowiedź @ Nolana, zaczynając od MongoDB v3.2, możesz użyć częściowego unikatowego indeksu z wyrażeniem filtru.

Wyrażenie filtra częściowego ma ograniczenia. Może zawierać tylko:

  • wyrażenia równościowe (np. pole: wartość lub za pomocą $eqoperatora),
  • $exists: true wyrażenie,
  • $gt, $gte, $lt, $lteWyrażenia,
  • $type wyrażenia,
  • $and operator tylko na najwyższym poziomie

Oznacza to, że {"yourField"{$ne: null}}nie można użyć trywialnego wyrażenia .

Jednak zakładając, że Twoje pole zawsze używa tego samego typu , możesz użyć $typewyrażenia .

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

W MongoDB v3.6 dodano obsługę określania wielu możliwych typów, które mogą być przekazywane jako tablica:

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

co oznacza, że ​​pozwala, aby wartość była dowolnego z wielu typów, gdy nie null.

Dlatego, jeśli chcemy, aby emailpole w poniższym przykładzie akceptowało jedną stringlub, powiedzmy, binary datawartości, odpowiednim $typewyrażeniem byłoby:

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

realizacja

mangusta

Możesz to określić w schemacie mangusty:

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

lub bezpośrednio dodaj go do kolekcji (która używa natywnego sterownika node.js):

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

natywny sterownik mongodb

za pomocą collection.createIndex

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

mongodb shell

używając db.collection.createIndex:

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

Umożliwi to wstawianie wielu rekordów z nullwiadomością e-mail lub bez pola adresu e-mail, ale nie z tym samym ciągiem wiadomości e-mail.

MasterAM
źródło
Świetna odpowiedź. Jesteś zbawicielem.
r3wt
Ta odpowiedź zrobiła to również dla mnie.
Emmanuel NK,
1
Większość akceptowanych odpowiedzi na to pytanie obejmuje upewnienie się, że nie ustawiasz jawnie wartości null dla indeksowanych kluczy. Że zamiast tego są przekazywane niezdefiniowane. Robiłem to i nadal otrzymuję błąd (podczas używania uniquei sparse). Zaktualizowałem schemat za pomocą tej odpowiedzi, porzuciłem istniejący indeks i zadziałało to jak marzenie.
Phil
Zagłosowano na tę odpowiedź, ponieważ dostarcza wiedzy i możliwych odpowiedzi w oparciu o najczęstsze scenariusze, które w pierwszej kolejności kończą się na tej odpowiedzi SO. Dzięki za szczegółową odpowiedź! : +1:
anothercoder
6

Krótka informacja dla osób badających ten temat.

Wybrana odpowiedź będzie działać, ale możesz zamiast tego rozważyć użycie indeksów częściowych.

Zmieniono w wersji 3.2: Począwszy od MongoDB 3.2, MongoDB zapewnia opcję tworzenia częściowych indeksów. Indeksy częściowe oferują nadzbiór funkcji indeksów rzadkich. Jeśli używasz MongoDB 3.2 lub nowszej, indeksy częściowe powinny być preferowane zamiast indeksów rzadkich.

Więcej dokumentacji na temat indeksów częściowych: https://docs.mongodb.com/manual/core/index-partial/

Nolan Garrido
źródło
2

W rzeczywistości tylko pierwszy dokument, w którym pole „e-mail” nie istnieje, zostanie pomyślnie zapisany. Kolejne zapisy, w których nie ma „e-maila”, zakończą się niepowodzeniem i wyświetlą błąd (patrz fragment kodu poniżej). Dlatego zajrzyj do oficjalnej dokumentacji MongoDB w odniesieniu do unikalnych indeksów i brakujących kluczy tutaj pod adresem 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"});

Z definicji unikalny indeks może zezwalać na przechowywanie tylko jednej wartości tylko raz. Jeśli uznasz null za jedną z takich wartości, można ją wstawić tylko raz! Masz rację w swoim podejściu, zapewniając i weryfikując je na poziomie aplikacji. Tak można to zrobić.

Możesz również przeczytać ten http://www.mongodb.org/display/DOCS/Querying+and+nulls

Samyak Bhuta
źródło