簡體   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