3
"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
        }
    }
},

I have property structure in MongoDB like this, what I am trying to do is, if i have start date and end date, for example (2018-09-06 - 2018-09-07), I want to get result in this format

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

Is there any efficient way to do it dynamically? I can do it by putting in projections things like this {"data.visits.daily.2018-09-06": 1, "data.visits.daily.2018-09-07": 1} and it works but it doesn't seem to me like a good solution.

2 Answers 2

3

Using MongoDB 3.4.4 and newer versions:

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"] },
                        ]
                    }
                }
            }
        }
    } }
])

The above pipeline will yield the final output

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

Explanations

The pipeline can be decomposed to show each individual operator's results.

$objectToArray

$objectToArray enables you to transform the document with dynamic keys into an array that contains a element for each field/value pair in the original document. Each element in the return array is a document that contains two fields k and v.

Running the pipeline with just the operator in a $project stage

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

yields

{
    "_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

The $filter operator acts as a filtering mechanism for the array produced by the $objectToArray operator, works by selecting a subset of the array to return based on the specified condition which becomes your query.

Consider the following pipeline which returns an array of the key/value pair that matches the condition "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"] },
                    ]
                }
            }  
        }
    } }
])

which yields

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

$arrayToObject

This will transform the filtered array above from

[ 
    {
        "k" : "2018-09-06",
        "v" : 2969
    }, 
    {
        "k" : "2018-09-07",
        "v" : 2624
    }
]

to the original document with the dynamic key

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

so running the pipeline

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"] },
                        ]
                    }
                }
            }
        }
    } }
])

will produce

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

But of course you would want to preserve the original schema i.e. the current fields so you would need to use $addFields instead of the $project pipeline used for illustrated.

$addFields

This is is equivalent to a $project stage that explicitly specifies all existing fields in the input documents and adds the new fields. Specifying an existing field name in an $addFields operation causes the original field to be replaced and you would need to use dot notation to to update the embedded data.visits.daily field with the dynamic keys.

Sign up to request clarification or add additional context in comments.

Comments

0

You can achieve this using the following aggregation :

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"}
            }
        },
    ]
);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.