Заполните итоговый счетчик для всех значений статуса - PullRequest
1 голос
/ 24 января 2020

У меня есть Enum следующим образом для всех статусов:

enum Status {    
    ACCEPTED("Accepted"),
    REJECTED("Rejected"),
    FAILED("Failed");

    private final String label;

    Status(final String label) {
        this.label = label;
    }
}

Модель для сопоставления результатов запроса, который я запускаю из базы данных:

public class Summary {    
    private LocalDate date;
    private Status status;
    private int count;    
}

Скажите моя база данных возвращает следующее, исключая любые подсчеты для Rejected для 24-го и Failed для 27-го:

DATE        | status        | count
20-01-24    | Accepted      | 1     
20-01-24    | Failed        | 40        
20-01-27    | Accepted      | 10        
20-01-27    | Rejected      | 15

Как я могу по-прежнему включать сводки для rejected & failed за эти даты вместе с вышеупомянутое?

Я вызываю БД следующим образом:

final List<Summary> statuses = this.dbRepo.getSummaries(someCriteria);

, но этот список не включает Rejected & Failed, так как они не возвращаются БД.

Через Java 8 и потоковую операцию, есть ли способ проверить все доступные значения перечисления для Status и, если в statuses отсутствует один статус для дат, включить его со счетом 0?

То, что я до сих пор пробовал, это получить результаты из БД и поместить их в карту следующим образом:

public Map<LocalDate, List<Summary>> getSummaries(final SomeCriteria someCriteria) {
    return this.namedParameterJdbcTemplate.query(this.QUERY,
            new MapSqlParameterSource()
                    .addValue("date", someCriteria.getFrom())
            ((rs, rowNum) -> Summary.builder()
                                                    .date(rs.getDate("date").toLocalDate())
                                                    .status(valueOfLabel(rs.getString("status")))
                                                    .count(rs.getInt("count"))
                                                    .build())
    ).stream().collect(groupingBy(Summary::getDate));
}

Ответы [ 3 ]

0 голосов
/ 24 января 2020

Это то, что я сделал бы в два шага, в версии картезианского самопсоединения для бедного человека (по предварительно сгруппированным данным):

//select status, date, sum(count) group by status, date
Map<Status, Map<LocalDate, Integer>> existingCounts = 
    summaries.stream()
    .collect(Collectors.groupingBy(
        Summary::getStatus, 
        Collectors.groupingBy(Summary::getDate, 
                              Collectors.summingInt(Summary::getCount))));

List<Summary> result = Arrays.stream(Status.values())
    .flatMap(
        status -> existingCounts.values()
                   .stream().distinct()
                   .flatMap(
                           map -> map.keySet().stream()
                                    .map(date -> new SimpleEntry<>(status, date))))
    .map(e -> new Summary(e.getValue(), e.getKey(),
        existingCounts.containsKey(e.getKey())
                ? (existingCounts.get(e.getKey()).containsKey(e.getValue())
                        ? existingCounts.get(e.getKey()).get(e.getValue()).intValue()
                        : 0)
                : 0))
    .collect(Collectors.toList());

Выше приведен этот список:

[date=2020-01-27, status=ACCEPTED, count=10]
[date=2020-01-24, status=ACCEPTED, count=1]
[date=2020-01-27, status=ACCEPTED, count=10]
[date=2020-01-24, status=ACCEPTED, count=1]
[date=2020-01-27, status=REJECTED, count=15]
[date=2020-01-24, status=REJECTED, count=0]
[date=2020-01-27, status=REJECTED, count=15]
[date=2020-01-24, status=REJECTED, count=0]
[date=2020-01-27, status=FAILED, count=0]
[date=2020-01-24, status=FAILED, count=40]
[date=2020-01-27, status=FAILED, count=0]
[date=2020-01-24, status=FAILED, count=40]
0 голосов
/ 24 января 2020

Выполнение этого за один шаг, возможно, было бы возможно, но я думаю, что это не будет так читабельно! и делая это в несколько степпов, дайте понять ИМО!

на первом шаге: группировка по дате и статусу свойств.

Map<LocalDate, Set<Status>> groupByDateAndStatus = list.stream()
        .collect(Collectors.groupingBy(Summary::getDate,
               Collectors.mapping(Summary::getStatus, Collectors.toSet())));

{2020-01-27 = [ПРИНЯТЬ, ОТКАЗАНО], 2020-01-24 = [НЕУДАЧЕНО, ПРИНЯТО]]

во второй группе шагов резюме с дата и доступный статус .

 Map<LocalDate, List<Summary>> map = list.stream()
            .collect(Collectors.toMap(Summary::getDate,
                summary -> new ArrayList<>(Collections.singletonList(summary)), YOURCLASS::merge));

и на последнем шаге добавьте summary для отсутствующих status.

EnumSet.allOf(Status.class)
        .forEach(status -> groupByDateAndStatus.entrySet()
        .stream()
        .filter(entry -> !entry.getValue().contains(status))
        .forEach(entry -> map.merge(entry.getKey(), 
 new ArrayList<Summary>(Collections.singletonList(new Summary(entry.getKey(), status, 0))),
                                                                           YOURCLASS::merge)));

и метод слияния:

private static List<Summary> merge(List<Summary> l1, List<Summary> l2) {
   l1.addAll(l2);
   return l1; 
}

вывод:

{
  2020-01-27=[
      Summary{date=2020-01-27, status=ACCEPTED, count=10}, 
      Summary{date=2020-01-27, status=REJECTED, count=15}, 
      Summary{date=2020-01-27, status=FAILED, count=0}], 
  2020-01-24=[ 
      Summary{date=2020-01-24, status=ACCEPTED, count=1}, 
      Summary{date=2020-01-24, status=FAILED, count=40}, 
      Summary{date=2020-01-24, status=REJECTED, count=0}
    ]
}

Map<LocalDate, List<Summary>> map = new HashMap<>();
Map<LocalDate, Map<Status, Summary>> lookup = list.stream()
        .collect(Collectors.groupingBy(Summary::getDate,
            Collectors.toMap(Summary::getStatus, Function.identity())));

EnumSet.allOf(Status.class)
        .forEach(status -> lookup
            .forEach((key, value) ->
               map.merge(key, new ArrayList<>(Collections.singletonList(value.getOrDefault(status, new Summary(key, status, 0)))),
                                    YOURCLASS::merge)));
0 голосов
/ 24 января 2020

Вы можете использовать вложенный grouping для создания начального поиска Map

Map<LocalDate, Map<Status, Summary>> lookup = statuses.stream()
        .collect(Collectors.groupingBy(Summary::getDate,
                Collectors.toMap(Summary::getStatus, Function.identity())));

, а затем выполнить оставшуюся задачу, повторяя все значения Status для каждой даты, такие как:

List<Summary> overallResult = lookup.entrySet().stream()
        .flatMap(en -> EnumSet.allOf(Status.class).stream()
                .map(status -> en.getValue().containsKey(status) ?
                        en.getValue().get(status) : Summary.builder()
                                .date(en.getKey()).status(status).count(0).build()))
        .collect(Collectors.toList());

При вводе в вопросе это приведет к:

Summary(date=2020-01-27, status=ACCEPTED, count=10)
Summary(date=2020-01-27, status=REJECTED, count=15)
Summary(date=2020-01-27, status=FAILED, count=0)
Summary(date=2020-01-24, status=ACCEPTED, count=1)
Summary(date=2020-01-24, status=REJECTED, count=0)
Summary(date=2020-01-24, status=FAILED, count=40)
...