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

Posted

技术标签:

【中文标题】如何使用 MongoDB 聚合管道从集合中的两个子文档中返回最小值?【英文标题】:How can I return the minimum values from two subdocuments in a collection using MongoDB's aggregation pipeline? 【发布时间】:2015-08-26 23:09:36 【问题描述】:

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

一个示例对象是:


    "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
    

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

我正在寻找一个聚合管道,它将返回每个不同制造商/范围对的最低月租金和最低月付款的列表,但我对如何使用聚合框架的有限知识似乎吸引了我出去。

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

[
    
        "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"
            
        
    ]
)

值得注意的是,就我的测试数据而言,我故意没有为 Range B 指定租金,因为在某些情况下,租金和/或付款都没有针对给定的范围指定。

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


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

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

感谢阅读。

【问题讨论】:

【参考方案1】:

这有点复杂,但这里有一点要理解。第一种情况是简化,然后为每个情况找到最小的数量

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”值而产生了一些开销。更好的方法是在并行查询中运行每个聚合,然后只需合并结果以发送到客户端。

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

【讨论】:

以上是关于如何使用 MongoDB 聚合管道从集合中的两个子文档中返回最小值?的主要内容,如果未能解决你的问题,请参考以下文章

使用聚合管道聚合 MongoDB 中的时间戳集合

如何使用聚合从 mongodb 中的两个集合中查询?

Mongodb 聚合管道限制 $lookup 字段

使用 mongodb 聚合管道按升序向集合中的所有记录添加日期

如何使用聚合管道从 mongodb 中的当前字段中减去更新?

MongoDB - 聚合查询