【问题标题】:MongoDB Document Re-shapingMongoDB 文档重塑
【发布时间】:2014-03-23 20:36:05
【问题描述】:

这个问题来自(正如我通常所做的那样)仔细阅读关于 SO 提出的问题,因此,为我自己提出了另一个问题。因此,除了学习练习,我发现另一个问题会出现,比如这个。

original question 仍未被 OP 接受,并且确实没有明确“他们”想要实现什么。但我确实给出了我的解释,以简单两种形式得出解决方案。

最后,这个过程让我想知道,考虑到解决方案的 long 形式,在下一个(目前期待 2.6)MongoDB 版本中是否会引入一些新功能,使用已引入的其他聚合运算符。

所以情况如下:

示例文档

{
    "tracked_item_type" : "Software",
    "tracked_item_name" : "Word",
    "duration" : 9540
}
{
    "tracked_item_type" : "Software",
    "tracked_item_name" : "Excel",
    "duration" : 4000
}
{
    "tracked_item_type" : "Software",
    "tracked_item_name" : "Notepad",
    "duration" : 4000
}
{
    "tracked_item_type" : "Site",
    "tracked_item_name" : "Facebook",
    "duration" : 7920
}
{
    "tracked_item_type" : "Site",
    "tracked_item_name" : "Twitter",
    "duration" : 5555
}
{
    "tracked_item_type" : "Site",
    "tracked_item_name" : "Digital Blasphemy",
    "duration" : 8000
}

期望的结果

每种类型的前两个结果,按总时长排序。尽管这是一个样本,但持续时间被认为是许多项目的 $sum

{ 
    "tracked_item_type": "Site",
    "tracked_item_name": "Digital Blasphemy",
    "duration" : 8000
}
{ 
    "tracked_item_type": "Site",
    "tracked_item_name": "Facebook",
    "duration" : 7920
}
{ 
    "tracked_item_type": "Software",
    "tracked_item_name": "Word",
    "duration" : 9540
}
{ 
    "tracked_item_type": "Software",
    "tracked_item_name": "Notepad",
    "duration" : 4000
}

聚合解决方案

这是我冗长解决问题的方法

db.collection.aggregate([

    // Group on the types and "sum" of duration
    {"$group": {
        "_id": {
            "tracked_item_type": "$tracked_item_type",
            "tracked_item_name": "$tracked_item_name"
         },
        "duration": {"$sum": "$duration"}
    }},

    // Sort by type and duration descending
    {"$sort": { "_id.tracked_item_type": 1, "duration": -1 }},

    /* The fun part */

    // Re-shape results to "sites" and "software" arrays 
    {"$group": { 
        "_id": null,
        "sites": {"$push":
            {"$cond": [
                {"$eq": ["$_id.tracked_item_type", "Site" ]},
                { "_id": "$_id", "duration": "$duration" },
                null
            ]}
        },
        "software": {"$push":
            {"$cond": [
                {"$eq": ["$_id.tracked_item_type", "Software" ]},
                { "_id": "$_id", "duration": "$duration" },
                null
            ]}
        }
    }},


    // Remove the null values for "software"
    {"$unwind": "$software"},
    {"$match": { "software": {"$ne": null} }},
    {"$group": { 
        "_id": "$_id",
        "software": {"$push": "$software"}, 
        "sites": {"$first": "$sites"} 
    }},

    // Remove the null values for "sites"
    {"$unwind": "$sites"},
    {"$match": { "sites": {"$ne": null} }},
    {"$group": { 
        "_id": "$_id",
        "software": {"$first": "$software"},
        "sites": {"$push": "$sites"} 
    }},


    // Project out software and limit to the *top* 2 results
    {"$unwind": "$software"},
    {"$project": { 
        "_id": 0,
        "_id": { "_id": "$software._id", "duration": "$software.duration" },
        "sites": "$sites"
    }},
    {"$limit" : 2},


    // Project sites, grouping multiple software per key, requires a sort
    // then limit the *top* 2 results
    {"$unwind": "$sites"},
    {"$group": {
        "_id": { "_id": "$sites._id", "duration": "$sites.duration" },
        "software": {"$push": "$_id" }
    }},
    {"$sort": { "_id.duration": -1 }},
    {"$limit": 2}

])

“还没有”输出

聚合达不到最终结果的点。至少就我目前的理解而言。

{
    "result" : [
        {
            "_id" : {
                "_id" : {
                    "tracked_item_type" : "Site",
                    "tracked_item_name" : "Digital Blasphemy"
                 },
                 "duration" : 8000
           },
            "software" : [
                {
                    "_id" : {
                        "tracked_item_type" : "Software",
                        "tracked_item_name" : "Word"
                    },
                    "duration" : 9540
                },

                {
                    "_id" : {
                        "tracked_item_type" : "Software",
                        "tracked_item_name" : "Notepad"
                    },
                    "duration" : 4000
                }
            ]
        },
        {
            "_id" : {
                "_id" : {
                    "tracked_item_type" : "Site",
                    "tracked_item_name" : "Facebook"
                },
                "duration" : 7920
            },
            "software" : [
                {
                    "_id" : {
                        "tracked_item_type" : "Software",
                        "tracked_item_name" : "Word"
                    },
                    "duration" : 9540
                },
                {
                    "_id" : {
                        "tracked_item_type" : "Software",
                        "tracked_item_name" : "Notepad"
                    },
                    "duration" : 4000
                }
            ]
        }
    ],
    "ok" : 1
}

这一切似乎都非常合理(无论如何对我来说)结果,虽然不是完整,但可以在代码中后处理以便按摩 em> 将其转换为所需的形式。

但事实上,这似乎是一种练习,也是一个有趣的点,即是否可以通过使用任何即将推出的功能进行聚合(或可能是其他功能)来实现我一直没有掌握的技术)来获得所需的结果形式。

因此,请随时就如何实现这一目标提出任何建议/指示。

【问题讨论】:

    标签: mongodb aggregation-framework


    【解决方案1】:

    首先得到了我自己的答案!

    我没想到会这样,但可以在 2.6 的实现中找到答案,其中包括一些很棒的新 Set Operators

    所以我(最终)想到的是,问题归结为有两个需要一个的单独列表,那么如何合并 这些项目,以便它们都属于 一个 字段。因此, 明显 运算符,$setUnion

    这是新的片段,我将分部分解释:

    // So this part just "normalizes" a little so we get one record that essentially has
    // two arrays in it
    {"$group": { 
        _id: { _id: null, software: "$software"  },
        sites: {$push:"$_id" }
    }},
    

    以及生成的文档:

    {
        "_id" : {
            "_id" : null,
            "software" : [
                 {
                     "_id" : {
                         "tracked_item_type" : "Software",
                         "tracked_item_name" : "Word"
                     },
                 "duration" : 9540
                 },
                 {
                     "_id" : {
                         "tracked_item_type" : "Software",
                         "tracked_item_name" : "Notepad"
                 },
                 "duration" : 4000
             }
            ]
        },
        "sites" : [
            {
                "_id" : {
                    "tracked_item_type" : "Site",
                    "tracked_item_name" : "Digital Blasphemy"
                },
                "duration" : 8000
            },
            {
                "_id" : {
                    "tracked_item_type" : "Site",
                    "tracked_item_name" : "Facebook"
                },
                "duration" : 7920
            }
        ]
    }
    

    所以那个文档,可以说是结果更好形式,比我之前离开的地方, 考虑到项目不再重复,本质上是我们想要合并一个两个列表。所以现在要做的就是使用促进这种合并的操作符:

    // Then we just project with a new field, and the "$setUnion" of the two arrays
    {"$project": { 
        "_id": 0,
        "records": {"$setUnion": ["$_id.software", "$sites"]} 
    }},
    

    这将我们带到了这里:

    {
        "records" : [
            {
                "_id" : {
                    "tracked_item_type" : "Site",
                    "tracked_item_name" : "Facebook"
                },
                "duration" : 7920
            },
            {
                "_id" : {
                    "tracked_item_type" : "Software",
                    "tracked_item_name" : "Word"
                },
                "duration" : 9540
            },
            {
                "_id" : {
                    "tracked_item_type" : "Site",
                    "tracked_item_name" : "Digital Blasphemy"
                },
                "duration" : 8000
            },
            {
                "_id" : {
                    "tracked_item_type" : "Software",
                    "tracked_item_name" : "Notepad"
                },
                "duration" : 4000
            }
        ]
    }
    

    基本上就是这样。现在我们只有四个项目,所以通过一点“unwinding”、projectionsort,我们将得到 我正在寻找的确切结果。

    所以这就是全部内容,仅供记录:

    db.collection.aggregate([
    
        // Group on the types and "sum" of duration
        {"$group": {
            "_id": {
                "tracked_item_type": "$tracked_item_type",
                "tracked_item_name": "$tracked_item_name"
             },
            "duration": {"$sum": "$duration"}
        }},
    
        // Sort by type and duration descending
        {"$sort": { "_id.tracked_item_type": 1, "duration": -1 }},
    
        /* The fun part */
    
        // Re-shape results to "sites" and "software" arrays 
        {"$group": { 
            "_id": null,
            "sites": {"$push":
                {"$cond": [
                    {"$eq": ["$_id.tracked_item_type", "Site" ]},
                    { "_id": "$_id", "duration": "$duration" },
                    null
                ]}
            },
            "software": {"$push":
                {"$cond": [
                    {"$eq": ["$_id.tracked_item_type", "Software" ]},
                    { "_id": "$_id", "duration": "$duration" },
                    null
                ]}
            }
        }},
    
    
        // Remove the null values for "software"
        {"$unwind": "$software"},
        {"$match": { "software": {"$ne": null} }},
        {"$group": { 
            "_id": "$_id",
            "software": {"$push": "$software"}, 
            "sites": {"$first": "$sites"} 
        }},
    
        // Remove the null values for "sites"
        {"$unwind": "$sites"},
        {"$match": { "sites": {"$ne": null} }},
        {"$group": { 
            "_id": "$_id",
            "software": {"$first": "$software"},
            "sites": {"$push": "$sites"} 
        }},
    
    
        // Project out software and limit to the *top* 2 results
        {"$unwind": "$software"},
        {"$project": { 
            "_id": 0,
            "_id": { "_id": "$software._id", "duration": "$software.duration" },
            "sites": "$sites"
        }},
        {"$limit" : 2},
    
    
        // Project sites, grouping multiple software per key, requires a sort
        // then limit the *top* 2 results
        {"$unwind": "$sites"},
        {"$group": {
            "_id": { "_id": "$sites._id", "duration": "$sites.duration" },
            "software": {"$push": "$_id" }
        }},
        {"$sort": { "_id.duration": -1 }},
        {"$limit": 2},
    
        // So this part just "normalizes" a little so we get one record that
        // essentially has two arrays in it
        {"$group": { 
            _id: { _id: null, software: "$software"  },
            sites: {$push:"$_id" }
        }},
    
        // Then we just project with a new field, and the "$setUnion" of the two arrays
        {"$project": { 
            "_id": 0,
           "records": {"$setUnion": ["$_id.software", "$sites"]} 
        }},
    
        // Unwind the array to documents
        {"$unwind": "$records"},
    
        // Shape the final output
        {"$project": { 
            "tracked_item_type": "$records._id.tracked_item_type",
            "tracked_item_name": "$records._id.tracked_item_name",
            "duration": "$records.duration"
        }},
    
         // Final sort on the result
        {"$sort": { "tracked_item_type": 1, "duration": -1 }} 
    
    ])
    

    显然有一个下降点,整个方法变得不切实际,因为一般前提是能够将$push所有文档放入它们自己的数组中,以便top 结果可以通过最终调用$limit提取这些结果。

    因此,如果每个 “类别” 会有 大量 个结果,那么处理 可能是一种更实用的方法类别”,然后简单地将这些结果中的每个限制为所需的前两个项。

    但作为练习,我现在至少知道这可以完成。希望这对某人有用。

    仍然有兴趣看看是否有人有其他方法。

    【讨论】:

    • 您不需要集合运算符来获得答案。 (相当丑陋的)解决方案仅使用聚合框架的 2.4 功能是可能的。
    • @AsyaKamsky 我认为问题中提到了这一点。该问题将结果显示到某些阶段。但这一切都是为了达到预期的结果。欢迎/鼓励您自己提交自己的方法
    【解决方案2】:

    这是一个聚合,它在每个类别中按持续时间查找前两个(它确实任意打破“关系”,这似乎与您的示例输出一致):

    var pregroup = { "$group" : {
            "_id" : {
                "type" : "$tracked_item_type",
                "name" : "$tracked_item_name"
            },
            "duration" : {
                "$sum" : "$duration"
            }
        }
    };
    var sort = { "$sort" : { "_id.type" : 1, "duration" : -1 } };
    var group1 = { "$group" : {
            "_id" : "$_id.type",
            "num1" : {
                "$first" : {
                    "name" : "$_id.name",
                    "dur" : "$duration"
                }
            },
            "other" : {
                "$push" : {
                    "name" : "$_id.name",
                    "dur" : "$duration"
                }
            },
        "all" : {
            "$push" : {
                "name" : "$_id.name",
                "dur" : "$duration"
            }
        }
        }
    };
    var unwind = { "$unwind" : "$other" };
    project = {
        "$project" : {
            "keep" : {
                "$ne" : [
                    "$num1.name",
                    "$other.name"
                ]
            },
            "num1" : 1,
            "all" : 1,
            "other" : 1
        }
    };
    var match = { "$match" : { "keep" : true } };
    var sort2 = { "$sort" : { "_id" : 1, "other.dur" : -1 } };
    var group2 = { "$group" : {
            "_id" : "$_id",
            "numberOne" : {
                "$first" : "$num1"
            },
            "numberTwo" : {
                "$first" : "$other"
            },
        "all" : {
            "$first" : "$all"
        }
        }
    };
    unwind2 = { "$unwind" : "$all" };
    project2 = { "$project" : {
        "_id" : 0,
        "tracked_item_type" : "$_id",
        "tracked_item_name" : {
            "$cond" : [
                {
                    "$or" : [
                        {
                            "$eq" : [
                                "$all.name",
                                "$numberOne.name"
                            ]
                        },
                        {
                            "$eq" : [
                                "$all.name",
                                "$numberTwo.name"
                            ]
                        }
                    ]
                },
                "$all.name",
                null
            ]
        },
        "duration" : {
            "$cond" : [
                {
                    "$or" : [
                        {
                            "$eq" : [
                                "$all.name",
                                "$numberOne.name"
                            ]
                        },
                        {
                            "$eq" : [
                                "$all.name",
                                "$numberTwo.name"
                            ]
                        }
                    ]
                },
                "$all.dur",
                null
            ]
        }
    }
    }
    match2 = { "$match" : { "tracked_item_name" : { "$ne" : null } } };
    

    使用您的示例数据运行:

    db.top2.aggregate(pregroup, sort, group1, unwind, project, match, sort2, group2, unwind2, project2, match2).toArray()
    [
        {
            "tracked_item_type" : "Software",
            "tracked_item_name" : "Word",
            "duration" : 9540
        },
        {
            "tracked_item_type" : "Software",
            "tracked_item_name" : "Notepad",
            "duration" : 4000
        },
        {
            "tracked_item_type" : "Site",
            "tracked_item_name" : "Digital Blasphemy",
            "duration" : 8000
        },
        {
            "tracked_item_type" : "Site",
            "tracked_item_name" : "Facebook",
            "duration" : 7920
        }
    ]
    

    这将适用于任意数量的域(不同的跟踪项目类型值),您无需提前知道它们的所有名称。但是,将其概括为前三、前四、前五等,将为每个额外的前“N”值增加四个阶段——不是很实用或漂亮。

    vote up this jira ticket 在聚合框架中获得“top N”功能的更原生实现。

    【讨论】:

    • 一种非常新颖的方法。虽然不完全符合问题的精神,但到达那里所示的 "Desired Result" ,而不是接近那种形式的东西。尽管如此,做得很好。正如您所说,这种方法确实具有 与预先知道域值相关的优点,尽管它对要过滤的项目数量有自己的限制。但回到“精神”,甚至似乎没有即将发布的功能可以将这些结果转换为所需的形式。你当然是对的,我们需要分组的限制函数。
    • 我不知道为什么这不符合我们的精神 - 我认为这是为每个域获得前两个结果。
    • 问题中的期望结果非常清楚,它也达到了近似值,但不是实际结果,就像这里 record 类型的布局结果一样需要在代码中进行按摩。正如我已经说过的那样,这不会影响您自己的方法。您和我一样都知道,如果分组结果可能受到限制,那么这是一个简单的解决方案。但他们不能。这对我来说没有声望。我只是想看看是否有某种方法可以实现如图所示的实际文档形式。但是,嘿,你可能会获得投票并获得一些赏金。
    • 你想要的格式的问题是它很难随意打破关系(现在我想起来了,也许为“2nd”保留一个相等的值是不正确的,也许保留额外的如果有平局,则条目实际上是正确的,否则您可能没有做“正确的事情”)
    • 我一直想为我的博客编写“top-N”解决方法,我想现在我没有理由不这样做了。
    猜你喜欢
    • 2017-01-25
    • 2023-04-08
    • 2016-08-30
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 2015-05-15
    • 2020-03-16
    • 1970-01-01
    相关资源
    最近更新 更多