Criteria/aggregation: search for all complete docu

2020-03-31 02:03发布

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".

1条回答
在下西门庆
2楼-- · 2020-03-31 02:42

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);
查看更多
登录 后发表回答