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

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

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

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

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

Сейчас я решаю его на уровне приложения, но хотел бы сохранить этот запрос db.

ТНХ

Ответ 1

Начиная с 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, только несколькими документами без поля email.

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

Ответ 2

ТЛ; др

Да, возможно иметь несколько документов с полем, равным 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 значения 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 Shell

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

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

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

Ответ 3

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

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

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

Больше doco по частичным индексам: https://docs.mongodb.com/manual/core/index-partial/

Ответ 4

Фактически, только первый документ, в котором не существует поля "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