Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to compare two object elements in a mongodb array

{
  "customerSchemes": [
    {
      "name": "A",
      "startDate": some date in valid date format
    },
    {
      "name": "B",
      "startDate": some date in valid date format.
    }
  ]
}

I am trying to figure out all documents where scheme A started before scheme B. Please note that the scheme Array is not in ascending order of startDate. Plan B can have an earlier date as compared to plan A. I believe unwind operator could be of some use here but not sure how to progress with next steps.

like image 485
JavaDeveloper Avatar asked Mar 08 '21 21:03

JavaDeveloper


People also ask

How does MongoDB compare array of objects?

You can use aggregation array operator like $filter or $reduce to compare array elements and return the result. These array operators work like iterators (like iterating in a for-loop on a JavaScript or Java array field). See the examples, for the two operators in the MongoDB Manual.

What does $$ mean in MongoDB?

"$" is equivalent to "$$CURRENT." where the CURRENT is a system variable that defaults to the root of the current object in the most stages, unless stated otherwise in specific stages. CURRENT can be rebound. And, "Unless documented otherwise, all stages start with CURRENT the same as ROOT ."

What is array data type in MongoDB?

Documents in a MongoDB database commonly include fields defined with the Array data type. A field configured with this type can contain zero or more elements that together form a list—or array—of values. An array is simply a list of values, and an array value can take many forms.

How to use $elemmatch in MongoDB?

How to use $elemMatch. The official MongoDB documentation for version 4.0 states: The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria.

Why do we need to be careful with MongoDB arrays?

We need to be careful with how we build the queries when dealing with MongoDB arrays. Oftentimes, we make the mistake in the query and fetch documents we are not looking for.

Why can't I do a simple query in MongoDB?

You can not do the query you want in a simple way in mongo because mongo does not support searching or updating based on the element in the collection. So even such simple document as {a : 1, b : 1} and find the document where a = b is impossible without $where clause.


Video Answer


3 Answers

aggregate():

  • $filter to filter name: "A" from customerSchemes
  • $arrayElemAt to get first element from filtered result from above step
  • same steps like above for name: "B"
  • $let to declare variables for "A" in a and "B" in b
  • in to check condition from above variables if a's startDate is greater than b's startDate then return true otherwise false
  • $expr expression match with $eq to match above process, if its true then return document
db.collection.aggregate([
  {
    $match: {
      $expr: {
        $eq: [
          {
            $let: {
              vars: {
                a: {
                  $arrayElemAt: [
                    {
                      $filter: {
                        input: "$customerSchemes",
                        cond: { $eq: ["$$this.name", "A"] }
                      }
                    },
                    0
                  ]
                },
                b: {
                  $arrayElemAt: [
                    {
                      $filter: {
                        input: "$customerSchemes",
                        cond: { $eq: ["$$this.name", "B" ] }
                      }
                    },
                    0
                  ]
                }
              },
              in: { $gt: ["$$a.startDate", "$$b.startDate"] }
            }
          },
          true
        ]
      }
    }
  }
])

Playground


find():

You can use above match stage expression condition in find() query as well without any aggregation pipeline,

Playground

latest support hint: if you are using latest(4.4) MongoDB version then you can use $first instead of $arrayElemAt, see Playground

like image 197
turivishal Avatar answered Oct 17 '22 04:10

turivishal


You could use $unwind array and format the elements for comparison effectively transforming into key value pair. This assumes you only have two array values so I didn't know apply any filtering.

Something like

db.colname.aggregate(
[
  {"$unwind":"$customerSchemes"},
  {"$group":{
    "_id":"$_id",
    "data":{"$push":"$$ROOT"},
    "fields":{
      "$mergeObjects":{
        "$arrayToObject":[[["$customerSchemes.name","$customerSchemes.startDate"]]]
      }
    }
  }},
  {"$match":{"$expr":{"$lt":["$fields.A","$fields.B"]}}},
  {"$project":{"_id":0,"data":1}}
])

Working example here - https://mongoplayground.net/p/mSmAXHm0-o-

Using $reduce

db.colname.aggregate(
[
  {"$addFields":{
    "fields":{
      "$reduce":{
        "input":"$customerSchemes",
        "initialValue":{},
        "in":{
          "$mergeObjects":[
            {"$arrayToObject":[[["$$this.name","$$this.startDate"]]]},
            "$$value"]
        }
      }
    }
  }},
  {"$match":{"$expr":{"$lt":["$fields.A","$fields.B"]}}},
  {"$project":{"fields":0}}
])

Working example here - https://mongoplayground.net/p/WNxbScI9N9b

like image 37
s7vr Avatar answered Oct 17 '22 03:10

s7vr


So the idea is

  1. Sort the customerSchemes array by startDate.
  2. Pick the first item from the sorted list.
  3. Include it only if the customerSchemes.name is A.

Try this query:

db.collection.aggregate([
    { $unwind: "$customerSchemes" },
    {
        $sort: { "customerSchemes.startDate": 1 }
    },
    {
        $group: {
            _id: "$_id",
            customerSchemes: { $push: "$customerSchemes" }
        }
    },
    {
        $match: {
            $expr: {
                $eq: [{ $first: "$customerSchemes.name" }, "A"]
            }
        }
    }
]);

Output:

/* 1 createdAt:3/12/2021, 6:40:42 PM*/
{
    "_id" : ObjectId("604b685232a8d433d8ede6c4"),
    "customerSchemes" : [
        {
            "name" : "A",
            "startDate" : ISODate("2021-03-01T00:00:00.000+05:30")
        },
        {
            "name" : "B",
            "startDate" : ISODate("2021-03-02T00:00:00.000+05:30")
        }
    ]
},

/* 2 createdAt:3/12/2021, 6:40:42 PM*/
{
    "_id" : ObjectId("604b685232a8d433d8ede6c6"),
    "customerSchemes" : [
        {
            "name" : "A",
            "startDate" : ISODate("2021-03-01T00:00:00.000+05:30")
        },
        {
            "name" : "B",
            "startDate" : ISODate("2021-03-05T00:00:00.000+05:30")
        }
    ]
}

Test data:

/* 1 createdAt:3/12/2021, 6:40:42 PM*/
{
    "_id" : ObjectId("604b685232a8d433d8ede6c4"),
    "customerSchemes" : [
        {
            "name" : "A",
            "startDate" : ISODate("2021-03-01T00:00:00.000+05:30")
        },
        {
            "name" : "B",
            "startDate" : ISODate("2021-03-02T00:00:00.000+05:30")
        }
    ]
},

/* 2 createdAt:3/12/2021, 6:40:42 PM*/
{
    "_id" : ObjectId("604b685232a8d433d8ede6c5"),
    "customerSchemes" : [
        {
            "name" : "A",
            "startDate" : ISODate("2021-03-03T00:00:00.000+05:30")
        },
        {
            "name" : "B",
            "startDate" : ISODate("2021-03-02T00:00:00.000+05:30")
        }
    ]
},

/* 3 createdAt:3/12/2021, 6:40:42 PM*/
{
    "_id" : ObjectId("604b685232a8d433d8ede6c6"),
    "customerSchemes" : [
        {
            "name" : "B",
            "startDate" : ISODate("2021-03-05T00:00:00.000+05:30")
        },
        {
            "name" : "A",
            "startDate" : ISODate("2021-03-01T00:00:00.000+05:30")
        }
    ]
}
like image 2
Dheemanth Bhat Avatar answered Oct 17 '22 02:10

Dheemanth Bhat