MongoDB在特定嵌套属性上的投影

时间:2018-09-26 09:49:15

标签: mongodb mongodb-query aggregation-framework

"data" : {
    "visits" : {
        "daily" : {
            "2018-09-05" : 3586,
            "2018-09-06" : 2969,
            "2018-09-07" : 2624,
            "2018-09-08" : 2803,
            "2018-09-09" : 3439,
            "2018-09-10" : 3655
        }
    }
},

我在MongoDB中具有这样的属性结构,我想做的是,例如,如果我有开始日期和结束日期,例如(2018-09-06-2018-09-07), 我想以这种格式获取结果

"data" : {
    "visits" : {
        "daily" : {
            "2018-09-06" : 2969,
            "2018-09-07" : 2624
        }
    }
},

有没有有效的方法可以动态地做到这一点?我可以通过将类似{{data.visits.daily.2018-09-06“:1,” data.visits.daily.2018-09-07“:1}的投影内容做到这一点,但它可以正常工作在我看来,这不是一个好的解决方案。

2 个答案:

答案 0 :(得分:1)

使用 MongoDB 3.4.4 和更高版本:

db.collection.aggregate([
    { "$addFields": { 
        "data.visits.daily": {
            "$arrayToObject": {
                "$filter": {
                    "input": { "$objectToArray": "$data.visits.daily" },
                    "as": "el",
                    "cond": {
                        "$and": [
                            { "$gte": ["$$el.k", "2018-09-06"] },
                            { "$lte": ["$$el.k", "2018-09-07"] },
                        ]
                    }
                }
            }
        }
    } }
])

上述管道将产生最终输出

{
    "data" : {
        "visits" : {
            "daily" : {
                "2018-09-06" : 2969,
                "2018-09-07" : 2624
            }
        }
    }
}

说明

可以分解管道以显示每个操作员的结果。

$objectToArray

$objectToArray 使您可以使用动态键来转换文档 放入一个数组,该数组包含原始文档中每个字段/值对的元素。返回数组中的每个元素都是一个包含两个字段k和v的文档。

$project 阶段仅由操作员运行管道

db.collection.aggregate([
    { "$project": {
        "keys": { "$objectToArray": "$data.visits.daily" }
    } }
])

收益

{
    "_id" : ObjectId("5bab6d09b1951fef20a5dce4"),
    "keys" : [ 
        {
            "k" : "2018-09-05",
            "v" : 3586
        }, 
        {
            "k" : "2018-09-06",
            "v" : 2969
        }, 
        {
            "k" : "2018-09-07",
            "v" : 2624
        }, 
        {
            "k" : "2018-09-08",
            "v" : 2803
        }, 
        {
            "k" : "2018-09-09",
            "v" : 3439
        }, 
        {
            "k" : "2018-09-10",
            "v" : 3655
        }
    ]
}

$filter

$filter 运算符充当 $objectToArray 运算符生成的数组的过滤机制,它通过选择数组的子集来工作根据指定条件返回 成为您的查询。

考虑以下管道,该管道返回与条件"2018-09-06" <= key <= "2018-09-07"相匹配的键/值对的数组

db.collection.aggregate([
    { "$project": {
        "keys": { 
            "$filter": {
                "input": { "$objectToArray": "$data.visits.daily" },
                "as": "el",
                "cond": {
                    "$and": [
                        { "$gte": ["$$el.k", "2018-09-06"] },
                        { "$lte": ["$$el.k", "2018-09-07"] },
                    ]
                }
            }  
        }
    } }
])

产生

{
    "_id" : ObjectId("5bab6d09b1951fef20a5dce4"),
    "keys" : [ 
        {
            "k" : "2018-09-06",
            "v" : 2969
        }, 
        {
            "k" : "2018-09-07",
            "v" : 2624
        }
    ]
}

$arrayToObject

这将从

转换上面的过滤数组
[ 
    {
        "k" : "2018-09-06",
        "v" : 2969
    }, 
    {
        "k" : "2018-09-07",
        "v" : 2624
    }
]

使用动态键到原始文档

{
    "2018-09-06" : 2969,
    "2018-09-07" : 2624
}

所以要运行管道

db.collection.aggregate([
    { "$project": { 
        "keys": {
            "$arrayToObject": {
                "$filter": {
                    "input": { "$objectToArray": "$data.visits.daily" },
                    "as": "el",
                    "cond": {
                        "$and": [
                            { "$gte": ["$$el.k", "2018-09-06"] },
                            { "$lte": ["$$el.k", "2018-09-07"] },
                        ]
                    }
                }
            }
        }
    } }
])

会产生

{
    "_id" : ObjectId("5bab6d09b1951fef20a5dce4"),
    "keys" : {
        "2018-09-06" : 2969,
        "2018-09-07" : 2624
    }
}

但是,您当然要保留原始架构,即当前字段,因此您需要使用 $addFields 而不是 $project >用于说明的管道。

$addFields

这等效于 $project 阶段,该阶段明确指定输入文档中的所有现有字段并添加新字段。在 $addFields 操作中指定现有字段名称将导致替换原始字段,并且您将需要使用点符号来使用动态键更新嵌入的data.visits.daily字段。

答案 1 :(得分:0)

您可以使用以下聚合来实现:

var startdate = "2018-09-06";
var enddate = "2018-09-09";
db['01'].aggregate(
    [
        {
            $project: {
               daily:{$objectToArray:"$data.visits.daily"}
            }
        },
        {
            $unwind: {
                path : "$daily",

            }
        },
        {
            $addFields: {
                "date": {$dateFromString:{dateString:"$daily.k",format:"%Y-%m-%d"}}
            }
        },
        {
            $match: {
            $and:[{date:{$gte:new Date(startdate)}},{date:{$lte:new Date(enddate)}}]
            }
        },
        {
            $group: {
            _id:"_id",
            daily:{$push:"$daily"}
            }
        },
        {
            $project: {
                "data.visits.daily":{$arrayToObject:"$daily"}
            }
        },
    ]
);