1

I have document structure like this -

{
"_id" : "11223344",
"orderId" : "00001041",
"key" : "56h68ab4c876dbe1cd0b1ee"
"status" : [
    {
        "updatedTimeStamp" : ISODate("2017-06-01T16:05:42.737Z"),
        "orderStatus" : "INITIATED"
    },
    {
        "updatedTimeStamp" : ISODate("2017-06-01T16:05:42.737Z"),
        "orderStatus" : "CONFIRM_PAYMENT"
    },
    {
        "updatedTimeStamp" : ISODate("2017-06-01T16:07:36.797Z"),
        "orderStatus" : "RESTAURENT_CONFIRMATION_PENDING"
    },
    {
        "updatedTimeStamp" : ISODate("2017-06-01T16:20:36.798Z"),
        "orderStatus" : "ORDER_CONFIRMED"
    },
    {
        "updatedTimeStamp" : ISODate("2017-06-01T16:51:27.562Z"),
        "orderStatus" : "PREPARED"
    },
    {
        "updatedTimeStamp" : ISODate("2017-06-01T17:31:27.562Z"),
        "orderStatus" : "DISPATCHED"
    }
],
"customerDetails" : {
    "firstName" : "XXXX",
    "lastName" : "XXXXX",
    "emailId" : "xxxx_xxxx@gmail.com",
    "phoneNumber" : "XXXXXXXXX"
},
"amountPaid" : "250.0",
"orderDetails" : {blah... blah... blah...}

}

In this orders collection, I want to search for all documents for given status x (say CONFIRM_PAYMENT). So it is find by max value of "updatedTimeStamp" in array of documents "status" and if that max array embedded document is equal to status "CONFIRM_PAYMENT", then whole document should be included in final result.

Can we solve it with Criteria API?

There is similar kind of question - MongoDB find by max value in array of documents . But I want to do it with either spring criteria or aggregate api. Below is the query I made by help of above mentioned question. This query is giving all Ids of documents that I want. I am interested in complete documents.

db.order.aggregate([
  {$match: {key:"56h68ab4c876dbe1cd0b1ee"}},
  {$unwind: "$status"},
  {$project:{Id:"$_id",status:"$status.orderStatus", updatedTimeStamp:"$status.updatedTimeStamp"} },
  {$sort:{updatedTimeStamp:1} },
  {$group: {_id: "$Id", LatestStatus:{$last: "$status"}, updatedTimeStamp:{$last: "$updatedTimeStamp"} } },
  {$match:{"LatestStatus":"CONFIRM_PAYMENT"}},
 {$project:{Id:"$Id"}}
])

Can someone help to solve this with either criteria or aggregate API? *There is one other filter in this query that is "key".

doga
  • 471
  • 2
  • 9
  • 20

1 Answers1

2

You can use $expr ( 3.6 mongo version operator ) to use aggregation functions in regular query.

Compare query operators vs aggregation comparison operators.

Use $indexOfArray operator to find the $max updated timestamp element in status array followed by projecting the status value and comparison to check value against the input status.

So shell query is

{
  "key":"56h68ab4c876dbe1cd0b1ee",
  "$expr":{
    "$eq":[
      {"$let":{
        "vars":{
          "status":{"$arrayElemAt":["$status",{"$indexOfArray":["$status.updatedTimeStamp",{"$max":"$status.updatedTimeStamp"}]}]}
        },
        "in":"$$status.orderStatus"
      }},
      "CONFIRM_PAYMENT"]
  }
}

Spring code:

Query query = new BasicQuery("{key:'56h68ab4c876dbe1cd0b1ee','$expr':{'$eq':[{$let:{vars:{status:{'$arrayElemAt':['$status',{'$indexOfArray':['$status.updatedTimeStamp',{'$max':'$status.updatedTimeStamp'}]}]}}, in:'$$status.orderStatus'}},'CONFIRM_PAYMENT']}}");
List<Document> results = mongoTemplate.find(query, Document.class);

Mongo 3.4 complaint version:

Shell Query:

db.order.aggregate([ 
  { "$match" : { "key" : "56h68ab4c876dbe1cd0b1ee"}} , 
  { "$addFields" : {
    "cmpret" : { 
      "$eq" : [ 
        { "$let" : {
          "vars" : { "status" : { "$arrayElemAt" : [ "$status" , { "$indexOfArray" : [ "$status.updatedTimeStamp" , { "$max" : "$status.updatedTimeStamp"}]}]}} ,
          "in" : "$$status.orderStatus"
        }} , 
        "CONFIRM_PAYMENT"
      ]
    }
  }} , 
  { "$match" : { "cmpret" : true}} , 
  { "$project" : { "cmpret" : 0}}
])

Spring Code:

  AggregationOperation match1 = Aggregation.match(Criteria.where("key").is("56h68ab4c876dbe1cd0b1ee"));
        AggregationOperation addFields = new AggregationOperation() {
            @Override
            public Document toDocument(AggregationOperationContext aggregationOperationContext) {
                Document cmpret = Document.parse("{'$eq':[{$let:{vars:{status:{'$arrayElemAt':['$status',{'$indexOfArray':['$status.updatedTimeStamp',{'$max':'$status.updatedTimeStamp'}]}]}}, in:'$$status.orderStatus'}},'CONFIRM_PAYMENT']}}");
                return new Document("$addFields", new Document("cmpret", cmpret));
            }
        };

  AggregationOperation match2 =  Aggregation.match(Criteria.where("cmpret").is(true));

   AggregationOperation dropFields = new AggregationOperation() {
            @Override
            public Document toDocument(AggregationOperationContext aggregationOperationContext) {
                return new Document("$project", new Document("cmpret", 0));
            }
        };
   Aggregation aggregation = Aggregation.newAggregation(
                match1,
                addFields,
                match2,
                dropFields
    );

   AggregationResults<Document> results = mongoTemplate.aggregate(aggregation, "order", Document.class);
s7vr
  • 73,656
  • 11
  • 106
  • 127
  • I'm using 3.4 version... I have a strong feeling that your answer is correct, but neither I can test this nor I can accept this answer. If possible can you please provide answer with aggregate api... Mongo version update on production is huge task and I'm sure that my lead will not accept this... – doga Feb 07 '18 at 17:33
  • 1
    I understand. I've added the 3.4 complaint version. Give it a try. – s7vr Feb 07 '18 at 18:13
  • While executing above code, I am facing this problem - "Can't find a codec for class com.order.commons.enums.OrderStatus". OrderStatus is enum and its one of ordinal value is PAYMENT_CONFIRMED as mentioned above in question. Exception is- org.bson.codecs.configuration.CodecConfigurationException. Do you have solution for this? I am getting this exception at line-mongoTemplate.aggregate(). There are some solutions available online. One is create custom codec class for order status enum, but What if i have many other enums in my documents, I have to create seperate codec for each enum. @Veeram – doga Feb 12 '18 at 19:17
  • I'm not aware of any solution myself. This seems like very particular problem. Do you mind creating a new question ? – s7vr Feb 12 '18 at 19:23