MongoDB 根据过滤规则投影嵌套数组中对象的特定值

MongoDB Projecting specific values from objects in nested arrays according to filtering rules

我对 MongoDB 很陌生,第一次在大型应用程序上使用它。我们有一个复杂的嵌套结构,它代表一个对象,该对象具有与其关联的多个文档,以及与每个文档相关联的多个人。为了构建 GUI,我需要从文档层次结构中提取和连接一些信息并将其“提升”到顶层,从而创建一个简单的平面结构。如果每个嵌套的“子查询”有多个结果,我只对第一个感兴趣。

我曾尝试使用聚合构建器来实现这一点,但每次我都以一百多行无休止的 unwind、addfield、project 结束,它变得太长太复杂(而且可能也不是很快)可行。必须有一个更简单的解决方案。让我提供一个示例结构(为了简洁起见,我省略了大部分字段,只留下了基本的字段):

{
    "_id": ObjectId(),
    "number": "ABC-123456",
    "status": "new",
    "items": [
        {
            "_id": ObjectId(),
            "name": "invoice",
            "people": [
                {
                    "first_name": "John",
                    "last_name": "Doe",
                    "active": false
                },
                {
                    "first_name": "Jane",
                    "last_name": "Smith",
                    "active": true
                },
                {
                    "first_name": "Fred",
                    "last_name": "Bloggs",
                    "active": true
                }
            ]
        },
        {
            "_id": ObjectId(),
            "name": "unimportant_document",
            "people": [
                {
                    "first_name": "John",
                    "last_name": "Doe",
                    "active": true
                }
            ]
        },
        {
            "_id": ObjectId(),
            "name": "order",
            "people": [
                {
                    "first_name": "Fred",
                    "last_name": "Bloggs",
                    "active": true
                }
            ]
        }
    ]
}

现在,我想得到类似这样的结果:

{
    "_id": "XXX",
    "number": "ABC-123456",
    "status": "new",
    "invoice_person_full_name": "Jane Smith",
    "order_person_full_name": "Fred Bloggs"
}

基本上,我需要将“first_name”和“last_name”从具有“active”的第一人称连接起来:在特定名称的文档中的一组人中为真(“invoice “对于“invoice_person_full_name”和“订单”对于“order_person_full_name”)。

我不关心重复项,所以如果有多个名为“invoice”的文档,例如,我只想检索第一张发票和这张发票的第一位活动人员。

正如我所说,我已经尝试使用聚合生成器来执行此操作,使用带有过滤器的项目将项目数组过滤为仅“发票”名称,然后展开,再次投影过滤人员,再次投影以获得第一个,展开,添加字段,编写 JS 函数来连接名称(实际上有学位,中间名等需要用空格分隔并且可能为 null,因此 $concat 是不够的)并最终返回。这需要 40 多行代码并且仅适用于单个项目名称,因此我需要多次组合此代码以获得我需要的所有名称的结果。这可能意味着我没有以正确的方式解决问题。

我希望我把问题说清楚了,任何帮助将不胜感激!

  1. $set

    1.1。 invoice_persons - 通过从 items 数组中获取第一个文档来创建字段,其中 name 是“invoice”。

    1.2。 order_persons - 通过从 items 数组中获取其 name 为“order”的第一个文档来创建字段。

  2. $set

    2.1。 invoice_person_full_name - 首先过滤来自 invoice_persons.people 的文档,其 activetrue。接下来用 $map 执行全名的字符串连接。最后,获取第一个文档。

    2.2。 order_person_full_name - 首先过滤来自 order_persons.people 的文档,其 activetrue。接下来用 $map 执行全名的字符串连接。最后,获取第一个文档。

  3. $unset - 删除字段。

db.collection.aggregate([
  {
    $set: {
      "invoice_persons": {
        $first: {
          $filter: {
            input: "$items",
            cond: {
              $eq: [
                "$$this.name",
                "invoice"
              ]
            }
          }
        }
      },
      "order_persons": {
        $first: {
          $filter: {
            input: "$items",
            cond: {
              $eq: [
                "$$this.name",
                "order"
              ]
            }
          }
        }
      }
    }
  },
  {
    $set: {
      "invoice_person_full_name": {
        $first: {
          $map: {
            input: {
              $filter: {
                input: "$invoice_persons.people",
                cond: {
                  $eq: [
                    "$$this.active",
                    true
                  ]
                }
              }
            },
            in: {
              "$concat": [
                "$$this.first_name",
                " ",
                "$$this.last_name"
              ]
            }
          }
        }
      },
      "order_person_full_name": {
        $first: {
          $map: {
            input: {
              $filter: {
                input: "$order_persons.people",
                cond: {
                  $eq: [
                    "$$this.active",
                    true
                  ]
                }
              }
            },
            in: {
              "$concat": [
                "$$this.first_name",
                " ",
                "$$this.last_name"
              ]
            }
          }
        }
      }
    }
  },
  {
    "$unset": [
      "invoice_persons",
      "order_persons",
      "items"
    ]
  }
])

Sample Mongo Playground