2

I need to get the latest entry from subdocument's array from a document defined by "_id".

The document looks like this:

{
"_id": "nex67",
"ownedparts": [
    {
        "id": "tool1",
        "history": [
            {
                "time": ISODate("2016-06-07T09:12:54.015Z"),
                "value": 300
            },
            {
                "time": ISODate("2016-06-07T09:12:54.015Z"),
                "value": 240
            }
        ]
    },
    {
        "id": "screw1",
        "history": [
            {
                "time": ISODate("2016-06-07T09:12:54.015Z"),
                "value": 500
            }
        ]
    }
]}

With this query i get full history of "tool1":

db.users.find(  
{
    "_id": "nex67",
    "ownedparts.id": "tool1"
},  { "ownedparts.history.$": 1 })

And with this command i do get the latest entry, but it returns them from both "tool1" and "screw1":

db.users.find(  
{"_id": "nex67"},
{"ownedparts.history": { $slice: -1 }})

So basically i need help combining these queries so that i can get the latest history entry from nex67's tool1.

The result i need should look like this:

{
    "_id" : "nex67",
    "ownedparts" : [
            {
                    "id" : "tool1",
                    "history" : [
                            {
                                    "time" : ISODate("2016-06-07T09:12:54.015Z"),
                                    "value" : 240
                            }
                    ]
            }
    ]}

3 Answers 3

2

The optimal way to do this is in MongoDB 3.2 using the $arrayElemAt and the $filter operators.

db.users.aggregate(
    [
        { "$match": { "_id": "nex67", "ownedparts.id": "tool1" } }, 
        { "$project": { 
            "ownedparts": { 
                "$let": { 
                    "vars": { 
                        "el": { 
                            "$arrayElemAt": [ 
                                { "$filter": { 
                                    "input": "$ownedparts", 
                                    "as": "own", 
                                    "cond": { "$eq": [ "$$own.id", "tool1" ] }
                                }}, 
                                0 
                            ]
                        }
                    }, 
                    "in": { 
                        "id": "$$el.id", 
                        "history": { "$arrayElemAt": [ "$$el.history", -1 ] } 
                    }
                }
            }
        }}
    ]
)

which produces:

{ 
    "_id" : "nex67", 
    "ownedparts" : { 
        "id" : "tool1", 
        "history" : {
            "time" : ISODate("2016-06-07T09:12:54.015Z"), 
            "value" : 240 
        } 
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

nice one - perfect combination of $filter inside $arrayElemAt!
1

As we have nested arrays - it is not a trivial problem. To solve that we need to use aggregation framework, to select first record project, then unwind array to reduce nesting and last project2 to reconstruct document as required. match is used as a filter to reuduce amount of document in process.

var match = {
    $match : {
        "_id" : "nex67"
    }
}

var project = {
    $project : {
        _id : 1,
        ownedpartsFirst : {
            $slice : ["$ownedparts", 1]
        }
    }
}
var unwind = {
    $unwind : "$ownedpartsFirst"
}
var project2 = {
    $project : {
        _id : 1,
        ownedparts :
        [{
                id : "$ownedpartsFirst.id",
                history : {
                    $slice : ["$ownedpartsFirst.history", -1]
                }
            }
        ]
    }
}

db.collectionName.aggregate([match, project, unwind, project2])

output:

{
    "_id" : "nex67",
    "ownedparts" : [ 
        {
            "id" : "tool1",
            "history" : [ 
                {
                    "time" : ISODate("2016-06-07T09:12:54.015Z"),
                    "value" : 240.0
                }
            ]
        }
    ]
}

1 Comment

this works too, but i'm choosing the one that uses $arrayElemAt as answer because it seems more optimal.
-1

try with this

db.users.find(  
         {"_id": "nex67"},
         {"ownedparts.$.history": { $slice: -1 }})

And you have a problem with this consult

 db.users.find(  
{
         "_id": "nex67",
         "ownedparts.id": "tool1"
},  { "ownedparts.history.$": 1 })

The simbol $ is between ownedparts and history to get the element from you array

 db.users.find(  
{
         "_id": "nex67",
         "ownedparts.id": "tool1"
},  { "ownedparts.$.history": 1 })

1 Comment

this returns all sub array elements

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.