在MongoDB中将数组中的字符串值连接成单个字段

22

假设我有一系列具有以下格式的文档:

{
    "_id": "3_0",
    "values": ["1", "2"]
}

我希望获得一个将数组值连接在单个字段中的投影:

{
    "_id": "3_0",
    "values": "1_2"
}

这种做法可行吗?我尝试了 $concat ,但我猜想我不能将 $values 作为 $concat 的数组。

3个回答

28
在现代MongoDB版本中,您可以这样做。您仍然不能“直接”将数组应用于$concat,但是您可以使用$reduce来处理数组元素并生成以下内容:
db.collection.aggregate([
  { "$addFields": {
    "values": { 
      "$reduce": {
        "input": "$values",
        "initialValue": "",
        "in": {
          "$cond": {
            "if": { "$eq": [ { "$indexOfArray": [ "$values", "$$this" ] }, 0 ] },
            "then": { "$concat": [ "$$value", "$$this" ] },
            "else": { "$concat": [ "$$value", "_", "$$this" ] }
          }    
        }
      }        
    }
  }}
])

结合 $indexOfArray 来避免在数组中的“第一个”索引处与 "_" 下划线不进行“连接”。 另外,我的额外“愿望”已经得到了 $sum 的回答。
db.collection.aggregate([
  { "$addFields": {
    "total": { "$sum": "$items.value" }
  }}
])

这种情况通常在涉及数组的聚合运算符中会被提到。这里的区别在于,它指的是以编码方式提供的“参数”“数组”,而不是当前文档中存在的“数组元素”。
你唯一能够在文档中进行数组项内部拼接的方法就是使用JavaScript选项,就像mapReduce中的这个例子一样。
db.collection.mapReduce(
    function() {
        emit( this._id, { "values": this.values.join("_") } );
    },
    function() {},
    { "out": { "inline": 1 } }
)

当然,如果你实际上没有聚合任何内容,那么可能最好的方法是在客户端代码中进行“join”操作来后处理查询结果。但是,如果它需要在多个文档中使用,则mapReduce将是唯一可以使用它的地方。
我可以补充一下,“例如”,我希望像这样的东西能够工作:
{
    "items": [
        { "product": "A", "value": 1 },
        { "product": "B", "value": 2 },
        { "product": "C", "value": 3 }
    ]
}

总的来说:

db.collection.aggregate([
    { "$project": {
        "total": { "$add": [
            { "$map": {
                "input": "$items",
                "as": "i",
                "in": "$$i.value"
            }}
        ]}
    }}
])

但实际情况并非如此,因为$add期望参数而不是来自文档的数组。叹气!:(。这种设计的一部分原因可以认为是“仅仅因为”从转换结果中传递了一个单值的“数组”或“列表”,并不能“保证”这些值实际上是运算符预期的有效的单一数字类型值。至少在当前实现的“类型检查”方法中不是这样的。

这意味着目前我们仍然需要这样做:

db.collection.aggregate([
   { "$unwind": "$items" },
   { "$group": {
       "_id": "$_id",
        "total": { "$sum": "$items.value" }
   }}
])

很遗憾,目前没有方法可以应用这样的分组运算符来连接字符串。

因此,您可以期望在这方面进行某种变化,或者希望某些变化允许在$map操作的范围内改变外部作用域变量。更好的是,一个新的$join操作也会受到欢迎。但是,目前还不存在这些操作,而且可能在未来一段时间内都不会存在。


感谢您的答案和示例。就像我想的那样,这个问题没有简单的解决方案...我猜最终我会做另一个mapReduce,但我试图避免这样做,因为我已经做了一个前面的mapReduce来生成这些数据。 您知道在nodeJS中是否有一种用MongoDB驱动程序链接mapReduce操作的方式吗? - Eylen
@Eylen,不是真的,意思是不输出到集合并再次运行。否则就不可能了。但是也许如果您考虑一下在初始mapReduce操作中正在执行的操作,那么后续步骤可能就不必要了。当然,总有“finalize”方法。这似乎是逻辑上的结论,除非您需要在连接后进一步进行“分组”。 - Neil Lunn
是的,我需要进行另一种完全不同于第一个的分组。我会再考虑一下是否可以采取其他方法。感谢帮助。 - Eylen

6
你可以使用 reduce 操作符和 substr 操作符一起使用,来进行相关的it技术操作。
db.collection.aggregate([
{
    $project: {
        values: {
            $reduce: {
              input: '$values',
              initialValue: '',
              in: {
                $concat: ['$$value', '_', '$$this']
              }
            }
        }   
    }       
},
{
    $project: {
        values: { $substr: ['$values', 1 , -1]}
    }       
}])

3

从Mongo 4.4开始,$function聚合操作符允许应用自定义的JavaScript函数来实现MongoDB查询语言不支持的行为。

例如,为了连接一个字符串数组:

// { "_id" : "3_0", "values" : [ "1", "2" ] }
db.collection.aggregate(
  { $set:
    { "values":
      { $function: {
          body: function(values) { return values.join('_'); },
          args: ["$values"],
          lang: "js"
      }}
    }
  }
)
// { "_id" : "3_0", "values" : "1_2" }

$function需要三个参数:

  • body,它是要应用的函数,它的参数是要连接的数组。
  • args,其中包含记录中body函数作为参数所需的字段。在我们的情况下是"$values"
  • lang,它是body函数编写的语言。目前只有js可用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接