如何在mongodb中创建两个唯一的交换字段?

时间:2015-08-09 00:28:12

标签: mongodb mongodb-query

我有一个这样的集合:

{ 'start_location': '1', 'end_location': '2' },
{ 'start_location': '3', 'end_location': '4' }

我想确保无法插入以下条目:

{ 'start_location': '1', 'end_location': '2' } 

{ 'start_location': '2', 'end_location': '1' }

虽然应该允许这样的条目:

{ 'start_location': '1', 'end_location': '3' }

我尝试过复合索引,但无法成功。

1 个答案:

答案 0 :(得分:0)

索引不能为您强制执行这样的逻辑,但您可以构建逻辑,以便只有在存在配对组合的情况下才能创建。为此,您需要使用"upsert"功能,并仅将修改置于$setOnInsert块内

db.startend.update(
   {
       "$or": [
            { "start_location": "2", "end_location": "1" },
            { "start_location": "1", "end_location": "2" }
       ]
   },
   {

       "$setOnInsert": {
           "start_location": "2",
           "end_location": "1"
       }
   },
   { "upsert": true }
)

将文档与"start_location": "1""end_location": "2"匹配,因此不会创建新文档。但是,如果组合不存在:

db.startend.update(
   {
       "$or": [
            { "start_location": "1", "end_location": "3" },
            { "start_location": "3", "end_location": "1" }
       ]
   },
   {

       "$setOnInsert": {
           "start_location": "1",
           "end_location": "3"
       }
   },
   { "upsert": true }
)

然后创建一个新文档。

因此$or条件允许您以参数的反向顺序和预期的顺序指定组合,因此您可以比较每个可能的匹配。

在" upsert"在不满足条件时创建新文档,或者您可以在$setOnInsert块之外进行任何修改,例如此$inc,以便在每个匹配项上增加值,故意想这样做:

db.startend.update(
   {
       "$or": [
            { "start_location": "2", "end_location": "1" },
            { "start_location": "1", "end_location": "2" }
       ]
   },
   {

       "$setOnInsert": {
           "start_location": "2",
           "end_location": "1"
       },
       "$inc": { "counter": 1 }
   },
   { "upsert": true }
)

如上所述,索引不是强制执行此逻辑的方法,而是指定" forward"和"反向"声明中的条件是一个相对容易的模式,可以遵循并实现"插入/更新"。

请注意,对此进行一些研究可能会导致您使用" start"来考虑使用数组格式。和"结束"像这样的两个数组位置:

{ "location": [ "1", "2" ] },
{ "location": [ "3", "4" ] }

你可能认为这是一个好主意,因为有一个$all运算符可以在这里用来基本匹配"任何顺序"所有"所有"元素存在。然而,有一个"隐藏的捕获"在这里,这实际上会失败并出现错误:

db.startend.update(
   {
       "location": { "$all": [ "1", "3" ] }
   },
   {
        "$setOnInsert": {
            "location": [ "1", "3" ]
        }
   },
   { "upsert": true }
)

错误将是:

  

写结果({           " nMatched" :0,           " nUpserted" :0,           " n修改" :0,           " writeError" :{                   "代码" :54,                   " ERRMSG" :"无法推断要设置的查询字段,路径' location'匹配两次"           }   })

你会合理地期望" upsert"会发生在这里,但产生错误。这里的具体问题是因为被比较的数据是在一个数组中,更新是"尝试"找出哪个"位置"要更新的数组,即使这不是你在这里要求的。

为了解决这个问题,你需要强制查询引擎“不”#34;尝试匹配数组位置,唯一的方法就是使用$where的JavaScript评估来替换$all在数组比较中所做的逻辑:

db.startend.update(
   {
       "$where": function() {
           var input = [ "1", "3" ];

           return this.location.every(function(el) {
               return ( input.indexOf(el) != -1 );
           });
       }
   },
   {
        "$setOnInsert": {
            "location": [ "1", "3" ]
        }
   },
   { "upsert": true }
)

虽然该逻辑将保持正确,但它并不是真正可取的,此处的$where条件不能使用索引进行查找,这意味着表达式必须始终扫描集合中的每个文档才能确定有或没有匹配。

所以需要注意的是,表面看起来很好看的东西会使你的编码过程变得复杂,实际上最终会成为一个巨大的性能问题。