Spring MongoDB query documents if days difference is x days
Asked Answered
G

2

14

I have a collection that has two date fields and I am trying to query all records that have a difference of 15 days:

{
   "_id" : "someid",
   "factoryNumber" : 123,
   "factoryName" : "some factory name",
   "visitType" : "audit",
   "personelId" : "somePersonel",
   "lastVisit": ISODate("2018-10-30T00:00:00.000+0000"),
   "acceptedDate" : ISODate("2018-11-16T00:00:00.000+0000")
}

Now in some cases acceptedDate will not be there so i need to evaluate it against the current date. Not complete sure how to write this type of query in spring to obtain the desire result.

    Criteria.where("acceptedDate"). 
(is 15 days past last visit or current date if last visit not present)
Gmur answered 20/12, 2018 at 15:7 Comment(0)
D
11

Starting 3.6 you have to use new operator $expr which allows use of aggregation expressions inside match queries or in regular queries.

You can create the json query and pass it directly as $expr is not supported in spring yet in regular query.

15 days = 15 * 24 * 60 * 60 * 1000 = 1296000000 millis

Query query = new BasicQuery("{'$expr':{'$gte':[{'$subtract':[{'$ifNull':['$acceptedDate',{'$date':" + System.currentTimeMillis() + "}]},'$lastVisit']},1296000000]}}");
List<Document> results = mongoTemplate.find(query, Document.class);

3.4 version

If you like to use spring mongo methods you have to use projection to add new field which holds comparison and followed by match operation and extra projection to drop the comparison field. Unfortunately $addFields is still not supported so you have to use the AggregationOperation to create a new stage manually.

AggregationOperation addFields = new AggregationOperation() {
    @Override
    public Document toDocument(AggregationOperationContext aggregationOperationContext) {
        Document document = new Document("comp", Document.parse("{'$gte':[{'$subtract':[{'$ifNull':['$acceptedDate', {'$date':" + System.currentTimeMillis() + "}]},'$lastVisit']},1296000000]}}"));      
        return new Document("$addFields", document);
    }
};

Aggregation aggregation = Aggregation.newAggregation(
        addFields,
        Aggregation.match(Criteria.where("comp").is(true))
        Aggregation.project().andExclude("comp");
);

List<Document> results = mongoTemplate.aggregate(aggregation, collection name, Document.class).getMappedResults();

3.2 version

AggregationOperation redact = new AggregationOperation() {
    @Override
    public DBObject toDBObject(AggregationOperationContext aggregationOperationContext) {
    Map<String, Object> map = new LinkedHashMap<>();
    map.put("if",  BasicDBObject.parse("{'$gte':[{'$subtract':[{'$ifNull':['$acceptedDate', {'$date':" + System.currentTimeMillis() + "}]},'$lastVisit']},1296000000]}}"));
    map.put("then", "$$KEEP");
    map.put("else", "$$PRUNE");
    return new BasicDBObject("$redact", new BasicDBObject("$cond", map));
};

Aggregation aggregation = Aggregation.newAggregation(redact);

List<FactoryAcceptance> results = mongoTemplate.aggregate(aggregation, FactoryAcceptance.class, FactoryAcceptance.class).getMappedResults();
Ddt answered 22/12, 2018 at 16:44 Comment(8)
the version being used company wide is 3.2 right now. I guess i cant use exprGmur
also when i override AggregationOperation it forces me to use DBObject? as oppose to my collection entity name?Gmur
Updated answer for 3.2 version and spring mongo 1.x jar.Ddt
thank you for that one quick question, if my entity is called FactoryAcceptance, then i would need to map the List<DBObject> to my List<FactoryAcceptance> ? would it have all the fields in the record?Gmur
For some reason i get a json parsing error on the first map.put - BasicDBObject.parse, it looks good to me thoughGmur
#Veeram any ideas why that would be ?Gmur
Updated answer. Please verify.Ddt
Let us continue this discussion in chat.Gmur
H
4

You need to use aggregate pipeline to get the documents

  • $ifNull - to set current date if accepted date is null
  • $addFields - to add fields from, fromDays and toDays to existing document to
  • filter in $redact
  • $redact - match within fields and filter
  • $project - to exclude the fields added in $addFields stage

mongo query

db.t1.aggregate([
    {$addFields : {
        from : {$ifNull : ["$acceptedDate", new Date()]}
    }},
    {$addFields: {
        fromDays : {$sum : [{$multiply : [365, {$year : "$from"}]}, {$dayOfYear : "$from"}]},
        toDays : {$sum : [{$multiply : [365, {$year : "$lastVisit"}]}, {$dayOfYear : "$lastVisit"}]}
    }},
    { $redact: {
        $cond: {
           if: {$lte : [{$subtract : ["$fromDays", "$toDays"]}, 15]},
           then: "$$DESCEND",
           else: "$$PRUNE"
         }
       }
    },
    {$project : {from:0, fromDays:0, toDays:0}}
])

sample collection

> db.t1.find().pretty()
{
        "_id" : "someid",
        "factoryNumber" : 123,
        "factoryName" : "some factory name",
        "visitType" : "audit",
        "personelId" : "somePersonel",
        "lastVisit" : ISODate("2018-10-30T00:00:00Z"),
        "acceptedDate" : ISODate("2018-11-16T00:00:00Z")
}
{
        "_id" : "someotherid",
        "factoryNumber" : 123,
        "factoryName" : "some factory name",
        "visitType" : "audit",
        "personelId" : "somePersonel",
        "lastVisit" : ISODate("2018-10-30T00:00:00Z")
}

result with min 150 days

> db.t1.aggregate([ {$addFields : { from : {$ifNull : ["$acceptedDate", new Date()]} }}, {$addFields: { fromDays : {$sum : [{$multiply : [365, {$year : "$from"}]}, {$dayOfYear : "$from"}]}, toDays : {$sum : [{$multiply : [365, {$year : "$lastVisit"}]}, {$dayOfYear : "$lastVisit"}]} }}, { $redact: {         $cond: {            if: {$lte : [{$subtract : ["$fromDays", "$toDays"]}, 150]},            then: "$$DESCEND",            else: "$$PRUNE"          }        } }, {$project : {from:0, fromDays:0, toDays:0}} ]).pretty()
{
        "_id" : "someid",
        "factoryNumber" : 123,
        "factoryName" : "some factory name",
        "visitType" : "audit",
        "personelId" : "somePersonel",
        "lastVisit" : ISODate("2018-10-30T00:00:00Z"),
        "acceptedDate" : ISODate("2018-11-16T00:00:00Z")
}
{
        "_id" : "someotherid",
        "factoryNumber" : 123,
        "factoryName" : "some factory name",
        "visitType" : "audit",
        "personelId" : "somePersonel",
        "lastVisit" : ISODate("2018-10-30T00:00:00Z")
}
>

translate the mongo aggregate query into spring mongodb query

Hornbeam answered 20/12, 2018 at 16:38 Comment(1)
nice, spring part is what i am concern about the most since that will generate this. +1 for the approach.Gmur

© 2022 - 2024 — McMap. All rights reserved.