Mongodb 通过比较将多个数组分解为文档

Mongodb 通过比较将多个数组分解为文档,mongodb,mongodb-query,aggregation-framework,Mongodb,Mongodb Query,Aggregation Framework,我想在mongo中将一个记录分解为多个新记录。怎么做 当前数据: {"user_id": 123, "scores": [65, 71, 79, 80], "materials": ["A", "B", "C", "D"]} 我想从上面的数据中创建以下数据: {"user_id": 123, "score_original": 65, "score_improvement": 6, "material": "A"}, {"user_id": 123, "score_original": 71,

我想在mongo中将一个记录分解为多个新记录。怎么做

当前数据:

{"user_id": 123, "scores": [65, 71, 79, 80], "materials": ["A", "B", "C", "D"]}
我想从上面的数据中创建以下数据:

{"user_id": 123, "score_original": 65, "score_improvement": 6, "material": "A"},
{"user_id": 123, "score_original": 71, "score_improvement": 8, "material": "B"},
{"user_id": 123, "score_original": 79, "score_improvement": 1, "material": "C"}

这个问题确实需要一些澄清,但是如果您的意图是通过对下一个数组元素的比较来扩展每个项,并且在直接比较中数组的长度总是相等的,那么有两种方法

也就是说,根据您的需要,最后有一个简单的方法和更复杂的方法。因此,要逐步了解它们,以便您了解每种方法所涉及的内容:

放弃 在现代MongoDB版本中,您可以使用聚合框架来合并和比较数组元素,然后扩展到如下新项目:

db.getCollection('junk').aggregate([
  { "$project": {
    "user_id": 1,
    "data": {
      "$map": {
        "input": {
          "$slice": [
            { "$zip": {
              "inputs": [
                "$scores",
                { "$map": {
                  "input": {
                    "$reverseArray": {
                      "$reduce": {
                        "input": { "$reverseArray": "$scores" },
                        "initialValue": [],
                        "in": {
                          "$concatArrays": [
                            "$$value",
                            [
                              [
                                 "$$this",
                                 { "$subtract": [
                                   { "$arrayElemAt": [
                                     { "$ifNull": [{ "$arrayElemAt": ["$$value", -1] }, [] ]},
                                     0
                                   ]},
                                   "$$this"
                                 ]}
                              ]
                            ]
                          ]   
                        }
                      } 
                    }
                  },
                  "in": { "$arrayElemAt": ["$$this",-1] }
                }},
                "$materials"
              ]
            }},
            { "$subtract": [{ "$size": "$scores" },1] }
          ]
        },
        "in": {
          "score_original": { "$arrayElemAt": [ "$$this", 0 ] },
          "score_improvement": { "$arrayElemAt": [ "$$this", 1 ] },
          "material": { "$arrayElemAt": [ "$$this", 2 ] }      
        }
      }
    }
  }},
  { "$unwind": "$data" },
  { "$replaceRoot": {
    "newRoot": {
      "$arrayToObject": {
        "$concatArrays": [
          [{ "k": "user_id", "v": "$user_id" }],
          { "$objectToArray": "$data" }
        ]
      }  
    }  
  }}
])
返回所需的结果:

/* 1 */
{
    "user_id" : 123.0,
    "score_original" : 65.0,
    "score_improvement" : 6.0,
    "material" : "A"
}

/* 2 */
{
    "user_id" : 123.0,
    "score_original" : 71.0,
    "score_improvement" : 8.0,
    "material" : "B"
}

/* 3 */
{
    "user_id" : 123.0,
    "score_original" : 79.0,
    "score_improvement" : 1.0,
    "material" : "C"
}
由于您希望与下一项进行比较,因此大部分工作都是通过反向数组内容完成的。通常,在聚合框架操作中进行最后一次比较要比使用计算出的索引值更容易,所以这就是为什么要将其反转

改进值的基本前提是通过反向数组将当前值与输出数组中的最后一个值进行比较,并使用。因为您需要输出改进,还需要以前的值进行比较,这是通过检查来提取值进行比较的

在馈送到下一个操作之前,这些存储在阵列对中以供输出。当然,您需要再次使用新输出来保持原始顺序

由于现在基本上有三个值数组,将这些值组合成一个值数组的一种方法是为每个元素生成一个数组。这不是唯一的方法,但再次强调,它可能比为提取而篡改索引值更容易阅读

当然,您可以使用它来获得每个数组项的最终对象形式。但不是在应用之前,因为最后一个数组元素由于与不存在的下一项相比没有改进而被丢弃。至少这符合你的逻辑

最后的部分只是使用将数组构造转换为单独的文档,然后重塑最终输出。在这里,使用and和运算符来构造新的根文档,而无需显式命名。然而,这也可能只是一个简单的例子:

因此,有不同的方法可以应用于数组的对象构造,也可以应用于数组的对象构造。只是像MongoDB这样的新运营商至少需要MongoDB 3.4.4。所有其他事情都可以通过MongoDB 3.4完成

聚合替代品 您也可以在可用的情况下使用提供的数组索引:

db.getCollection('junk').aggregate([
  { "$project": {
    "_id": 0,
    "user_id": 1,
    "data": {
      "$map": {
        "input": { "$range": [ 0, { "$subtract": [{ "$size": "$scores" }, 1] } ] },
        "as": "r",
        "in": {
          "score_original": { "$arrayElemAt": [ "$scores", "$$r" ] },
          "score_improvement": {
            "$subtract": [
              { "$arrayElemAt": [ "$scores", { "$add": [ "$$r", 1 ] } ] },
              { "$arrayElemAt": [ "$scores", "$$r" ] }
            ]
          },
          "material": { "$arrayElemAt": [ "$materials", "$$r" ] }
        }
      }
    }   
  }},
  { "$unwind": "$data" },
  { "$replaceRoot": {
    "newRoot": {
      "$arrayToObject": {
        "$concatArrays": [
          [{ "k": "user_id", "v": "$user_id" }],
          { "$objectToArray": "$data" }
        ]
      }  
    }  
  }}  
])
它具有相同的输出,并且遵循以下方法中所示的基本逻辑

地图缩小 如果您没有支持所用运算符的MongoDB 3.4,则始终可以应用mapReduce,只需计算并发出每个数组值:

db.getCollection('junk').mapReduce(
  function() {
    for( var i=0; i < this.scores.length-1; i++ ) {
      var id = this._id.valueOf() + '_' + i;
      emit(id, {
        "user_id": this.user_id,
        "score_original": this.scores[i],
        "score_improvement": this.scores[i+1] - this.scores[i],
        "material": this.materials[i]
      });
    }    
  },
  function() {},
  { "out": { "inline": 1 } }
)
您应该注意到,除了在实现中要简单得多之外,实际上根本没有定义任何reducer函数。这也将导致不可避免的结论

迭代光标 这实际上只是一个基本的游标迭代和扩展,所以这就是您真正需要做的。这意味着从定义在in-out映射器函数中的基开始,作为一个简单的shell抽象:

db.getCollection('junk').find().forEach(d => {
  for (var i=0; i < d.scores.length-1; i++) {
    printjson({
      "user_id": d.user_id,
      "score_original": d.scores[i],
      "score_improvement": d.scores[i+1] - d.scores[i],
      "material": d.materials[i] 
    }) 
  }  
})
事实就是这么简单

这里的基本经验是,虽然您可以要求数据库执行复杂的操作,但除非它实际上大大减少了从服务器返回的数据负载,否则通常最好的情况是使用本机客户机代码处理数据

即使问题中呈现的数据是从其他一些聚合操作中获得的,在这一阶段,简单地迭代游标结果以进行最终转换通常会更好


如果转换需要进一步的聚合操作,那么一定要遵循第一个过程。但是,如果所显示的数据实际上已经通过聚合获得,并且需要在进一步的聚合中进行转换,那么您可能应该检查现有的聚合过程,因为您甚至可能不需要具有多个数组的中间状态,这就是大部分复杂性的来源。

提供的答案中是否有您认为无法解决您问题的内容?如果是,请对答案进行评论,以澄清哪些问题需要解决,哪些问题尚未解决。如果它确实回答了你提出的问题,那么请注意你提出的问题。对不起,我忘了接受你伟大的回答。谢谢。谢谢你详细的回答。我还没有完全理解你的答案,不过我终于明白了你的意思 非常感谢你!
"results" : [ 
    {
        "_id" : "59e4144331be3474a2f28a92_0",
        "value" : {
            "user_id" : 123.0,
            "score_original" : 65.0,
            "score_improvement" : 6.0,
            "material" : "A"
        }
    }, 
    {
        "_id" : "59e4144331be3474a2f28a92_1",
        "value" : {
            "user_id" : 123.0,
            "score_original" : 71.0,
            "score_improvement" : 8.0,
            "material" : "B"
        }
    }, 
    {
        "_id" : "59e4144331be3474a2f28a92_2",
        "value" : {
            "user_id" : 123.0,
            "score_original" : 79.0,
            "score_improvement" : 1.0,
            "material" : "C"
        }
    }
],
db.getCollection('junk').find().forEach(d => {
  for (var i=0; i < d.scores.length-1; i++) {
    printjson({
      "user_id": d.user_id,
      "score_original": d.scores[i],
      "score_improvement": d.scores[i+1] - d.scores[i],
      "material": d.materials[i] 
    }) 
  }  
})
{
    "user_id" : 123,
    "score_original" : 65,
    "score_improvement" : 6,
    "material" : "A"
}
{
    "user_id" : 123,
    "score_original" : 71,
    "score_improvement" : 8,
    "material" : "B"
}
{
    "user_id" : 123,
    "score_original" : 79,
    "score_improvement" : 1,
    "material" : "C"
}