繁体   English   中英

MongoDB Aggregation - 通过另一个对象数组过滤器将文档与对象数组匹配

[英]MongoDB Aggregation - match documents with array of objects, by another array of objects filter

我有由对象数组组成的文档,并且该数组中的每个对象都由另一个对象数组组成。
为简单起见,省略了文档中不相关的字段。

它看起来像这样(2 个文档):

{
  title: 'abc',
  parts: [
    {
      part: "verse",
      progressions: [
        {
          progression: "62a4a87da7fdbdabf787e47f",
          key: "Ab",
          _id: "62b5aaa0c9e9fe8a7d7240d3"
        },
        {
          progression: "62adf477ed11cbbe156d5769",
          key: "C",
          _id: "62b5aaa0c9e9fe8a7d7240d3"
        },
      ],
      _id: "62b5aaa0c9e9fe8a7d7240d2"
    },
    {
      part: "chorus",
      progressions: [
        {
          progression: "62a4a51b4693c43dce9be09c",
          key: "E",
          _id: "62b5aaa0c9e9fe8a7d7240d9"
        }
      ],
      _id: "62b5aaa0c9e9fe8a7d7240d8"
    }
  ],
}

{
  title: 'def',
  parts: [
    {
      part: "verse",
      progressions: [
        {
          progression: "33a4a87da7fopvvbf787erwe",
          key: "E",
          _id: "62b5aaa0c9e9fe8a7d7240d3"
        },
        {
          progression: "98opf477ewfscbbe156d5442",
          key: "Bb",
          _id: "62b5aaa0c9e9fe8a7d7240d3"
        },
      ],
      _id: "12r3aaa0c4r5me8a7d72oi8u"
    },
    {
      part: "bridge",
      progressions: [
        {
          progression: "62a4a51b4693c43dce9be09c",
          key: "C#",
          _id: "62b5aaa0c9e9fe8a7d7240d9"
        }
      ],
      _id: "62b5aaa0rwfvse8a7d7240d8"
    }
  ],
}

客户端随请求发送的参数是一个对象数组:

[
  { part: 'verse', progressions: ['62a4a87da7fdbdabf787e47f', '62a4a51b4693c43dce9be09c'] },
  { part: 'chorus', progressions: ['62adf477ed11cbbe156d5769'] }
]

我想通过 mongodb 聚合检索上面输入数组中至少一个对象与它们匹配的文档:
在此示例中,文档在其parts 数组字段中具有在part 属性中具有值“verse”的对象,并且在其中一个对象的progress 属性中具有进程id 的['62a4a87da7fdbdabf787e47f', '62a4a51b4693c43dce9be09c'] 之一在progresss 属性中,或者在其parts 数组字段中具有值的文档中,在part 属性中具有值“chorus”的对象和progress 中的一个对象的progress 属性中的progresss id 之一['62adf477ed11cbbe156d5769']级数属性。
在这个例子中,匹配的文档是第一个(标题为'abc'),但在实际使用中,匹配的文档可能很多。

我尝试自己创建一个聚合管道(使用 mongoose 'aggregate' 方法):

// parsedProgressions = [
//   { part: 'verse', progressions: ['62a4a87da7fdbdabf787e47f', '62a4a51b4693c43dce9be09c'] },
//   { part: 'chorus', progressions: ['62adf477ed11cbbe156d5769'] }
// ]
songs.aggregate([
  {
    $addFields: {
      "tempMapResults": {
        $map: {
          input: parsedProgressions,
          as: "parsedProgression",
          in: {
            $cond: {
              if: { parts: { $elemMatch: { part: "$$parsedProgression.part", "progressions.progression": mongoose.Types.ObjectId("$$parsedProgression.progression") } } },
              then: true, else: false
            }
          }
        }
      }
    }
  },
  {
    $addFields: {
      "isMatched": { $anyElementTrue: ["$tempMapResults"] }
    }
  },
  { $match: { isMatched: true } },
  { $project: { title: 1, "parts.part": 1, "parts.progressions.progression": 1 } }
]);

但它不起作用 - 据我了解,因为 $elemMatch 只能在 $match 阶段使用。
无论如何,我想我把聚合管道过度复杂化了,所以如果你能修复我的聚合管道/提供一个更好的工作管道,我会很高兴。

这不是一个简单的情况,因为它们都是嵌套数组,我们需要匹配partprogressions ,它们不在同一级别

一个选项看起来有点复杂,但可以让你的数据变小:

  1. 为了使事情变得更容易, $set一个名为matchCond的新数组字段,其中包括一个名为progs的数组,其中包含parts.progressions 向其中的每个子对象插入匹配的progressions输入数组。 我们确实需要在这里小心并处理没有匹配的progressions输入数组progressions输入数组的情况,因为这是第二个文档中的“桥”部分的情况。
  2. 现在我们只需要检查这些progs项目中的任何一个, progression字段是否与input数组中的一个选项匹配。 这是使用$filter$redice来完成结果的数量。
  3. 只需匹配有结果的文档并格式化答案
db.collection.aggregate([
  {
    $set: {
      matchCond: {
        $map: {
          input: "$parts",
          as: "parts",
          in: {progs: {
              $map: {
                input: "$$parts.progressions",
                in: {$mergeObjects: [
                    "$$this",
                    {input: {progressions: []}},
                    {input: {$first: {
                          $filter: {
                            input: inputData,
                            as: "inputPart",
                            cond: {$eq: ["$$inputPart.part", "$$parts.part"]}
                          }
                     }}}
                ]}
              }
          }}
        }
      }
    }
  },
  {$set: {
      matchCond: {
        $reduce: {
          input: "$matchCond",
          initialValue: 0,
          in: {$add: [
              "$$value",
              {$size: {
                  $filter: {
                    input: "$$this.progs",
                    as: "part",
                    cond: {$in: ["$$part.progression", "$$part.input.progressions"]}
                  }
                }
              }
            ]
          }
        }
      }
    }
  },
  {$match: {matchCond: {$gt: 0}}},
  {$project: {title: 1, parts: 1}}
])

看看它在操场上的例子是如何工作的

另一种选择是使用$unwind ,它看起来很简单,但会复制您的数据,因此可能会更慢:

db.collection.aggregate([
  {$addFields: {inputData: inputData, cond: "$parts"}},
  {$unwind: "$cond"},
  {$unwind: "$cond.progressions"},
  {$unwind: "$inputData"},
  {$match: {
      $expr: {
        $and: [
          {$eq: ["$cond.part", "$inputData.part"]},
          {$in: ["$cond.progressions.progression", "$inputData.progressions"]}
        ]
      }
    }
  },
  {$project: {title: 1, parts: 1}}
])

看看它在操场上的例子是如何工作的 - 放松

这两者之间有几种选择...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM