MongoDB:数组元素属性的唯一索引

时间:2011-07-19 07:48:34

标签: mongodb

我的结构类似于:

class Cat {
  int id;
  List<Kitten> kittens;
}

class Kitten {
  int id;
}

我想阻止用户创建一只拥有多个具有相同ID的小猫的猫。我尝试按如下方式创建索引:

db.Cats.ensureIndex({'id': 1, 'kittens.id': 1}, {unique:true})

但是当我尝试插入格式错误的猫时,Mongo会接受它。

我错过了什么吗?甚至可以这样做吗?

6 个答案:

答案 0 :(得分:29)

据我所知,唯一索引仅强制执行不同文档的唯一性,因此会产生重复的键错误:

db.cats.insert( { id: 123, kittens: [ { id: 456 } ] } )
db.cats.insert( { id: 123, kittens: [ { id: 456 } ] } )

但这是允许的:

db.cats.insert( { id: 123, kittens: [ { id: 456 }, { id: 456 } ] } )

我不确定是否有任何方式强制执行Mongo级别所需的约束,也许这是您在插入更新时可以在应用程序逻辑中检查的内容?

答案 1 :(得分:25)

确保数组字段中各个值的唯一性

除了上面的示例之外,MongoDB中还有一个函数可以确保在向数组字段添加新对象/值时,如果值/对象尚不存在,它将只执行更新

因此,如果您的文档如下所示:

{ _id: 123, kittens: [456] }

这是允许的:

db.cats.update({_id:123}, {$push: {kittens:456}})

导致

{ _id: 123, kittens: [456, 456] }

然而,使用 $ addToSet 功能(而不是 $ push )会在添加之前检查该值是否已存在。 所以,从:

开始
{ _id: 123, kittens: [456] }

然后执行:

db.cats.update({_id:123}, {$addToSet: {kittens:456}})

不会有任何影响。

因此,长话短说,唯一约束不会验证数组字段的值项中的唯一性,只是两个文档在索引字段中不能具有相同的值。

答案 2 :(得分:3)

在数组属性中有一个与uniquness相同的插入。以下命令实际上是插入的,同时确保了小猫的唯一性(如果123的对象已经存在,则upsert为您创建它。)

db.cats.update(
  { id: 123 },
  { $addToSet: {kittens: { $each: [ 456, 456] }}, $set: {'otherfields': 'extraval', "field2": "value2"}},
  { upsert: true}
)

对象的结果值为

{
    "id": 123,
    "kittens": [456],
    "otherfields": "extraval",
    "field2": "value2"
}

答案 3 :(得分:1)

您可以使用document validator来解决此问题。

这里是一个示例验证器,其中“ a”是一个数组,并且在“ a”子文档字段中,“ b”值必须唯一。假定集合为空或已经符合规则:

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\bin

original post

了解有关此解决方案的更多信息

答案 4 :(得分:0)

在这种情况下,您可以编写自定义的Mongoose验证方法。您可以加入后期验证。猫鼬具有验证功能,您可以在验证之前(之前)或之后(之后)实现一个挂钩。在这种情况下,您可以使用后验证来查看数组是否有效。然后只需确保该数组没有重复项即可。您可以根据自己的细节进行效率改进。例如,如果只有“ _id”,则可以使用JS include函数。

catSchema.post('validate',function(next) {
    return new Promise((resolve,reject) => {
        for(var i = 0; i < this.kittens.length; i++) {
           let kitten = this.kittens[i];
           for(var p = 0; p < this.kittens.length; p++) {
              if (p == i) {
                  continue;
              }
              if (kitten._id == this.kittens[p]._id) {
                  return reject('Duplicate Kitten Ids not allowed');
              }
           }
        }
        return resolve();
    });
});

我喜欢在验证中使用promise,因为它更容易指定错误。

答案 5 :(得分:0)

在这里看起来很重要的一点是,确保在mongodb对象数组中不应该有一个项目具有相同的ID或需要唯一对待的其他字段。然后,使用$addToSet这样的简单查询就足够了。

请原谅我不是mongo-shell专家,使用Java Mongo Driver版本4.0.3

collection = database.getCollection("cat", Cat.class);
UpdateResult result = collection.updateOne(and(eq("Id", 1), nin("kittens.id", newKittenId)), addToSet("kittens", new Kitten("newKittenId")));

此处使用的查询为匹配查询增加了一个额外条件,如下所示:其中cat.id为1,而newKittenId尚未归先前添加的任何小猫所有。因此,如果找到了猫的ID,并且没有小猫采用新的kitkitId,则查询将继续并通过添加新的小猫来更新猫的小猫。但是,如果newKittenId被一只小猫拿走了,它只会简单地返回updateresult而没有计数,也不会修改字段(什么也没有发生)。

注意:这不能确保对kitten.id的唯一约束,mongo DB不支持文档中对象数组的唯一性,并且addToSet不能真正处理对象数组中的重复项,除了对象是数据库检查here for more explanation中有关addToSet的副本的100%之外。