我如何聚合过滤嵌套文档并从其他字段获取价值

How can i aggregate filter nested documents and get value from other field

我有一个 collection 这样的:

{
  '_id' : ObjectId('6251f8556e75125f9260f333'),
  'name': 'jojo',
  'profile': 'jojo profile',
  'date': ISODate("2022-04-09T21:18:40.473Z"),
  'look': [
    { 'art': 'group-id', 'data': 'alma', 'dt': '1'},
    { 'art': 'called', 'data': 'central', 'dt': '1'},
    { 'art': 'access-time', 'data': 108000, 'dt': '1'}
  ]
  'answer': [
    { 'art': 'rate-id', 'data': 'limit1', 'dt': '1'},
    { 'art': 'protocol', 'data': 'tcp', 'dt': '1'}
  ]
},
{
  '_id' : ObjectId('6251f8306e75125f9260f332'),
  'name': 'dodo',
  'profile': 'dodo profile',
  'date': ISODate("2022-04-09T15:20:58.562Z"),
  'look': [
    { 'art': 'group-id', 'data': 'alma', 'dt': '1'},
    { 'art': 'called', 'data': 'central', 'dt': '1'},
  ]
  'answer': [
    { 'art': 'rate-id', 'data': 'limit1', 'dt': '1'},
  ]
},
{
  '_id' : ObjectId('6251a5113700ba4a0a59c48f'),
  'name': 'kaka',
  'profile': 'kaka profile',
  'date': ISODate("2022-04-09T15:22:25.816Z"),
  'look': [
    { 'art': 'access-time', 'data': 50400, 'dt': '1'}
  ]
  'answer': [
    { 'art': 'protocol', 'data': 'tcp', 'dt': '1'}
  ]
}

我期待这样的输出:

{
  '_id' : ObjectId('6251f8556e75125f9260f333'),
  'name': 'jojo',
  'profile': 'jojo profile',
  'date': ISODate("2022-04-09T21:18:40.473Z"),
  'goup': 'alma',        // filter by 'group-id' and put value of data field
  'called': 'central',   // filter by 'called' and put value of data field
  'accessTime': 108000,  // filter by 'access-time' and put value of data field
  'rate': 'limi1',       // filter by 'rate-id' and put value of data field
  'protocol': 'tcp',     // filter by 'protocol' and put value of data field
},
{
  '_id' : ObjectId('6251f8306e75125f9260f332'),
  'name': 'dodo',
  'profile': 'dodo profile',
  'date': ISODate("2022-04-09T15:20:58.562Z"),
  'goup': 'alma',
  'called': 'central',
  'accessTime': '',     // set blank data if not exist
  'rate': 'limi1',
  'protocol': '',       // set blank data if not exist
},
{
  '_id' : ObjectId('6251a5113700ba4a0a59c48f'),
  'name': 'kaka',
  'profile': 'kaka profile',
  'date': ISODate("2022-04-09T15:22:25.816Z"),
  'goup': '',          // set blank data if not exist
  'called': '',        // set blank data if not exist
  'accessTime': 50400,
  'rate': '',          // set blank data if not exist
  'protocol': 'tcp',
}

我在这里搜索过,但找不到与我面临的问题相匹配的答案,可能是因为关键字错误。 由于我是 mongodb 的新手,我对如何解决我想要的查询感到困惑。我怎样才能做到这一点?请帮助我...

为此,您应该使用 mongo 数据库的 aggregation framework,因为需要复杂的操作才能获得所需形状的数据。

https://www.mongodb.com/docs/manual/aggregation/

每个聚合都是一系列阶段,每个阶段都做一些特定的事情。

我使用了下一个阶段:

  1. addFields:允许您将新字段添加到每个文档的响应中,因此如果文档中没有组,它将添加或替换它。
  2. 项目:允许您删除文档的某些字段。在投影阶段,如果将属性设置为 0,将从响应中删除该属性。

我还使用了一些运算符:

  1. 过滤器:这允许您过滤数组元素的数据
  2. arrayElemenAt: 接收一个数组和return指定的位置

管道:

[
   {
      "$addFields":{
         "group":{
            "$arrayElemAt":[
               {
                  "$filter":{
                     "input":"$look",
                     "as":"item",
                     "cond":{
                        "$eq":[
                           "$$item.art",
                           "group-id"
                        ]
                     }
                  }
               },
               0
            ]
         },
         "called":{
            "$arrayElemAt":[
               {
                  "$filter":{
                     "input":"$look",
                     "as":"item",
                     "cond":{
                        "$eq":[
                           "$$item.art",
                           "called"
                        ]
                     }
                  }
               },
               0
            ]
         },
         "accessTime":{
            "$arrayElemAt":[
               {
                  "$filter":{
                     "input":"$look",
                     "as":"item",
                     "cond":{
                        "$eq":[
                           "$$item.art",
                           "access-time"
                        ]
                     }
                  }
               },
               0
            ]
         },
         "rate":{
            "$arrayElemAt":[
               {
                  "$filter":{
                     "input":"$answer",
                     "as":"item",
                     "cond":{
                        "$eq":[
                           "$$item.art",
                           "rate-id"
                        ]
                     }
                  }
               },
               0
            ]
         },
         "protocol":{
            "$arrayElemAt":[
               {
                  "$filter":{
                     "input":"$answer",
                     "as":"item",
                     "cond":{
                        "$eq":[
                           "$$item.art",
                           "protocol"
                        ]
                     }
                  }
               },
               0
            ]
         }
      }
   },
   {
      "$addFields":{
         "group":"$group.data",
         "called":"$called.data",
         "accessTime":"$accessTime.data",
         "rate":"$rate.data",
         "protocol":"$protocol.data"
      }
   },
   {
      "$project":{
         "look":0,
         "answer":0
      }
   }
]

这对于当前的结构来说非常麻烦,因为对于每个字段你都必须将对象转换为数组,过滤它然后将其转换回来,这是它的样子:

db.collection.aggregate([
  {
    $replaceRoot: {
      newRoot: {
        "$mergeObjects": [
          {
            _id: "$_id",
            name: "$name",
            profile: "$profile",
            date: "$date",
            
          },
          {
            "$arrayToObject": {
              $map: {
                input: {
                  $filter: {
                    input: {
                      $objectToArray: {
                        $ifNull: [
                          {
                            "$arrayElemAt": [
                              {
                                $filter: {
                                  input: {
                                    $ifNull: [
                                      "$look",
                                      []
                                    ]
                                  },
                                  cond: {
                                    $eq: [
                                      "$$this.art",
                                      "group-id"
                                    ]
                                  }
                                }
                              },
                              0
                            ]
                          },
                          {
                            art: ""
                          }
                        ]
                      }
                    },
                    cond: {
                      $eq: [
                        "$$this.k",
                        "data"
                      ]
                    }
                  }
                },
                in: {
                  k: "goup",
                  v: "$$this.v"
                }
              }
            }
          },
          {
            "$arrayToObject": {
              $map: {
                input: {
                  $filter: {
                    input: {
                      $objectToArray: {
                        $ifNull: [
                          {
                            "$arrayElemAt": [
                              {
                                $filter: {
                                  input: {
                                    $ifNull: [
                                      "$look",
                                      []
                                    ]
                                  },
                                  cond: {
                                    $eq: [
                                      "$$this.art",
                                      "called"
                                    ]
                                  }
                                }
                              },
                              0
                            ]
                          },
                          {
                            art: ""
                          }
                        ]
                      }
                    },
                    cond: {
                      $eq: [
                        "$$this.k",
                        "data"
                      ]
                    }
                  }
                },
                in: {
                  k: "called",
                  v: "$$this.v"
                }
              }
            }
          },
          {
            "$arrayToObject": {
              $map: {
                input: {
                  $filter: {
                    input: {
                      $objectToArray: {
                        $ifNull: [
                          {
                            "$arrayElemAt": [
                              {
                                $filter: {
                                  input: {
                                    $ifNull: [
                                      "$look",
                                      []
                                    ]
                                  },
                                  cond: {
                                    $eq: [
                                      "$$this.art",
                                      "access-time"
                                    ]
                                  }
                                }
                              },
                              0
                            ]
                          },
                          {
                            art: ""
                          }
                        ]
                      }
                    },
                    cond: {
                      $eq: [
                        "$$this.k",
                        "data"
                      ]
                    }
                  }
                },
                in: {
                  k: "access-time",
                  v: "$$this.v"
                }
              }
            }
          },
          {
            "$arrayToObject": {
              $map: {
                input: {
                  $filter: {
                    input: {
                      $objectToArray: {
                        $ifNull: [
                          {
                            "$arrayElemAt": [
                              {
                                $filter: {
                                  input: {
                                    $ifNull: [
                                      "$answer",
                                      []
                                    ]
                                  },
                                  cond: {
                                    $eq: [
                                      "$$this.art",
                                      "rate-id"
                                    ]
                                  }
                                }
                              },
                              0
                            ]
                          },
                          {
                            art: ""
                          }
                        ]
                      }
                    },
                    cond: {
                      $eq: [
                        "$$this.k",
                        "data"
                      ]
                    }
                  }
                },
                in: {
                  k: "rate",
                  v: "$$this.v"
                }
              }
            }
          },
          {
            "$arrayToObject": {
              $map: {
                input: {
                  $filter: {
                    input: {
                      $objectToArray: {
                        $ifNull: [
                          {
                            "$arrayElemAt": [
                              {
                                $filter: {
                                  input: {
                                    $ifNull: [
                                      "$answer",
                                      []
                                    ]
                                  },
                                  cond: {
                                    $eq: [
                                      "$$this.art",
                                      "protocol"
                                    ]
                                  }
                                }
                              },
                              0
                            ]
                          },
                          {
                            art: ""
                          }
                        ]
                      }
                    },
                    cond: {
                      $eq: [
                        "$$this.k",
                        "data"
                      ]
                    }
                  }
                },
                in: {
                  k: "protocol",
                  v: "$$this.v"
                }
              }
            }
          }
        ]
      }
    }
  }
])

Mongo Playground

如果您使用的是 Mongo 版本 5+,那么您可以使用 $getField 来稍微简化语法,下面是一个字段在此语法中的样子:

goup: {
    $getField: {
        field: 'data',
        input: {
            '$arrayElemAt': [
                {
                    $filter: {
                        input: {
                            $ifNull: [
                                '$look',
                                [],
                            ],
                        },
                        cond: {
                            $eq: [
                                '$$this.art',
                                'group-id',
                            ],
                        },
                    },
                },
                0,
            ],
        },
    },
},

您需要一个聚合操作,该操作具有一个包含以下关键运算符和阶段的管道:

  • $map:将 lookanswer 数组转换为仅映射 kv 字段的文档的运算符,对于获得具有以下运算符的哈希映射
  • $arrayToObject:这使得上述成为可能,即将数组转换为单个文档
  • $mergeObjects:将顶级字段即 _iddatenameprofile 与上面转换后的文档组合在一起
  • $replaceWith: 管道阶段用上面的指定文档替换根文档

总体而言,您的管道应遵循:

const first = { 
   $first: {
      $split: ['$$this.art', '-']
   }
};
const keyExpression = {
   $cond: [
     { $eq: [first, 'access'] },
     'accessTime',
     first
   ]
};

const pipeline = [
    { $replaceWith: {
        $mergeObjects: [
            {  
                _id: '$_id', 
                date: '$date', 
                name: '$name', 
                profile: '$profile',
                protocol: '',
                group: '',
                called: '',
                rate: '',
                accessTime: '',
            },
            { $arrayToObject: {
                $map: {
                  input: '$look',
                  in: { k: keyExpression, v: '$$this.data' } 
                }
            } },
            { $arrayToObject: {
                $map: {
                  input: '$answer',
                  in: { k: keyExpression, v: '$$this.data' } 
                }
            } }
        ]
    } } 
]

Mongo Playground