MongoDB - 从任意深度和 object 结构中获取子文档

MongoDB - get subdocuments from arbitrary depth and object structure

我有一个 collection,其中有 object 个不同的随机 structures/depths。在其中,有一个 object 我想作为我的结果得到,它有一个特定的 key/value 对,我可以用它来找到它们。

我的例子 collection:

{
    "_id": ObjectId("123"),
    "someKey": "blue",
    "children": [
        {
            "foo": "bar",
            "anotherKey": "anotherValue",
            "whateverA": 111
        }
    ]
}
{
    "_id": ObjectId("456"),
    "blahKey": "dog",
    "children": [
        {
            "anotherRandom": "randomValue",
            "children": [
                {
                    "moreRandom": "stuffValue",
                    "children": [
                        {
                            "foo": "bar",
                            "animalKey": "legsValue",
                            "whateverB": 222
                        }
                    ]
                }
            ]
        }
    ]
}

我想搜索包含“foo:bar”的子文档,得到如下所示的结果:

{
    "foo": "bar",
    "anotherKey": "anotherValue",
    "whateverA": 111
}
{
    "foo": "bar",
    "animalKey": "legsValue",
    "whateverB": 222
}

然后我可以对结果进行分页。这在 MongoDB 5 中甚至可能吗?

谢谢。

如果我们使用@rickhg12hs

您可以这样做:

编辑:已修复以处理未找到的情况(感谢@Jimba 的评论):

db.collection.aggregate([
  {
    $addFields: {
      res: {
        $function: {
                 body: "function drill(t, n) {if (n && n.length > 0){for (let elem of n){if(elem['foo'] && elem['foo'] === 'bar'){t.push(elem);}else {drill(t, elem.children)}}}return t}",
          args: [
            [],
            "$children"
          ],
          lang: "js"
        }
      }
    }
  },
  {
    $project: {
      res: {
        $cond: [
          {$gt: [{$size: "$res"}, 0]},
          {$arrayElemAt: ["$res", 0]},
          {res: null}
        ]
      },
      _id: 0
    }
  },
  {
    $match: {res: {$ne: null}}
  },
  {
    $replaceRoot: {newRoot: "$res"}
  }
])

如你所见on this playground example

我们可以使用 $function 递归查找名为 foo 的键和 return 包含它的对象。

根据评论中的一个问题编辑

您可以根据需要使用您的代码对其进行操作: 例如在 js 中:

const key = 'foo';
const val = 'bar';
const body = `function drill(t, n) {if (n && n.length > 0){for (let elem of n){if(elem[${key}] && elem[${key}] === ${val}){t.push(elem);}else {drill(t, elem.children)}}}return t}`;


db.collection.aggregate([
  {
    $addFields: {res: {$function: {body, args: [[], "$children"], lang: "js"}}}
  },
  {
    $project: {
      res: {
        $cond: [
          {$gt: [{$size: "$res"}, 0]},
          {$arrayElemAt: ["$res", 0]},
          {res: null}
        ]
      },
      _id: 0
    }
  },
  {
    $match: {res: {$ne: null}}
  },
  {
    $replaceRoot: {newRoot: "$res"}
  }
])