【问题标题】:MongoDB aggregate object map to distinct valuesMongoDB聚合对象映射到不同的值
【发布时间】:2014-05-25 07:54:27
【问题描述】:

我得到了三份文件:

[
  { _id: 1, article: 1, details: { color: "red" } },
  { _id: 2, article: 1, details: { color: "blue", size: 44 },
  { _id: 3, article: 2, details: { color: "blue", size: 44 }
]

我想在查询中转换为:

[
  { article: 1, details: { color: ["red", "blue"], size: [44] } },
  { article: 2, details: { color: ["blue"], size: [44] }
]

目前这是通过 mapReduce 实现的:

db.varieties.mapReduce(map, reduce, { out: { inline: 1 } });

function map() {
  for (var key in this.details) {
    this.details[key] = [this.details[key]];
  }

  emit(this.article, this.details);
}

function reduce(article, details) {
  var result = {};

  details.forEach(function(detail) {
    for (var key in detail) {
      if (!Array.isArray(result[key])) result[key] = [];
      if (~result[key].indexOf(detail[key])) result[key].concat(detail[key]);
    }
  });
  return result;
}

但是我想通过 mongodb 聚合框架来实现这一点,因为我的环境中的 map reduce 实现非常“困难”。

关于我到目前为止的聚合:

var pipeline = [];

pipeline.push({ $project: { article: 1, details: 1 } });
pipeline.push({ $group: { _id: "$article", details: { $push: '$details' } });

db.varieties.aggregate(pipeline);

但是这只会返回:

[
  { article: 1, details: [{ color: "red", size: 44 }, { color: "blue", size: 44 }] },
  { article: 2, details: [{ color: "blue", size: 44 }]
]

我在某处读到这是$unwind 的用例,不幸的是这不适用于对象。

那么让我们来回答我的问题:

  1. 是否可以以某种方式将details 对象转换为带有{ key: "color", value: "red" } 的数组,如果可以,如何实现?
  2. 如果上述方法不可行,并且我将重组我的文档以以上述格式存储(详细信息为数组),我需要如何完成聚合以从我的原始 mapReduce 获得相同的结果?

我不能硬编码细节的关键。聚合必须处理未知键的详细信息。

【问题讨论】:

    标签: mongodb mapreduce aggregation-framework


    【解决方案1】:

    你最好使用聚合框架:

    db.colors.aggregate([
        { "$group": {
            "_id": "$article",
            "color": {"$addToSet": "$details.color" },
            "size": { "$addToSet": "$details.size" }
        }},
        { "$project": {
            "details": {
                "color": "$color",
                "size": "$size"
            }
        }}
    ])
    

    生产:

    { "_id" : 2, "details" : { "color" : [ "blue" ], "size" : [ 44 ] } }
    { "_id" : 1, "details" : { "color" : [ "blue", "red" ], "size" : [ 44 ] } }
    

    因此,当您 $group 时,您不能将这些键放在“详细信息”下,但您始终可以将 $project 设置为您想要的结果形式。

    聚合框架是原生代码实现,运行速度比 JavaScript 解释器驱动的 mapReduce 快得多。

    但如果你真的需要灵活性,这个概念是相似的,只是需要更长的时间,但可以在细节下使用不同的键:

    db.colors.mapReduce(
      function () {
        emit( this.article, this.details );
      },
      function (key,values) {
    
          var reduced = {
          };
    
          values.forEach(function(value) {
            for ( var k in value ) {
              if ( !reduced.hasOwnProperty(k) )
                reduced[k] = [];
              if ( reduced[k].indexOf( value[k] ) == -1 )
                reduced[k].push( value[k] );
            }
    
          });
    
          return reduced;
    
      },
      {
          "finalize": function(key,value) {
    
            for (var k in value) {
              if ( Object.prototype.toString.call( value[k] ) !== '[object Array]') {
                var replace = [];
                replace.push( value[k] );
                value[k] = replace;
              }
    
            }
    
            return value;
          },
          "out": { "inline": 1 }
      }
    )
    

    但这都是一种非常“mapReduce”的方式,所以主要字段的值会有所不同。

    { "_id" : 1, "value" : { "color" : [ "blue", "red" ], "size" : [ 44 ] } }
    { "_id" : 2, "value" : { "color" : [ "blue" ], "size" : [ 44 ] } }
    

    【讨论】:

    • 很遗憾,我无法对所有可能的细节进行硬编码,因为它们可能会随着时间而改变。
    • @bodokaiser 还添加了 mapReduce 方法来做同样的事情,但使用灵活的键。
    • 我的问题中已经有了一个可以工作的 mapReduce :) 您如何看待将细节更改为具有 { key: "size", value: 42 } 项的数组以便使用展开?这行得通吗?
    • @bodokaiser 因为我的输出仅与底部显示的示例输出不同(请参阅编辑)。 mapReduce 始终输出为_idvalue,因此添加一个额外的内部“详细信息”键似乎并没有增加太多。如果它对你来说真的很重要,那么输出到一个集合并使用 $project 和聚合来转换。
    猜你喜欢
    • 2021-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-29
    • 2013-05-18
    相关资源
    最近更新 更多