mongo aggregate based on conditions to filter the document for versioning

Viewed 651

I am working on versioning, We have documents based on UUIDs andjobUuids, andjobUuids are the documents associated with the currently working user. I have some aggregate queries on these collections which I need to update based on the job UUIDs,

The results fetched by the aggregate query should be such that,

  • if the current usersjobUuid document does not exist then the master document with jobUuid: "default" will be returned(The document without any jobUuid),
  • if job uuid exists then only the document is returned.

I have a$match used to get these documents based on certain conditions, from those documents I need to filter out the documents based on the above conditions, and an example is shown below,

The data looks like this:

[
  {
    "uuid": "5cdb5a10-4f9b-4886-98c1-31d9889dd943",
    "name": "adam",
    "jobUuid": "default",
  },
  {
    "uuid": "5cdb5a10-4f9b-4886-98c1-31d9889dd943",
    "jobUuid": "d275781f-ed7f-4ce4-8f7e-a82e0e9c8f12",
    "name": "adam"
  },
  {
    "uuid": "b745baff-312b-4d53-9438-ae28358539dc",
    "name": "eve",
    "jobUuid": "default",
  },
  {
    "uuid": "b745baff-312b-4d53-9438-ae28358539dc",
    "jobUuid": "d275781f-ed7f-4ce4-8f7e-a82e0e9c8f12",
    "name": "eve"
  },
  {
    "uuid": "26cba689-7eb6-4a9e-a04e-24ede0309e50",
    "name": "john",
    "jobUuid": "default",
  }
]

Results for "jobUuid": "d275781f-ed7f-4ce4-8f7e-a82e0e9c8f12" should be:

[
  {
    "uuid": "5cdb5a10-4f9b-4886-98c1-31d9889dd943",
    "jobUuid": "d275781f-ed7f-4ce4-8f7e-a82e0e9c8f12",
    "name": "adam"
  },
  {
    "uuid": "b745baff-312b-4d53-9438-ae28358539dc",
    "jobUuid": "d275781f-ed7f-4ce4-8f7e-a82e0e9c8f12",
    "name": "eve"
  },
  {
    "uuid": "26cba689-7eb6-4a9e-a04e-24ede0309e50",
    "name": "john",
    "jobUuid": "default",
  }
]

Based on the conditions mentioned above, is it possible to filter the document within the aggregate query to extract the document of a specific job uuid?

Edit 1: I got the following solution, which is working fine, I want a better solution, eliminating all those nested stages.

Edit 2: Updated the data with actual UUIDs and I just included only the name as another field, we do have n number of fields which are not relevant to include here but needed at the end (mentioning this for those who want to use the projection over all the fields).

3 Answers

Update based on comment:

but the UUIDs are alphanumeric strings, as shown above, does it have an effect on these sorting, and since we are not using conditions to get the results, I am worried it will cause issues.

You could use additional field to match the sort order to be the same order as values in the in expression. Make sure you provide the values with default as the last value.

[
  {"$match":{"jobUuid":{"$in":["d275781f-ed7f-4ce4-8f7e-a82e0e9c8f12","default"]}}},
  {"$addFields":{ "order":{"$indexOfArray":[["d275781f-ed7f-4ce4-8f7e-a82e0e9c8f12","default"], "$jobUuid"]}}},
  {"$sort":{"uuid":1, "order":1}},
  {
    "$group": {
      "_id": "$uuid",
      "doc":{"$first":"$$ROOT"}
    }
  },
  {"$project":{"doc.order":0}},
  {"$replaceRoot":{"newRoot":"$doc"}}
]

example here - https://mongoplayground.net/p/wXiE9i18qxf

Original

You could use below query. The query will pick the non default document if it exists for uuid or else pick the default as the only document.

[
  {"$match":{"jobUuid":{"$in":[1,"default"]}}},
  {"$sort":{"uuid":1, "jobUuid":1}},
  {
    "$group": {
      "_id": "$uuid",
      "doc":{"$first":"$$ROOT"}
    }
  },
  {"$replaceRoot":{"newRoot":"$doc"}}
]

example here - https://mongoplayground.net/p/KrL-1s8WCpw

Here is what I would do:

  1. match stage with $in rather than an $or (for readability)
  2. group stage with _id on $uuid, just as you did, but instead of pushing all the data into an array, be more selective. _id is already storing $uuid, so no reason to capture it again. name must always be the same for each $uuid, so take only the first instance. Based on the match, there are only two possibilities for jobUuid, but this will assume it will be either "default" or something else, and that there can be more than one occurrence of the non-"default" jobUuid. Using "$addToSet" instead of pushing to an array in case there are multiple occurrences of the same jobUuid for a user, also, before adding to the set, use a conditional to only add non-"default" jobUuids, using $$REMOVE to avoid inserting a null when the jobUuid is "default".
  3. Finally, "$project" to clean things up. If element 0 of the jobUuids array does not exist (is null), there is no other possibility for this user than for the jobUuid to be "default", so use "$ifNull" to test and set "default" as appropriate. There could be more than 1 jobUuid here, depending if that is allowed in your db/application, up to you to decide how to handle that (take the highest, take the lowest, etc).

Tested at: https://mongoplayground.net/p/e76cVJf0F3o

[{
    "$match": {
        "jobUuid": {
            "$in": [
                "1",
                "default"
            ]
        }
    }
},
{
    "$group": {
        "_id": "$uuid",
        "name": {
            "$first": "$name"
        },
        "jobUuids": {
            "$addToSet": {
                "$cond": {
                    "if": {
                        "$ne": [
                            "$jobUuid",
                            "default"
                        ]
                    },
                    "then": "$jobUuid",
                    "else": "$$REMOVE"
                }
            }
        }
    }
},
{
    "$project": {
        "_id": 0,
        "uuid": "$_id",
        "name": 1,
        "jobUuid": {
            "$ifNull": [{
                    "$arrayElemAt": [
                        "$jobUuids",
                        0
                    ]
                },
                "default"
            ]
        }
    }
}]

I was able to solve this problem with the following aggregate query,

  • We are first extracting the results matching only the jobUuid provided by the user or the "default" in the match section.

  • Then the results are grouped based on the uuid, using a group stage and we are counting the results as well.

  • Using the conditions in replaceRoot first we are checking the length of the grouped document,

  • If the grouped document length is greater than or equal to 2, we are filtering the document that matches the provided jobUuid.

  • If it's less or equal to the 1, then we are checking if it's matching the default jobUuid and returning it.

The Query is below:

[
    {
      $match: {
        $or: [{ jobUuid:1 },{ jobUuid: 'default'}]
      }
    },
    {
      $group: {
        _id: '$uuid',
        count: {
          $sum: 1
        },
        docs: {
          $push: '$$ROOT'
        }
      }
    },
    {
      $replaceRoot: {
        newRoot: {
          $cond: {
            if: {
              $gte: [
                '$count',
                2
              ]
            },
            then: {
              $arrayElemAt: [
                {
                  $filter: {
                    input: '$docs',
                    as: 'item',
                    cond: {
                      $ne: [
                        '$$item.jobUuid',
                        'default'
                      ]
                    }
                  }
                },
                0
              ]
            },
            else: {
              $arrayElemAt: [
                {
                  $filter: {
                    input: '$docs',
                    as: 'item',
                    cond: {
                      $eq: [
                        '$$item.jobUuid',
                        'default'
                      ]
                    }
                  }
                },
                0
              ]
            }
          }
        }
      }
    }
  ]
Related