nanobar
nanobar

Reputation: 66405

How to use array positional $ with aggregation projection in MongoDB

I had an aggregate call that was working when revisions was an Object (with the keys being the entryId).

await Project.aggregate([
  { $limit: 1 },
  { $match: { _id: ObjectId(projectId) } },
  {
    $project: {
      limits: 1,
      revisions: { $slice: [`$revisions.${entryId}.${languageId}`, startIndex, PageSize] },
      totalRevisions: {
        $size: { $ifNull: [`$revisions.${entryId}.${languageId}`, []] },
      },
    },
  },
]);

Now that I converted revisions to an array with entryId inside it, I'm not sure how to get the same result. I tried:

await Project.aggregate([
  { $limit: 1 },
  {
    $match: {
      _id: ObjectId(projectId),
      'revisions.entryId': ObjectId(entryId),
    },
  },
  {
    $project: {
      limits: 1,
      revisions: { $slice: [`$revisions.$.${languageId}`, startIndex, PageSize] },
      totalRevisions: {
        $size: { $ifNull: [`$revisions.$.${languageId}`, []] },
      },
    },
  },
]);

But I get the error:

MongoError: FieldPath field names may not start with '$'.

How can I use $slice and $ifNull with an item in an array? Thanks

Sample doc:

{
  "revisions" : [ 
    {
      "entryId" : ObjectId("5bbf8813c272e05171463bc4"),
      "5bbe76c6d3fb1a4f143f8304" : [ 
        {
          "authorId" : ObjectId("5b1c5384d75d9f3b0eb65c2a"),
          "revisionId" : ObjectId("5bbf8813c272e05171463bc7"),
          "updated" : "2018-10-11T17:27:47.842Z",
          "value" : "County"
        }
      ]
    }
  ]
}

Upvotes: 3

Views: 1716

Answers (1)

Ashh
Ashh

Reputation: 46481

Order of the pipeline stages really matters here. When you use $limit before the $match then it filters the data from the single document found in the $limit stage.

And if you will use $match before the $limit then it will filter the documents from your all the collection inside the database and will throw single document in the $limit stage.

After that You can try below aggregation

db.collection.aggregate([
  { "$match": {
    "_id": ObjectId(projectId),
    "revisions.entryId": ObjectId(entryId)
  }},
  {
    "$project": {
      "revisions": {
        "$map": {
          "input": "$revisions",
          "in": {
            "$arrayToObject": {
              "$map": {
                "input": {
                  "$filter": {
                    "input": { "$objectToArray": "$$this" },
                    "as": "ee",
                    "cond": { "$eq": ["$$ee.k", "5bbe76c6d3fb1a4f143f8304"] }
                  }
                },
                "as": "dd",
                "in": {
                  "k": "$$dd.k",
                  "v": { "$slice": [startIndex, 1 ] }
                }
              }
            }
          }
        }
      },
      "totalRevisions": {
        "$arrayElemAt": [
          {
            "$map": {
              "input": "$revisions",
              "in": {
                "$size": {
                  "$map": {
                    "input": {
                      "$filter": {
                        "input": { "$objectToArray": "$$this" },
                        "as": "ee",
                        "cond": { "$eq": ["$$ee.k", "5bbe76c6d3fb1a4f143f8304"] }
                      }
                    },
                    "as": "dd",
                    "in": {
                      "k": "$$dd.k",
                      "v": { "$slice": [startIndex, 1] }
                    }
                  }
                }
              }
            }
          },
          0
        ]
      }
    }
  }
])

But If you are just started with your project then I will highly recommend you to don't go with this structure because your nested array keys are dynamic and playing with the dynamic keys are always like playing with naked electric wire.

Upvotes: 1

Related Questions