【问题标题】:MongoDB: Unique index on array element's propertyMongoDB:数组元素属性的唯一索引
【发布时间】:2011-10-08 07:13:58
【问题描述】:

我有一个类似这样的结构:

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

class Kitten {
  int id;
}

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

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

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

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

【问题讨论】:

  • 这不是单只猫不接受同一只小猫两次。你不能有 2 只猫和同一只小猫。 kittens.id 在所有猫中必须是唯一的,这就是为什么小猫不能同时拥有妈妈和爸爸的原因。
  • 这是荒谬的,即使反过来也是合乎逻辑的。因为mongodb 不是关系型的。我想让unique 检查我的嵌入式文档,这将是sql 领域中的child 表。相反,我可能希望在子记录中我不想要相同的子 ID,但可以跨文档有效。

标签: mongodb


【解决方案1】:

据我所知,唯一索引只强制不同文档之间的唯一性,所以这会引发重复键错误:

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 级别强制执行您需要的约束,也许您可​​以在插入更新时检查应用程序逻辑?

【讨论】:

  • 不幸的是,您似乎是对的 - 我必须在代码中强制执行这一点。哦,好吧。
  • 好伤心! :(。应该有一个选项来确保文档数组的唯一性。
  • 可以做的就是在保存到数组的时候写验证钩子,保证它是唯一的
  • @Chris 我正在尝试使用tags:[ { type:Schema.Types.ObjectId, ref:"Tag", } ] 实现相同的目标,但不断出现类似MongoError: E11000 duplicate key error collection: kh-content-server.posts index: tags_1 dup key: { tags: null } 的错误
【解决方案2】:

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

除了上面的例子,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}})

不会有任何影响。

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

【讨论】:

    【解决方案3】:

    在数组属性中有一个等价的具有唯一性的插入。下面的命令本质上是在确保小猫的唯一性的同时进行插入(如果具有 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"
    }
    

    【讨论】:

    • 重要的属性是:$addToSetupsert$set 属性与 OP 无关。
    【解决方案4】:

    在这种情况下,您可以编写自定义 Mongoose 验证方法。您可以挂钩后验证。 Mongoose 具有验证功能,您可以在验证之前(前)或之后(后)实现挂钩。在这种情况下,您可以使用后验证来查看数组是否有效。然后只需确保数组没有重复。您可以根据您的详细信息进行效率改进。例如,如果您只有“_id”,则可以只使用 JS 包含函数。

    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,因为它更容易指定错误。

    【讨论】:

    • 我们有任何原始的 mongodb 方法,而不是 mongoose 验证器
    【解决方案5】:

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

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

    > db.runCommand({collMod:"coll", validator: {$expr:{$eq:[{$size:"$a.b"},{$size:{$setUnion:"$a.b"}}]}}})
    /* test it */
    > db.coll.insert({a:[{b:1}]}) /* success */
    > db.coll.update({},{ '$push' : { 'a':{b:1}}})
    WriteResult({
    	"nMatched" : 0,
    	"nUpserted" : 0,
    	"nModified" : 0,
    	"writeError" : {
    		"code" : 121,
    		"errmsg" : "Document failed validation"
    	}
    })

    original post 中查看有关此解决方案的更多信息

    【讨论】:

      【解决方案6】:

      这里似乎很重要的是确保在 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 并且没有小猫获取新的 kittenId,则查询继续并通过添加新的小猫来更新猫的小猫。但是如果 newKittenId 被其中一只小猫拿走了,它只会返回 updateresult ,没有计数,也没有修改字段(什么都没有发生)。

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

      【讨论】:

      • 这是目前的正确答案,$addToSet 是一个很好的解决方案,无需手动检索文档和检查。
      猜你喜欢
      • 2019-06-26
      • 1970-01-01
      • 1970-01-01
      • 2016-09-11
      • 1970-01-01
      • 2014-06-09
      • 1970-01-01
      • 2012-09-15
      相关资源
      最近更新 更多