【问题标题】:Group by field, sort and get the first (or last, whatever) items of the group in MongoDB (with Spring Data)按字段分组,排序并获取 MongoDB 中组的第一个(或最后一个)项目(使用 Spring Data)
【发布时间】:2020-08-12 11:52:10
【问题描述】:

我有以下实体(getter、setter 和构造函数省略)

public class Event {

    @Id
    private String id;

    private String internalUuid;

    private EventType eventType;
}

EventType 是一个包含任意事件类型的枚举:

public enum EventType {
    ACCEPTED,
    PROCESSED,
    DELIVERED;
}

我的问题是我有一个包含很多事件的表,其中一些具有相同的 internalUuid 但不同的状态。我需要获取一个事件列表,其中每个事件都代表“最新”状态(按 EventType 排序就足够了)。目前,我只是获取所有内容,在代码中分组以分隔列表,按 EventType 对列表进行排序,然后使用每个列表的第一个元素创建一个新列表。

示例如下。

表格中的数据:

{ "id": "1", "internalUuid": "1", "eventType": "ACCEPTED" },
{ "id": "2", "internalUuid": "1", "eventType": "PROCESSED" },
{ "id": "3", "internalUuid": "1", "eventType": "DELIVERED" },
{ "id": "4", "internalUuid": "2", "eventType": "ACCEPTED" },
{ "id": "5", "internalUuid": "2", "eventType": "PROCESSED" },
{ "id": "6", "internalUuid": "3", "eventType": "ACCEPTED" }

查询的输出(任何顺序都可以):

[
    { "id": "3", "internalUuid": "1", "eventType": "DELIVERED" },
    { "id": "5", "internalUuid": "2", "eventType": "PROCESSED" },
    { "id": "6", "internalUuid": "3", "eventType": "ACCEPTED" }
]

不保证“更高”的身份也有“更高”的 ID。

如果不手动完成整个过程,我该如何做到这一点?我真的不知道如何开始,因为我对 MongoDB 很陌生,但在 Google 上没有找到任何对我有帮助的东西。我正在使用 Spring Boot 和 Spring Data。

谢谢!

【问题讨论】:

  • 您能发布您的 EventType 和示例数据以及预期输出吗
  • 我不使用 Java,但您似乎需要聚合,首先使用任何 $match 阶段,然后是 $sort 阶段以按 eventType 排序,然后是 $group 阶段使用internalUuid 作为_id$first$last 捕获$$ROOT

标签: spring mongodb spring-boot spring-data


【解决方案1】:

好的,我想我已经弄清楚了(感谢Joe's 评论)。我不是 100% 确定代码是否正确,但它似乎可以满足我的要求。我愿意改进。

(我必须在 Event 和 EventType 中添加一个优先级字段,因为按 eventType 排序显然会对枚举名称进行基于字符串(字母)的排序):

private List<Event> findCandidates() {
    // First, 'match' so that all documents are found
    final MatchOperation getAll = Aggregation.match(new Criteria("_id").ne(null));

    // Then sort by priority
    final SortOperation sort = Aggregation.sort(Sort.by(Sort.Direction.DESC, "priority"));

    // After that, group by internalUuid and make sure to also push the full event to not lose it for the next step
    final GroupOperation groupByUuid = Aggregation.group("internalUuid").push("$$ROOT").as("events");
    
    // Get the first element of each sorted and grouped list (I'm not fully sure what the 'internalUuid' parameter does here and if I could change that)
    final ProjectionOperation getFirst = Aggregation.project("internalUuid").and("events").arrayElementAt(0).as("event");

    // We're nearly done! Only thing left to do is to map to our Event to have a usable List of Event in .getMappedResults()
    final ProjectionOperation map = Aggregation.project("internalUuid")
            .and("event._id").as("_id")
            .and("event.internalUuid").as("internalUuid")
            .and("event.eventType").as("eventType")
            .and("event.priority").as("priority");

    final Aggregation aggregation = Aggregation.newAggregation(getAll, sort, groupByUuid, getFirst, map);

    final AggregationResults<InvoiceEvent> aggregationResults =
            mongoTemplate.aggregateAndReturn(InvoiceEvent.class).by(aggregation).all();

    return aggregationResults.getMappedResults();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-22
    • 2014-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多