使用MongoDB聚合计算数组元素的平均值

f8rj6qna  于 2022-11-28  发布在  Go
关注(0)|答案(2)|浏览(265)

我有一个文件集,如:

{
    _id: 5,
    responses: [
        {
            staff: false,
            timestamp: 1000
        },
        {
            staff: true,
            timestamp: 1500
        }
    ]
}

我有一个函数(使用$function),用于对responses数组应用一些自定义逻辑:

const diffs=[];
let current;
for (let i = 0; i < responses.length; i++) {
  if (i+1>=responses.length) break;
  if (!current && !responses[i].staff) current = responses[i];
  if (!current) continue;
  const next = responses[i+1];
  if (!next.staff) continue;
  diffs.push(next.timestamp - current.timestamp);
  current = undefined;
}
return diffs;

它基本上返回数字数组,如[500, 1000, 10]等。它还可能返回空数组([])。
我想把所有数组合并成一个数组(假设一个文档返回[5, 10],下一个返回[1, 2],结果是[5, 10, 1, 2]--顺序无关紧要),然后使用$avg计算平均值。
我在阅读MongoDB文档时发现了$concatArrays,因此据我所知,该过程应该是:
1.计算每个文档的差异,最后得到一个类似[[1, 2], [3, 4], [5, 6, 7], [], ...]的数组
1.对步骤1中的值使用$concatArrays
1.对步骤2中的阵列使用$avg
我应该如何进行步骤1?我唯一不确定的部分是如何在第一个分组阶段保存一个变量,并从$function返回结果。我知道我需要这样做:

aggregate([
    {$group: {diffs: {$function: {...}}}}
])

但是,我得到错误MongoServerError: unknown group operator '$function'

3pmvbmvn

3pmvbmvn1#

最后,我得到了我想要的结果。如果读到原始问题的人对数据有一些困惑的话,我很抱歉--问题的基本要点是,对于每个文档,函数计算一个全是数字的数组(比如[1, 2, 3]),我想用所有文档中的所有数字组合成一个大数组,然后用它计算一个平均值。
我首先计算每个文档的数组(diffs),展开数组,使用$push将数组中的每个元素推入一个新的元素中,最后在创建的数组上使用$avg,希望这能帮助遇到类似问题的人。

db.tickets.aggregate([
    {
        $project: {
            responseTimes: {
                $function: {
                    body: function(responses) {
                        const diffs=[];
                        let current;
                        for (let i = 0; i < responses.length; i++) {
                            if (i+1>=responses.length) break;
                            if (!current && !responses[i].staff) current = responses[i];
                            if (!current) continue;
                            const next = responses[i+1];
                            if (!next.staff) continue;
                            diffs.push(next.timestamp - current.timestamp);
                            current = undefined;
                        }
                        return diffs;
                    },
                    args: ["$responses"],
                    lang: "js"
                }
            }
        }
    },

    {$unwind: "$responseTimes"},

    {
        $group: {
            _id: null,

            responseTimes: {
                $push: "$responseTimes"
            }
        }
    },

    {
        $project: {
            avgResponseTime: {
                $avg: "$responseTimes"
            }
        }
    }
])
2wnc66cl

2wnc66cl2#

Documentation表示:
在聚合表达式内执行JavaScript可能会降低性能。仅当提供的管道运算符无法满足应用程序的需要时,才使用$function运算符。
这是一个起点,可以通过聚合管道在本地实现同样的效果。我没有花太多时间进行测试,但这将是一个方向。

db.collection.aggregate([
  {
    $set: {
      responseTimes: {
        $reduce: {
          input: "$responses",
          initialValue: [],
          in: {
            $cond: {
              if: { $not: "$$this.staff" },
              then: { $concatArrays: [ "$$value", [ "$$this.timestamp" ] ] },
              else: {
                $cond: {
                  if: { $gt: [ { $size: "$$value" }, 0 ] },
                  then: {
                    $concatArrays: [
                      { $slice: [ "$$value", { $subtract: [ { $size: "$$value" }, 1 ]  } ] },
                      [ { $subtract: [ "$$this.timestamp", { $last: "$$value" } ] } ]
                    ]
                  },
                  else: null
                }
              }
            }
          }
        }
      }
    }
  },
  {
    $project: {
      avgResponseTime: { $avg: "$responseTimes" }
    }
  }
])

https://mongoplayground.net/p/Ie6IL8atLX-
另一种方法是使用input: { $range: [ 0, {$subtract: [{ $size: "$responses" }, 1]} ] },,然后使用{ $arrayElemAt: [ "$responses", "$$this" ] }访问元素,可能参见this example,这是一个类似的用例。

相关问题