Условные / максимальные запросы mongo с использованием Spring mongoTemplate

У меня есть сотни таких записей в коллекции mongodb:

{
    "city" : "London",
    "eventTime": 1582866000,
    "createdTime": 1582900145,
    "eventDetails: {
        ...
    }
}

Это соответствует классу Event

public class Event {
    private String city;
    private long eventTime;
    private long createdTime;
    private EventDetails eventDetails;

    //getters and setters
}

-eventTime (все значения времени указаны в локальном времени эпохи / unix) всегда будет равняться часу
-createdTime используется в качестве контроля версий (больше createdTime, более свежая версия этой информации)
- не используя update / upsert, поскольку я использую для хранения более старой информации

Я хочу иметь возможность запрашивать что-то вроде этого:

1) с учетом отметки времени версии - вернуть список объектов (весь объект) для каждого city, для каждого eventTime, где метка времени наиболее близка к (этаж или келлинг) createdTime
2) то же, что и 1), но для max (createdTime) - для каждого city и каждого eventTime

По сути - будет много дубликатов для каждого city, для каждого eventTime из-за нескольких версий, и мне нужна только указанная мной версия и последняя версия

Я пытаюсь выполнить эти два запроса, используя mongoTemplate в java (spring mongo). Я пробовал возиться с Query, Aggregation, AggregationOperator.Max, но, похоже, ничего не работает

Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("city").is(city)),
Aggregation.match(Criteria.where("eventTime").gte(startTime).lte(endTime)),
Aggregation.group("$$ROOT").max("createdTime").as("createdTime")
);
AggregationResults<Event> groupResults = mongoTemplate.aggregate(aggregation, "collection-name", Event.class);
result = groupResults.getMappedResults();

образцы данных и ожидаемые результаты:


person Prady    schedule 28.02.2020    source источник


Ответы (1)


Максимум

db.collection.aggregate([
  {
    $group: {
      _id: {
        city: "$city",
        eventTime: "$eventTime"
      },
      max: {
        $max: "$createdTime"
      },
      data: {
        $push: "$$ROOT"
      }
    }
  },
  {
    $project: {
      max_events: {
        $filter: {
          input: "$data",
          cond: {
            $eq: [
              "$max",
              "$$this.createdTime"
            ]
          }
        }
      }
    }
  },
  {
    $unwind: "$max_events"
  },
  {
    $replaceRoot: {
      newRoot: "$max_events"
    }
  }
])

MongoPlayground

MongoTemplate

import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

Aggregation agg = Aggregation.newAggregation(
    group("city", "eventTime").max("createdTime").as("max").push("$$ROOT").as("data"),
    project().and(new AggregationExpression() {
        @Override
        public Document toDocument(AggregationOperationContext context) {
            return new Document("$filter",
                    new Document("input", "$data")
                         .append("cond", new Document("$eq", Arrays.asList("$max", "$$this.createdTime"))));
        }
    }).as("max_events"),
    unwind("max_events"),
    replaceRoot("max_events")
);

AggregationResults<Event> events = mongoTemplate.aggregate(agg, mongoTemplate.getCollectionName(Event.class), Event.class);

for(Event ev: events) {
    System.out.println(ev);
}

Ближайший

db.collection.aggregate([
  {
    $group: {
      _id: {
        city: "$city",
        eventTime: "$eventTime"
      },
      data: {
        $push: "$$ROOT"
      }
    }
  },
  {
    $project: {
      closest: {
        $reduce: {
          input: "$data",
          initialValue: {
            $arrayElemAt: [
              "$data",
              0
            ]
          },
          in: {
            $cond: [
              {
                $lt: [
                  {
                    $abs: {
                      $subtract: [
                        "$$this.eventTime",
                        "$$this.createdTime"
                      ]
                    }
                  },
                  {
                    $abs: {
                      $subtract: [
                        "$$value.eventTime",
                        "$$value.createdTime"
                      ]
                    }
                  }
                ]
              },
              "$$this",
              "$$value"
            ]
          }
        }
      }
    }
  },
  {
    $unwind: "$closest"
  },
  {
    $replaceRoot: {
      newRoot: "$closest"
    }
  }
])

MongoPlayground

MongoTemplate

Aggregation agg = Aggregation.newAggregation(
    group("city", "eventTime").push("$$ROOT").as("data"),
    project().and(new AggregationExpression() {
        @Override
        public Document toDocument(AggregationOperationContext context) {
            return new Document("$reduce",
                    new Document("input", "$data")
                     .append("initialValue", new Document("$arrayElemAt", Arrays.asList("$data", 0)))
                     .append("in", new Document("$cond", Arrays.asList(
                            new Document("$lt",  Arrays.asList(
                                new Document("$abs", new Document("$subtract", Arrays.asList("$$this.eventTime", "$$this.createdTime"))), 
                                new Document("$abs", new Document("$subtract", Arrays.asList("$$value.eventTime", "$$value.createdTime"))))), 
                            "$$this",
                            "$$value"))));
        }
    }).as("closest"),
    unwind("closest"),
    replaceRoot("closest")
);

AggregationResults<Event> events = mongoTemplate.aggregate(agg, mongoTemplate.getCollectionName(Event.class), Event.class);

for(Event ev: events) {
    System.out.println(ev);
}
person Valijon    schedule 29.02.2020
comment
Мне нужно выполнить этот запрос, и я думаю, что ошибся в своем объяснении. поэтому между eventTime и createdTime существует связь «один-ко-многим», поэтому у каждого eventTime может быть несколько createdTime, потому что createdTime используется как версия записи. И я хочу вернуть список объектов Event, где для каждого eventTime в предоставленном диапазоне он возвращает max createdTime для соответствующего eventTime и ближайшего createdTime для каждого соответствующего eventTime - person Prady; 02.03.2020
comment
@Prady Post, пожалуйста, образец данных с ожидаемым результатом - person Valijon; 02.03.2020
comment
вот ссылка с образцами данных, ожидаемый результат для MAX и CLOSEST в каждом файле, я добавил ссылку в вопрос - person Prady; 02.03.2020
comment
@Prady проверяет ваши образцы - person Valijon; 02.03.2020
comment
@Prady проверьте, соответствует ли это решение вашим требованиям mongoplayground.net/p/mnnOLV1ZOrf. Мы можем разделить этот запрос на 2 отдельных запроса. - person Valijon; 02.03.2020
comment
Да, это то, что я ищу, большое вам спасибо! Можем ли мы разделить это на два запроса и найти способ сделать это с помощью spring mongoTemplate? Также я попытался запустить это в NoSQL Manager, и это дало мне неизвестное имя этапа конвейера: 'replaceWith' - person Prady; 03.03.2020
comment
Вы можете изменить $replaceWith (начиная с v4.2) на $replaceRoot:{newRoot:"$max_events"} - person Valijon; 03.03.2020
comment
@Prady Готово. Попробуйте выполнить эти запросы - person Valijon; 03.03.2020
comment
работает хорошо, большое спасибо. Вы были невероятно полезны! Не знаю, сколько времени мне понадобилось, чтобы самому понять это. Кроме того, если я хочу ограничить диапазон eventTime, могу ли я просто добавить Aggregation.match(Criteria.where("eventTime").gte(startTime).lte(endTime)) в ваш запрос? - person Prady; 03.03.2020
comment
@Prady да. Вы можете добавить несколько условий для соответствия критериям - person Valijon; 03.03.2020