MongoDB: 嵌入式文档中的唯一键

23

是否可以为嵌入式文档中的键设置唯一键?

我有一个名为Users的集合,其中包含以下示例文档:

 {
       Name: "Bob",
       Items: [
           {
               Name: "Milk"
           },
           {
               Name: "Bread"
           }
       ]
    },
    {
       Name: "Jim"
    },
有没有办法在 Items.Name 属性上创建索引?
当我尝试创建索引时,出现了以下错误:
> db.Users.ensureIndex({"Items.Name": 1}, {unique:true});
E11000 duplicate key error index: GroceryGuruApp.Users.$Items.Name_1  dup key: {
 : null }

有什么建议吗?谢谢!

5个回答

26

独特索引仅存在于集合之间。要在文档之间强制执行唯一性和其他约束条件,您必须在客户端代码中执行它。(可能的话虚拟集合可以实现,您可以投票支持)。

在您的情况下,您要做的是在键Items.Name上创建索引,但此键不存在于任何文档中(它不引用Items数组内嵌文档),因此它为null并违反了跨集合的唯一性约束。


谢谢。目前似乎在客户端强制执行这一点似乎是唯一的选择。 - Abe
2
谢谢!最终我不得不查询对象,在客户端检查重复,然后在Items数组上使用$set运算符。 - Abe
1
@Abe:你是在使用 findAndModify 完成这个操作吗?还是将其拆分为查询和更新两个部分?如果是拆分的话,你是如何处理操作的原子性的? - Anders Östman

11

您可以创建一个独特的复合稀疏索引来实现类似于您所希望的功能。这可能不是最佳选项(客户端仍然可能更好),但根据具体要求,它可以完成您所要求的操作。

要实现此目的,您需要在与 Name: Bob 同级的另一个字段上创建一个独特的标识符,用于每个顶层记录(可以是 FirstName + LastName + Address,我们将其称为 key Identifier)。

然后创建如下所示的索引:

ensureIndex({'Identifier':1, 'Items.name':1},{'unique':1, 'sparse':1})

使用稀疏索引将忽略没有该字段的项目,因此应该可以解决NULL键问题。将您的唯一标识符和Items.name组合成复合唯一索引应确保每个人不能有相同的项目名称两次。

虽然我应该补充说,我只使用Mongo工作了几个月,我的经验可能有所偏差。这不是基于实证证据,而是观察到的行为。

MongoDB索引更多信息


4
Mongo 2.2及以上版本不可行。唯一索引确保文档的唯一性而非子文档的唯一性,因此唯一的“标识符”使得该建议完全无用(因为每个文档的“标识符”都是唯一的,整个“标识符”+“项目名称”也总是唯一的)。更多详情请看这里 - Yaroslav Admin

3

另一种方法是将物品建模为哈希表,其中物品名称作为键。

Items: { "Milk": 1, "Bread": 1 }

我不确定你是想利用索引来提高性能还是仅仅为了约束。正确的处理方式取决于你的使用情况,并确定原子操作是否足以保持数据的一致性。


谢谢。这实际上是一个非常好的解决方案,可以消除重复项。我唯一看到的问题是我正在使用ASP.NET 3.5/C#,如果不使用字典,那将很难建模。我的情况下项目列表会经常更改。 - Abe
如果需要的话,您可以将其制作成值为Item对象的字典,而不仅仅是我在那里放置的1。但是,它是一个字典,并且不能真正映射到嵌套实体(如果这就是您在ASP.NET/C#中所指的内容),因为它的键会有所不同。 - Michael

3
索引将跨越所有用户,因为您要求它是“唯一的”,所以没有用户能够拥有两个同名的项目,并且没有两个用户能够拥有相同命名的项目。这符合您的要求吗?
此外,似乎它反对两个用户对Items.Name具有“null”值,很明显Jim确实有,还有另一个记录吗?
在索引集合上要求唯一性是不寻常的。
MongoDB允许唯一索引,其中它仅索引每个值的第一个,请参见http://www.mongodb.org/display/DOCS/Indexes#Indexes-DuplicateValues,但我认为在这种情况下不要求唯一性才是真正的解决方案。
如果您只想在单个用户的Items中确保唯一性,则可以尝试$addToSet选项。请参见http://www.mongodb.org/display/DOCS/Updating#Updating-%24addToSet

是的,那是一个很好的观点。我的独特定义似乎是对于给定用户文档,我不能有重复的项目名称。基本上,我只想在嵌入文档中的给定属性中不允许重复。 - Abe
你可以使用 $addToSet 来更新 Items 集合。请参阅 http://www.mongodb.org/display/DOCS/Updating#Updating-%24addToSet - Ian Mercer
很遗憾,$addToSet不能在嵌套文档的数组上工作。我尝试过这样做,但是重复的项仍然会被添加,至少在Mongo 1.6.5中是这样的。 - Abe

1
你可以使用findAndModify来创建一个序列/计数器函数。
function getNextSequence(name) {
   var ret = db.counters.findAndModify({
        query: { _id: name },
        update: { $inc: { seq: 1 } },
        new: true,
        upsert: true
    });
    return ret.seq;
}

然后在需要新id时使用它...
db.users.insert({
    _id: getNextSequence("userid"),
    name: "Sarah C."
})

这是来自http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/。请查看。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接