2

This is my current aggregation result:

{
    "_id" : 1.0,
    "name" : "Bob",
    "posts" : [ 
        {
            "_id" : 1.0,
            "content" : "the first post",
            "comments" : [ 
                {
                    "_id" : "1a",
                    "content" : "first comment on first post"
                }, 
                {
                    "_id" : "1b",
                    "content" : "second comment on first post"
                }
            ]
        }, 
        {
            "_id" : 2.0,
            "content" : "the second post",
            "comments" : [ 
                {
                    "_id" : "2a",
                    "content" : "first comment on second post"
                }
            ]
        }
    ]
}

What pipeline should I use to slice the posts.comments array so for example it only display 1 comment. This is the goal:

[{
  _id: doc1,
  ...,
   posts: [{
     ...,
     comments: [{ _id: 1a, ... }]
   }, {
     ...,
     comments: [{ _id: 2a, ... }]
   }]
}]

1 Answer 1

2

If you want to $slice the first array element only from the "inner" array of each member, then you apply it as such within a $map on the "outer" array:

db.collection.aggregate([
  { "$addFields": {
    "posts": {
      "$map": {
        "input": "$posts",
        "as": "p",
        "in": {
          "_id": "$$p._id",
          "content": "$$p.content",
          "comments": { "$slice": [ "$$p.comments", 1 ] }
        }
      }
    }
  }}
])

Which would return:

/* 1 */
{
    "_id" : 1.0,
    "name" : "Bob",
    "posts" : [ 
        {
            "_id" : 1.0,
            "content" : "the first post",
            "comments" : [ 
                {
                    "_id" : "1a",
                    "content" : "first comment on first post"
                }
            ]
        }, 
        {
            "_id" : 2.0,
            "content" : "the second post",
            "comments" : [ 
                {
                    "_id" : "2a",
                    "content" : "first comment on second post"
                }
            ]
        }
    ]
}

If you just wanted the "element" instead of returning an array, then it's the same thing, but using $arrayElemAt instead:

db.collection.aggregate([
  { "$addFields": {
    "posts": {
      "$map": {
        "input": "$posts",
        "as": "p",
        "in": {
          "_id": "$$p._id",
          "content": "$$p.content",
          "comments": { "$arrayElemAt": [ "$$p.comments", 1 ] }
        }
      }
    }
  }}
])

Which would return:

/* 1 */
{
    "_id" : 1.0,
    "name" : "Bob",
    "posts" : [ 
        {
            "_id" : 1.0,
            "content" : "the first post",
            "comments" : {
                "_id" : "1a",
                "content" : "first comment on first post"
            }
        }, 
        {
            "_id" : 2.0,
            "content" : "the second post",
            "comments" : {
                "_id" : "2a",
                "content" : "first comment on second post"
            }
        }
    ]
}

Noting that the main difference there ( aside from element vs array ) is in the usage, where $slice can be called asking just for the "length" of items to return, and optionally a starting index position, but here we just use the length.

For $arrayElemAt it is always just the "index", which is n-1 and always just a "singular element" as opposed to anything that could be a "list" or "array".

The whole point is that it is the "posts" array that you are doing the primary "reshaping" on, and to "reshape" an array you use $map and apply any transitions to each element.

I might add here that "current aggregation result" quite usually gives way to that there may well have been an opportunity to actually restrict the result before you actually got to that state. So it's quite often a good idea to go back and look at how your result was constructed to this point and look for the opportunity to return less if that is your actual intent.

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

1 Comment

Yes, using $addFields is the solution. Thank you! Just to make clear of my "current aggregation result" chain, first I use $match to filter then $unwind with $lookup to join latest_posts (exist on the initiating collection) with posts collection then finally $project to filter some fields

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.