mongoDB / мангуст: унікальний, якщо не нульовий


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", тому кожен запис без електронної пошти не збивається з "унікальним" параметром (якщо є інший користувач без електронної пошти).

Зараз я вирішую це на рівні програми, але хотів би зберегти цей db-запит.

Дякую

Відповіді:


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. Ви повинні скинути та повторно додати індекс. Не знаю, якщо це очікується або помилка.
Адам А

8
"Примітка: якщо індекс вже існує на db, він не буде замінений." - mongoosejs.com/docs/2.7.x/docs/schematypes.html
damphat

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

1
@ kako-nawao Це правда, вона працює лише для документів без emailполя, а не там, де насправді там є значення null. Дивіться оновлену відповідь.
JohnnyHK

2
Не працює з відсутніми полями. Можливо, поведінка була змінена в пізніших версіях mongodb. Відповідь слід оновити.
joniba

44

тл; д-р

Так, можливо мати декілька документів із полем, встановленим nullабо не визначеним, при цьому застосовувати унікальні "фактичні" значення.

вимоги :

  • MongoDB v3.2 +.
  • Заздалегідь знаючи свої конкретні типи значень (наприклад, завжди a stringабо objectколи ні null).

Якщо деталі не цікавляться, сміливо переходьте до implementationрозділу.

довша версія

Щоб доповнити відповідь @ Нолана, починаючи з 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

Ця відповідь зробила це і для мене.
Еммануель NK

1
Більшість прийнятих відповідей на це питання передбачає переконання, що ви не встановлюєте явно нульові значення індексованим ключам. Щоб їх замість не визначили. Я робив це і все ще отримував помилку (під час використання uniqueта sparse). Я оновив свою схему цією відповіддю, скинув існуючий індекс, і це спрацювало як шарм.
Філ

Проголосував за це, оскільки він дає знання та можливі відповіді, що базуються на найбільш поширених сценаріях, і, в першу чергу, на цю відповідь відповідатимуть. Дякуємо за детальну відповідь! : +1:
іншийкодер

6

Просто швидке оновлення для тих, хто досліджує цю тему.

Вибрана відповідь спрацює, але ви, можливо, захочете скористатися частковими індексами.

Змінено у версії 3.2: Починаючи з MongoDB 3.2, MongoDB надає можливість створювати часткові індекси. Часткові індекси пропонують набір функціональних можливостей розріджених індексів. Якщо ви використовуєте MongoDB 3.2 або новішу версію, часткові індекси слід віддавати перевагу над розрідженими індексами.

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


2

Насправді, лише перший документ, де "електронної пошти" як поля не існує, буде успішно збережений. Подальші збереження, коли "електронної пошти" немає, не вдається отримати помилку (див. Фрагмент коду нижче). З цієї причини подивіться офіційну документацію 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.