如何使用 MongoDB 的聚合管道 return 集合中两个子文档的最小值?

How can I return the minimum values from two subdocuments in a collection using MongoDB's aggregation pipeline?

我们在数据库中有一堆产品,每种产品都附有两种类型的货币价值。每个对象都有一个制造商、一个范围和一个描述,每个对象可以有一个月租金额(对于租赁协议)、一个月付款金额(对于融资协议)或两者。

示例对象为:

{
    "manufacturer":         "Manufacturer A",
    "range":                "Range A",
    "description":          "Product Description",
    "rentals": {
        "initialRental":    1111.05,
        "monthlyRental":    123.45,
        "termMonths":       24
    },
    "payments": {
        "deposit":          592.56,
        "monthlyPayment":   98.76,
        "finalPayment":     296.28,
        "termMonths":       36
    }
}

一个给定的制造商和范围通常可以有多个对象。

我正在寻找一个聚合管道,它将 return 每个不同 manufacturer/range 对的最低月租金和最低月付款列表,但我对如何使用的知识有限聚合框架似乎让我失望了。

如果有一个具有两个不同范围的不同制造商,我的预期结果如下:

[
    {
        "manufacturer":     "Manufacturer A",
        "range":            "Range A",
        "minimumRental":    123.45,
        "minimumPayment":   98.76
    },
    {
        "manufacturer":     "Manufacturer A",
        "range":            "Range B",
        "minimumRental":    234.56,
        "minimumPayment":   197.53
    }
]

我正在使用以下方法来尝试实现这一点,但我似乎在 $min 的分组和使用上绊倒了:

db.products.aggregate(
    [
        {
            "$group": {
                "_id": {
                    "manufacturer": "$manufacturer.name",
                    "range":        "$range.name"
                },
                "rentals": {
                    "$addToSet":    "$rentals.monthlyrental"
                },
                "payments": {
                    "$addToSet":    "$payments.monthlypayment"
                }
            }
        },
        {
            "$group": {
                "_id": {
                    "manufacturer": "$_id.manufacturer",
                    "range":        "$_id.range",
                    "payments":     "$payments"
                },
                "minimumRental": {
                    "$min": "$rentals"
                }
            }
        },
        {
            "$project": {
                "_id": {
                    "manufacturer":     "$_id.manufacturer",
                    "range":            "$_id.range",
                    "minimumRental":    "$minimumRental",
                    "payments":         "$_id.payments"
                }
            }
        },
        {
            "$group": {
                "_id": {
                    "manufacturer":     "$_id.manufacturer",
                    "range":            "$_id.range",
                    "minimumRental":    "$_id.minimumRental"
                },
                "minimumPayment": {
                    "$min":             "$_id.payments"
                }
            }
        },
        {
            "$project": {
                "_id": 0,
                "manufacturer":     "$_id.manufacturer",
                "range":            "$_id.range",
                "minimumRental":    "$_id.minimumRental",
                "minimumPayment":   "$minimumPayment"
            }
        }
    ]
)

值得注意的是,在我的测试数据中,我故意没有指定范围 B 的租金,因为在某些情况下,租金 and/or 付款并未同时指定给定范围.

因此,对我的测试数据使用上面的查询得到以下结果:

{
    "0" : {
        "minimumPayment" : [ 
            98.76
        ],
        "manufacturer" : "Manufacturer A",
        "range" : "Range A",
        "minimumRental" : [ 
            123.45
        ]
    },
    "1" : {
        "minimumPayment" : [ 
            197.53
        ],
        "manufacturer" : "Manufacturer A",
        "range" : "Range B",
        "minimumRental" : []
    }
}

这很接近,但我得到的似乎是一个数组而不是最小值。我的印象是我尝试做的事情是可能的,但我似乎无法找到任何足够具体的资源来找出我做错了什么。

感谢阅读。

有点复杂,但这里有一点需要理解。第一种情况是简化,然后只为每个

找到最小的数量
db.collection.aggregate([
    // Tag things with an A/B value11
    { "$project": {
        "_id": {
            "manufacturer": "$manufacturer.name",
            "range": "$range.name",
        },
        "rental": "$rentals.monthlyRental",
        "payment": "$payments.monthlyPayment"
        "type": { "$literal": [ "R","P" ] }
    }},

    // Unwind that "type"
    { "$unwind": "$type" },

    // Group conditionally on the type
    { "$group": {
        "_id": {
            "_id": "$_id",
            "type": "$type"
        },
        "value": {
            "$min": {
                "$cond": [
                    { "$eq": [ "$type", "R" ] },
                    "$rental",
                    "$payment"
                ]
            }
        }
    }},
    // Sort by type and amount
    { "$sort": { "_id.type": 1, "value": 1 } },

    // Group by type only and just take the first after sort
    { "$group": {
        "_id": "$_id.type",
        "manufacturer": { "$first": "$_id._id.manufacturer" },
        "range": { "$first": "$_id._id.range" }
    }}
])

基本上就是这样,只需根据需要使用 $project 清理字段或在代码中处理即可。


虽然我个人觉得这有点草率并且由于 $unwind 执行 "A/B" 值而产生了一些开销。更好的方法是 运行 并行查询中的每个聚合,然后合并结果发送给客户端。

我可以整天讨论并行查询,但基本示例在我最近给出的答案中,所以请阅读 ,它已经展示了执行此操作的一般技术。