4

I had a working mongoose query using Aggregate where I filter data based on a value inside an array of objects and am running into trouble due to change in requirements.

This is my how my data looks like in MongoDB (it has been filled with dummy data)

{
"_id" : ObjectId("5ef44528af5a1a2a641cd52b"),
"active" : true,
"name" : "CompanyA",
"contacts" : [ 
    {
        "secondary_emails" : [],
        "_id" : ObjectId("5f19727495f4da29403923e3"),
        "email" : "contact@gmail.com",
        "designation" : "VP Cloud & Security",
        "address" : "",
        "name" : "Ron"
    }
],
"services" : [ 
    {
        "active" : true,
        "_id" : ObjectId("5ef44528af5a1a2a641cd52d"),
        "name" : "Company Management",
        "title" : "Company Helpline",
        "description" : "Offering voice, meetings, collaboration and contact center all on one cloud platform.",
        "categories" : [ 
            "SD-WAN", 
            "Security and Compliance"
        ],
        "sub_categories" : [ 
            "Solution"
        ],
    },
    {
      "active" : true,
      "_id" : ObjectId("5ef44528af5a1a2a641cd52c"),
      "name" : "Company HR",
      "title" : "Human Resources",
      "description" : "Offering HR Services to all",
      "categories" : [ 
          "HR", "Company"
      ],
      "sub_categories" : [ 
          "Solution"
      ],
  }
]}

Objective: Filter services on the basis of the category provided by the user (assuming its "SD-WAN"), before the category was singular and now its an array of values, since one service can belong to multiple categories

Previous Solution My mongoose query, that I was using for singular category values was as below:

db.Collection("suppliers").aggregate([
  {
    $project: {
      services: {
        $filter: {
          input: "$services",
          as: "services",
          cond: { $eq: ["$$services.category", "SD-WAN" ] },
        },
      },
    },
  },
  { $match: { services: { $exists: true, $not: { $size: 0 } } } },
]);

I am having trouble trying to search for SD-WAN inside $$services.categories which is an array of values. Is there any way to update the above query to search for the value in services.categories array using the $eq pipeline operator or some other solution perhaps.

Expected Result

"_id" : ObjectId("5ef44528af5a1a2a641cd52b"),
"services" : [ 
    {
        "active" : true,
        "_id" : ObjectId("5ef44528af5a1a2a641cd52d"),
        "name" : "Company Management",
        "title" : "Company Helpline",
        "description" : "Offering voice, meetings, collaboration and contact center all on one cloud platform.",
        "categories" : [ 
            "SD-WAN", 
            "Security and Compliance"
        ],
        "sub_categories" : [ 
            "Solution"
        ],
    }
]

If anyone can help me out on this, it would be really appreciated. If more information is required, please ask. Thank you.

aieyan
  • 123
  • 4
  • 7

2 Answers2

2

Here what you looking for.

[
  {
    $unwind: "$services"
  },
  {
    $match: {
      "services.categories": "SD-WAN"
    }
  },
  {
    $group: {
      _id: "$_id",
      active: {
        $first: "$active"
      },
      name: {
        $first: "$name"
      },
      contacts: {
        $first: "$contacts"
      },
      services: {
        $addToSet: "$services"
      }
    }
  }
]
  1. use $unwind for flatten the array
  2. use $match to filter
  3. use $group to group it back.

We flatten services only. That why I use $addToSet

Working Mongo playground

varman
  • 8,704
  • 5
  • 19
  • 53
  • Thank you for the quick response, this resolved my problem perfectly! – aieyan Jul 30 '20 at 08:18
  • what $first do ? – Indraraj26 Jul 30 '20 at 08:22
  • 1
    When we unwind, there may be duplicates as much the size of array("Services"). So when we group it, we just need the first object since others are duplicates https://docs.mongodb.com/manual/reference/operator/aggregation/first/ – varman Jul 30 '20 at 08:28
0

Here is another way without using aggregate Playground link : https://mongoplayground.net/p/jmLghGBXb7f

db.collection.find({
  "services": {
    $elemMatch: {
      categories: {
        $regex: "SD-WAN",
        $options: "i"
      }
    }
  }
},
{
  "services": {
    $elemMatch: {
      categories: {
        $regex: "SD-WAN",
        $options: "i"
      }
    }
  }
})

output:

[
  {
    "_id": ObjectId("5ef44528af5a1a2a641cd52b"),
    "services": [
      {
        "_id": ObjectId("5ef44528af5a1a2a641cd52d"),
        "active": true,
        "categories": [
          "SD-WAN",
          "Security and Compliance"
        ],
        "description": "Offering voice, meetings, collaboration and contact center all on one cloud platform.",
        "name": "Company Management",
        "sub_categories": [
          "Solution"
        ],
        "title": "Company Helpline"
      }
    ]
  }
]
Indraraj26
  • 1,726
  • 1
  • 14
  • 29
  • This query works partly as it will only return the first service it finds with the category `SD-WAN`, there can be multiple services with the same category. The query should filter the services that have that category. Is there any way to do that using find? – aieyan Jul 30 '20 at 09:00