是否可以通过包含子文档的数组中的某个值对文档进行分组?

时间:2019-04-03 12:22:20

标签: mongodb

我有一个集合,其中包含几百万个文档,其结构如下:

{
    "_id" : "5c94bdbfcfccf91aa6903254",
    "source" : "somesourceinfo/6410",
    "language" : "de-de",
    "date_created" : "2019-03-22T10:10:58",
    "data" : [ 
        {
            "value" : "SALE",
            "type" : "product.category"
        }, 
        {
            "value" : "KOCHEN & BACKEN, Kochen, Bräter / Schmortöpfe",
            "type" : "product.category"
        },
        {
            "value" : "4009209314754",
            "type" : "product.gtin"
        }, 
        {
            "value" : "Fissler",
            "type" : "product.manufacturer"
        }, 
        {
            "value" : "55122631",
            "type" : "product.manufacturer_number"
        }
     ]
}

我需要将具有相同product.gtin的文档合并到一个文档中,其中data是一个数组,其中包含匹配文档的所有数据数组的元素。

我尝试过使用聚合框架,但是我似乎总是被卡在某个地方。我通常从匹配实际包含“ product.gtin”的文档开始。

然后,我尝试按该值(各个数组元素中的value属性)分组,并使用$ addToSet组合“数据” -Array,但是无法按该值分组或我只是找不到正确的表达方式。

我还尝试将数组转换为对象并将其放置到文档的根目录中,但是随后遇到了问题,因为我们的类型中包含点,因此我无法再访问属性。

我尝试了其他几种方法,但是通常我有时会遇到问题。

我在问自己,使用我们这样的数据结构是否真的可以实现这一目标。

我如何开始的示例:

db.bulk.aggregate(
    [
        {
            $match: { "data.type" : { $eq : "product.gtin" }} 
        }
    ],
    { allowDiskUse : true }
)

下一阶段将是$ group,然后使用适当的数组元素的值,但是我似乎无法访问它。 我见过人们通过$ unwind访问数组中的子文档,但是看来,这样做之后,我不能以一种很好的方式(例如data.value)进行分组,而又不能按相同的其他值进行分组。

1 个答案:

答案 0 :(得分:1)

已编辑答案,因为旧版本包含错误:

db.bulk.aggregate(
    [
        {
            $match: { "data.type": { $eq: "product.gtin" } }
        },
        {
            $addFields: {
                gtin: {
                    $reduce: {
                        input: "$data",
                        initialValue: "",
                        in: { $concat: ["$$value", { $cond: { if: { $eq: ["$$this.type", "product.gtin"] }, then: "$$this.value", else: "" } }] }
                    }
                }
            }
        },
        {
            $project: {
                data: {
                    $map: {
                        input: "$data",
                        as: "el",
                        in: { type: "$$el.value", value: "$$el.value", source: "$source" }
                    }
                },
                source: "$source",
                gtin: "$gtin"
            }
        },

        { $group: { _id: "$gtin", data: { $addToSet: "$data" }, source: { $addToSet: "$source" } } },
        {
            $addFields: {
                data: {
                    $reduce: {
                        input: "$data",
                        initialValue: [],
                        in: { $concatArrays: ["$$value", "$$this"] }
                    }
                }
            }
        },
        { $out: "bulk.gtin" }
    ],
    { allowDiskUse: true }
)
  1. 我使用$ match,因此仅选择存在gtin的文档。
  2. 我使用$ addFIelds将gtin字段添加到文档的根目录。通过使用$ reduce来添加该字段,当$ data.type为“ product.gtin”时,它将在“ $ data”数组上进行迭代,并将data.value的值连接到空字符串的初始值。这样,我就有了一个包含文档gtin的字段,可用于第3步。
  3. 使用$ project和$ map,我将document_id添加到“ $ data” -Array中的每个元素中。这样就很容易知道每个元素的来源。同样(对于我们的用例而言很重要)这种方式,每个“ $ data”数组都是不同的。在下一步中使用$ addToSet时,不会将包含完全相同文档的数组加在一起。但是我们想要重复的,以便以后可以计数。这样,每个“ $ data”数组实际上都是不同的,因为它包含不同的_id,并且肯定会添加到集合中。
  4. 然后,我使用$ group通过新创建的“ $ gtin”字段对文档进行分组。我还通过$ addToSet将“ $ data”和“ $ source”中的值添加到数组中。
  5. 我使用添加字段来覆盖$ data字段。在第3步中,$ addInSet并未将实际的数组元素一一添加,而是将整个数组包含在“ $ data”中。 因此,我必须从“ $ data”中当前的几个数组中制作一个单个数组。 我为此使用reduce,它接受$ data中包含的元素,并将它们串联在一起。这样,我还保留了重复的元素,这就是我想要的。
  6. 最后我将输出写入新集合