【问题标题】:sort array in query and project all fields在查询中排序数组并投影所有字段
【发布时间】:2014-05-18 07:22:01
【问题描述】:

我想在查询时对嵌套数组进行排序,同时投影文档中的所有字段。

示例文档:

{ "_id" : 0, "unknown_field" : "foo", "array_to_sort" : [ { "a" : 3, "b" : 4 }, { "a" : 3, "b" : 3 }, { "a" : 1, "b" : 0 } ] }

我可以使用聚合执行排序,但我无法保留我需要的所有字段。应用程序在查询时不知道每个文档中可能出现哪些其他字段,因此我无法明确投影它们。如果我有一个通配符来投影所有字段,那么这将起作用:

db.c.aggregate([
    {$unwind: "$array_to_sort"},
    {$sort: {"array_to_sort.b":1, "array_to_sort:a": 1}},
    {$group: {_id:"$_id", array_to_sort: {$push:"$array_to_sort"}}}
]);

...但不幸的是,它产生的结果不包含“unknown_field”:

    {
        "_id" : 0,
        "array_to_sort" : [
            {
                "a" : 1,
                "b" : 0
            },
            {
                "a" : 3,
                "b" : 3
            },
            {
                "a" : 3,
                "b" : 4
            }
        ]
    }

这里是插入命令,如果您想尝试一下:

db.c.insert({"unknown_field": "foo", "array_to_sort": [{"a": 3, "b": 4}, {"a": 3, "b":3}, {"a": 1, "b":0}]})

我无法对数组进行预排序,因为排序标准是动态的。我可能在查询时按 a 和/或 b 升序/降序的任意组合进行排序。我意识到我可能需要在我的客户端应用程序中执行此操作,但如果我可以在 mongo 中执行此操作会很不错,因为这样我还可以 $slice/skip/limit 分页的结果,而不是每次都检索整个数组。

【问题讨论】:

    标签: mongodb mongodb-query aggregation-framework


    【解决方案1】:

    由于您正在对文档_id 进行分组,因此您只需将希望保留的字段放在分组_id 中即可。然后你可以使用$project 重新形成

    db.c.aggregate([
        { "$unwind": "$array_to_sort"},
        { "$sort": {"array_to_sort.b":1, "array_to_sort:a": 1}},
        { "$group": { 
            "_id": {
                "_id": "$_id",
                "unknown_field": "$unknown_field"
            },
            "Oarray_to_sort": { "$push":"$array_to_sort"}
        }},
        { "$project": {
            "_id": "$_id._id",
            "unknown_field": "$_id.unknown_field",
            "array_to_sort": "$Oarray_to_sort"
        }}
    ]);
    

    其中的另一个“技巧”是在分组阶段为数组使用临时名称。因此,当您 $project 并更改名称时,您将按照投影语句中指定的顺序获取字段。如果没有,那么“array_to_sort”字段将不是顺序中的最后一个字段,因为它是从前一阶段复制的。

    这是$project 中的预期优化,但如果您想要订单,则可以按照上述方式进行。


    对于完全未知的结构,有 mapReduce 的处理方式:

    db.c.mapReduce(
        function () {
            this["array_to_sort"].sort(function(a,b) {
                return a.a - b.a || a.b - b.b;
            });
    
            emit( this._id, this );
        },
        function(){},
        { "out": { "inline": 1 } }
    )
    

    当然,它具有特定于 mapReduce 的输出格式,因此不完全是您拥有的文档,但所有字段都包含在“值”下:

    {
        "results" : [
                {
                        "_id" : 0,
                        "value" : {
                                "_id" : 0,
                                "some_field" : "a",
                                "array_to_sort" : [
                                        {
                                                "a" : 1,
                                                "b" : 0
                                        },
                                        {
                                                "a" : 3,
                                                "b" : 3
                                        },
                                        {
                                                "a" : 3,
                                                "b" : 4
                                        }
                                ]
                        }
                }
        ],
    }
    

    未来版本(截至撰写时)允许您使用 $$ROOT 变量聚合来表示文档:

    db.c.aggregate([
        { "$project": {
            "_id": "$$ROOT",
            "array_to_sort": "$array_to_sort"
        }},
        { "$unwind": "$array_to_sort"},
        { "$sort": {"array_to_sort.b":1, "array_to_sort:a": 1}},
        { "$group": { 
            "_id": "$_id",
            "array_to_sort": { "$push":"$array_to_sort"}
        }}
    ]);
    

    因此,使用最终的“项目”阶段毫无意义,因为您实际上并不了解文档中的其他字段。但它们都将包含在结果文档的 _id 字段中(包括原始数组和 order )。

    【讨论】:

    • @Neil_Lunn 谢谢。应用程序在查询时不知道 unknown_field 的名称。我正在寻找一种方法来捕捉所有这些 - 比如通配符或包含所有类型的东西。
    • @BobB 从 MongoDB 版本 2.6(目前是候选发布版)开始,有一个“$$ROOT”变量可用于在给定管道阶段以它的形式复制整个文档。但是,如果您不知道这些字段,您将无法以相同的形式获取文档。另一种选择是使用“mapReduce”和任意 JavaScript 来执行此操作。实际上,您最好重新考虑架构设计,使其至少具有某种程度的“统一”行为。
    • @BobB 添加了mapReduce和future聚合方法供参考。
    • @BobB 也许您错过了表明答案已被编辑的评论。
    • @Neil_Lunn 我无法测试 $$ROOT 功能,但这听起来可以满足我的需求。
    猜你喜欢
    • 2012-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-02
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多