mongoDB / mongoose: уникальный, если не нуль


103

Мне было интересно, есть ли способ принудительно создать уникальную запись коллекции, но только если запись не равна нулю . e Пример схемы:

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

«электронная почта» в этом случае не требуется, но если «электронная почта» сохранена, я хочу убедиться, что эта запись уникальна (на уровне базы данных).

Кажется, что пустые записи получают значение «null», поэтому каждая запись без электронной почты вылетает с опцией «уникальный» (если есть другой пользователь без электронной почты).

Прямо сейчас я решаю это на уровне приложения, но хотел бы сохранить этот запрос к базе данных.

Спасибо

Ответы:


169

Начиная с MongoDB v1.8 +, вы можете получить желаемое поведение, обеспечивающее уникальные значения, но позволяющее несколько документов без поля, установив для sparseпараметра значение true при определении индекса. Как в:

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

Или в оболочке:

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

Обратите внимание , что уникальный, редкий индекс по- прежнему не позволяет несколько документов с emailполем с значением из null, только нескольких Docs без в emailполе.

См. Http://docs.mongodb.org/manual/core/index-sparse/


18
Потрясающие! Определенно лучший ответ для новичков вроде меня после 1.8! ПРИМЕЧАНИЕ. Mongoose не обновит ваш уникальный индекс до разреженного, если вы просто добавите в схему sparse: true. Вам нужно отбросить и заново добавить индекс. Не знаю, ожидается ли это или ошибка.
Adam A

8
«Примечание: если индекс уже существует в базе данных, он не будет заменен». - mongoosejs.com/docs/2.7.x/docs/schematypes.html
damphat

Я не думаю, что это правильно отвечает на вопрос, поскольку несколько документов без определенного поля не то же самое, что несколько документов с нулевым значением в этом поле (которое не может быть однозначно проиндексировано).
kako-nawao

1
@ kako-nawao Это правда, это работает только для документов без emailполя, а не там, где оно на самом деле имеет значение null. См. Обновленный ответ.
JohnnyHK

2
Не работает с отсутствующими полями. Возможно, поведение было изменено в более поздних версиях mongodb. Ответ следует обновить.
joniba 03

44

tl; dr

Да, возможно иметь несколько документов с полем, установленным nullили не определенным, с применением уникальных «фактических» значений.

требования :

  • MongoDB v3.2 +.
  • Зная заранее свой конкретный тип (типы) значений (например, всегда а stringили objectкогда нет null).

Если вас не интересуют подробности, не стесняйтесь переходить к implementationразделу.

более длинная версия

Чтобы дополнить ответ @Nolan, начиная с MongoDB v3.2, вы можете использовать частичный уникальный индекс с выражением фильтра.

У выражения частичного фильтра есть ограничения. Он может включать только следующее:

  • выражения равенства (например, поле: значение или использование $eqоператора),
  • $exists: true выражение
  • $gt, $gte, $lt, $lteВыражение,
  • $type выражения
  • $and оператор только на верхнем уровне

Это означает, что использовать банальное выражение {"yourField"{$ne: null}}нельзя.

Однако, предполагая, что ваше поле всегда использует один и тот же тип , вы можете использовать $typeвыражение .

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

В MongoDB v3.6 добавлена ​​поддержка для указания нескольких возможных типов, которые могут быть переданы в виде массива:

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

что означает, что он позволяет значению быть любого из множества нескольких типов, когда это не так null.

Следовательно, если мы хотим, чтобы emailполе в приведенном ниже примере могло принимать либо, stringлибо, скажем, binary dataзначения, подходящим $typeвыражением было бы:

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

реализация

мангуста

Вы можете указать это в схеме мангуста:

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

или напрямую добавить его в коллекцию (которая использует собственный драйвер node.js):

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

родной драйвер mongodb

с помощью collection.createIndex

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

оболочка mongodb

используя db.collection.createIndex:

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

Это позволит вставить несколько записей с nullэлектронной почтой или вообще без поля электронной почты, но не с той же строкой электронной почты.


Отличный ответ. Ты спаситель.
r3wt

Этот ответ сделал то же самое и со мной.
Эммануэль Н.К.

1
Большинство принятых ответов на этот вопрос включают проверку того, что вы явно не устанавливаете нулевые значения для своих индексированных ключей. Чтобы они вместо этого передавались неопределенными. Я делал это и все еще получал ошибку (при использовании uniqueи sparse). Я обновил свою схему этим ответом, отказался от существующего индекса, и он работал как шарм.
Фил

Проголосовал за этот, потому что он предоставляет знания и возможные ответы, основанные на наиболее распространенных сценариях, в первую очередь, на этом ответе SO. Спасибо за подробный ответ! : +1:
anothercoder

6

Просто быстрое обновление для тех, кто исследует эту тему.

Выбранный ответ будет работать, но вы можете рассмотреть возможность использования вместо него частичных индексов.

Изменено в версии 3.2: Начиная с MongoDB 3.2, MongoDB предоставляет возможность создания частичных индексов. Частичные индексы предлагают расширенный набор функций разреженных индексов. Если вы используете MongoDB 3.2 или новее, частичные индексы должны быть предпочтительнее разреженных индексов.

Дополнительная документация по частичным индексам: https://docs.mongodb.com/manual/core/index-partial/


2

Фактически, только первый документ, в котором отсутствует поле «email», будет успешно сохранен. Последующие сохранения без «электронной почты» будут завершаться ошибкой (см. Фрагмент кода ниже). По этой причине посмотрите официальную документацию MongoDB относительно уникальных индексов и отсутствующих ключей здесь, на 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"});

По определению уникальный индекс может допускать сохранение только одного значения только один раз. Если вы рассматриваете null как одно из таких значений, его можно вставить только один раз! Вы правы в своем подходе, гарантируя и проверяя его на уровне приложения. Вот как это можно сделать.

Вы также можете прочитать это http://www.mongodb.org/display/DOCS/Querying+and+nulls

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.