Magus
Magus

Reputation: 15114

Include array index after a filter in a nested array in mongodb

I have a collection containing objects like this one:

{
    _id: '1234',
   lines: [{
       timestamp: 100,
       data: {
            prop1: 123,
            prop2: 123,
            prop3: 123
       }
   }, {
       timestamp: 200,
       data: {
            prop1: 123,
            prop2: 123,
            prop3: 123
       }
   }, {
       timestamp: 300,
       data: {
            prop1: 123,
            prop2: 123,
            prop3: 123
       }
   }]
}

I need to create a query to filter the lines in my object but I need to know what is the index of the object in the array. My query is like this:

collection.aggregateOne([
    { $match: { _id: queryId } },
    { $filter: { input: '$lines', as: 'line', cond: { $gt: ['$$line.timestamp', queryTimestamp ] } } }
]);

The query works, but I don't know what to write to have the array index of the lines in my output.

For example, if I'm looking for lines with a timestamp greater than 190 in my object, I want this output :

{
    _id: '1234',
    lines: [{
       index: 1, // Index is 1 because the first element is filtered
       timestamp: 200,
       data: {
            prop1: 123,
            prop2: 123,
            prop3: 123
       }
   }, {
       index: 2,
       timestamp: 300,
       data: {
            prop1: 123,
            prop2: 123,
            prop3: 123
       }
   }]
}

Is there a way to achieve that? I am using Mongodb 4.4.1.

Upvotes: 0

Views: 512

Answers (2)

Dheemanth Bhat
Dheemanth Bhat

Reputation: 4452

If I understood the question correctly, author wants to add index in sequence for the filtered data, where the filter is timestamp > 190. While the accepted answer seems to be correct w.r.t the data posted in the question, it wont work for below data:

{
    "_id" : "1234",
    "lines" : [
        {
            "timestamp" : 100,
            "data" : {
                "prop1" : 123,
                "prop2" : 123,
                "prop3" : 123
            }
        },
        {
            "timestamp" : 200,
            "data" : {
                "prop1" : 123,
                "prop2" : 123,
                "prop3" : 123
            }
        },
        {
            "timestamp" : 180,  // <- 180 in between 200 and 300 will break the sequence
            "data" : {
                "prop1" : 123,
                "prop2" : 123,
                "prop3" : 123
            }
        },
        {
            "timestamp" : 300,
            "data" : {
                "prop1" : 123,
                "prop2" : 123,
                "prop3" : 123
            }
        }
    ]
}

Output based on accepted answer:

{
    "_id" : "1234",
    "lines" : [
        {
            "timestamp" : 200,
            "data" : {
                "prop1" : 123,
                "prop2" : 123,
                "prop3" : 123
            },
            "index" : 1
        },
        {
            "timestamp" : 300,
            "data" : {
                "prop1" : 123,
                "prop2" : 123,
                "prop3" : 123
            },
            "index" : 3
        }
    ]
}

So improving the answer by @prasad_:

const queryTimestamp = 190;

db.collectionName.aggregate([
    { $unwind: "$lines" },
    {
        $match: {
            "lines.timestamp": { $gt: queryTimestamp }
        }
    },
    {
        $group: {
            _id: "$_id",
            "lines": { $push: "$lines" }
        }
    },
    {
        $addFields: {
            "lines": {
                $reduce: {
                    input: { $range: [0, { $size: "$lines" }] },
                    initialValue: [],
                    in: {
                        $concatArrays: [
                            "$$value",
                            [
                                {
                                    timestamp: { $arrayElemAt: ["$lines.timestamp", "$$this"] },
                                    data: { $arrayElemAt: ["$lines.data", "$$this"] },
                                    index: { $add: ["$$this", 1] }
                                }
                            ]
                        ]
                    }
                }
            }
        }
    }
]);

Output based on improved answer:

{
    "_id" : "1234",
    "lines" : [
        {
            "timestamp" : 200,
            "data" : {
                "prop1" : 123,
                "prop2" : 123,
                "prop3" : 123
            },
            "index" : 1
        },
        {
            "timestamp" : 300,
            "data" : {
                "prop1" : 123,
                "prop2" : 123,
                "prop3" : 123
            },
            "index" : 2
        }
    ]
}

Upvotes: 0

prasad_
prasad_

Reputation: 14297

This aggregation query returns the desired output:

db.collection.aggregate([
  { 
    $addFields: { 
        lines: { 
           $reduce: { 
               input: { $range: [ 0, { $size: "$lines" } ] }, 
               initialValue: [ ], 
               in: {
                   $concatArrays: [ 
                       "$$value", 
                       {
                          $let: {
                              vars: {
                                  doc: { $arrayElemAt: [ "$lines", "$$this" ] }
                              },
                              in: {
                                  $cond: {
                                      if: { $gt: ['$$doc.timestamp', queryTimestamp ] },
                                      then: [ { $mergeObjects: [ "$$doc", { index: "$$this"} ] }],
                                      else: [ ]
                                  }
                              }
                          }
                      }
                   ]
               }
           }
       }
    }
  }
])

Upvotes: 1

Related Questions