MongoDB 聚合管道:在不知道字段值的情况下按字段对数组元素进行分组

MongoDB Aggregation pipeline: group array elements by a field without knowing the field value

我有一组这样形状的足球比赛:

{
  _id: ObjectId("SomeUniqueMatchID"),
  players: [
    { team: "Arsenal", name: "Saka", distanceRan: 8590},
    { team: "Arsenal", name: "Aubameyang", distanceRan: 9230}
    // ...remaining players for both teams
  ],
  timestamp: 129380193,
  // other match info
}

我想查询奥巴梅扬和萨卡一起比赛时的平均距离运行,不管他们在哪个队,只要他们在同一队比赛.

我在球员领域了解 $unwind,然后 $match 最后使用 $group 计算平均值,但我不知道在 $match 上写什么当我在 运行 查询之前不认识团队时的情况。

我相信我明白你在找什么,但如果这是错误的,请告诉我。

正如您所说,您应该能够为此使用聚合管道,包括 $match、$avg 和 $group。

您可以查看 live demo of the query here

数据库

如果我们有如下数据库结构...

[
  {
    _id: ObjectId("123456789101819202122232"),
    players: [
      {
        team: "Arsenal",
        name: "Saka",
        distanceRan: 8590
      },
      {
        team: "Arsenal",
        name: "Aubameyang",
        distanceRan: 9230
      }
    ],
    timestamp: 129380193,
    
  },
  {
    _id: ObjectId("123456789101819202999999"),
    players: [
      {
        team: "Arsenal",
        name: "Saka",
        distanceRan: 7777
      },
      {
        team: "NotArsenal",
        name: "Aubameyang",
        distanceRan: 9999
      }
    ],
    timestamp: 129399999,
    
  }
]

查询

我们可以使用以下查询来计算平均值。

db.collection.aggregate([
  {
    $unwind: "$players"
  },
  {
    $match: {
      $or: [
        {
          "players.name": "Saka",
          "players.team": "Arsenal"
        },
        {
          "players.name": "Aubameyang",
          "players.team": "Arsenal"
        }
      ]
    }
  },
  {
    $group: {
      _id: "$_id",
      averageDistanceRan: {
        $avg: "$players.distanceRan"
      },
      "players": {
        $push: "$players"
      }
    }
  },
  {
    "$match": {
      "$expr": {
        "$eq": [
          {
            "$size": "$players"
          },
          2
        ]
      }
    }
  },
  {
    $project: {
      _id: "$_id",
      averageDistanceRan: 1
    }
  }
])

结果

这会给我们...

[
  {
    "_id": ObjectId("123456789101819202122232"),
    "averageDistanceRan": 8910
  }
]

查询 1

  • 放松玩家
  • 匹配只保留 2 个玩家的名字
  • match_id team 分组并将距离推入数组
  • 匹配并仅保留大小为 =2 的数组 (这是关键,只有当这些球员在同一场比赛和同一支球队中出场时,尺寸=2)
    (如果您在示例代码中看到 id=2,id=3 将被忽略,因为这些球员从未在同一场比赛和同一支球队中打过比赛)
  • uwnind 距离(这里我们知道所有这些都是有效的)
  • null分组(所有集合1组)并平均距离 (你不关心什么比赛,什么球队或哪个球员,所以只保留有关 avgDistance 的信息)

Test code here

aggregate(
[{"$unwind": {"path": "$players"}},
  {"$match": 
    {"$expr": 
      {"$or": 
        [{"$eq": ["$players.name", "Saka"]},
          {"$eq": ["$players.name", "Aubameyang"]}]}}},
  {"$group": 
    {"_id": {"_id": "$_id", "team": "$players.team"},
      "distances": {"$push": "$players.distanceRan"}}},
  {"$match": {"$expr": {"$eq": [{"$size": "$distances"}, 2]}}},
  {"$unwind": {"path": "$distances"}},
  {"$group": {"_id": null, "avgDistanceRan": {"$avg": "$distances"}}},
  {"$unset": ["_id"]}])

查询 2

  • local 它使用 reduce,以及 1 名球员在 1 场比赛中,最多 1 次和 1 个团队中最多的事实(即使这不是真的,query1 也有效)
  • 减少以保持这两个距离,仅当团队相同且名称是两者之一时才添加距离数组
  • 再次只保持距离 count=2(以确保同一队的同一场比赛中的两名球员)
  • 展开并按 null 分组,像上面的平均值

Test code here

aggregate(
[{"$set": 
    {"players": 
      {"$reduce": 
        {"input": "$players",
          "initialValue": {"team": null, "distances": []},
          "in": 
          {"$cond": 
            [{"$and": 
                [{"$or": 
                    [{"$eq": ["$$this.name", "Saka"]},
                      {"$eq": ["$$this.name", "Aubameyang"]}]},
                  {"$or": 
                    [{"$eq": ["$$value.team", null]},
                      {"$eq": ["$$value.team", "$$this.team"]}]}]},
              {"team": "$$this.team",
                "distances": 
                {"$concatArrays": 
                  ["$$value.distances", ["$$this.distanceRan"]]}},
              "$$value"]}}}}},
  {"$match": {"$expr": {"$eq": [{"$size": "$players.distances"}, 2]}}},
  {"$unwind": {"path": "$players.distances"}},
  {"$group": 
    {"_id": null, "avgdistanceRan": {"$avg": "$players.distances"}}},
  {"$unset": ["_id"]}])