mongoDB / mongoose:唯一,如果不为null


103

我想知道是否有办法强制唯一的收集条目,但前提是条目不为null。e示例架构:

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

在这种情况下,不需要'email',但是如果保存'email',我想确保该条目是唯一的(在数据库级别)。

空条目的值似乎为'null',因此每个条目都不会因'unique'选项而导致电子邮件崩溃(如果有其他用户没有电子邮件)。

现在,我正在应用程序级别解决它,但希望保存该数据库查询。

谢谢

Answers:


168

从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/


18
太棒了!绝对是1.8之后的像我这样的新手的最佳答案!注意:如果只向架构中添加sparse:true,猫鼬不会将您的唯一索引更新为稀疏。您必须删除并重新添加索引。邓诺(Dunno),这是预期的还是错误。
亚当A

8
“注意:如果该索引已经存在于数据库中,则不会被替换。” - mongoosejs.com/docs/2.7.x/docs/schematypes.html
damphat

我认为这不能正确回答问题,因为一些没有特定字段的文档与在该字段上具有空值(无法唯一索引)的一些文档不同。
kako-nawao 2015年

1
@ kako-nawao是的,它仅适用于没有email字段的文档,而不适用于其实际值为的位置null。查看最新答案。
JohnnyHK 2015年

2
不适用于缺少的字段。也许在更高版本的mongodb中已更改了行为。答案应该更新。
joniba

44

tl; dr

是的,null在强制执行唯一的“实际”值的同时,可能会有多个文档的字段设置为或未定义。

要求

  • MongoDB v3.2 +。
  • 事先了解您的具体价值类型(例如,始终为a stringobject不为时null)。

如果您对这些细节不感兴趣,请随时跳到本implementation部分。

更长的版本

为了补充@Nolan的答案,从MongoDB v3.2开始,您可以使用带有过滤表达式的部分唯一索引。

部分过滤器表达式具有局限性。它只能包括以下内容:

  • 等式表达式(即字段:值或使用$eq运算符),
  • $exists: true 表达,
  • $gt$gte$lt$lte表情,
  • $type 表达式,
  • $and 仅限顶级运营商

这意味着{"yourField"{$ne: null}}无法使用平凡的表达式。

但是,假设您的字段始终使用相同的类型,则可以使用$typeexpression

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

MongoDB v3.6添加了对指定多种可能类型的支持,这些类型可以作为数组传递:

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

这意味着它允许在不允许的情况下使用多种类型中的任何一种null

因此,如果我们要允许email以下示例中的字段接受一个值stringbinary 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

实际上,只有第一个不存在“电子邮件”字段的文档才能成功保存。出现错误时,后续保存(其中不包含“电子邮件”)的操作将失败(请参见下面的代码段)。因此,请参阅http://www.mongodb.org/display/DOCS/Indexes#Indexes-UniqueIndexes上有关唯一索引和缺失键的MongoDB官方文档。

  // 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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.