Reputation: 649
SELECT Count(1) AS total,
'hello' AS filter,
field1 AS field1,
Count(DISTINCT field2) AS total_field2
FROM table
WHERE field = true
AND status = 'ok'
GROUP BY field1
Doubts how to make a map using java8 to store the following result. Map key must be field field1
and map value must be total_field2
field.
That is, I need to group my list using field field1 and count field field2
For the total field I have
myList.stream().collect(Collectors.groupingBy(MyObject::getField1, Collectors.counting()))
// this is just counting the records grouped by field1
My result is correct total_field1: {4=55, 6=31}
For field2, I needed something like this, but it's just giving me a record
myList.stream().filter(distinctByKey(MyObject::getField2))
.collect(Collectors.groupingBy(MyObject::getField1, Collectors.counting()));
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}
Result total_Field2: {4=31}
should return me 2 example records total_Field2: {4=31, 6=31}
Example @Naman
public static <T, A, R> Collector<T, ?, R> filtering(
Predicate<? super T> predicate, Collector<? super T, A, R> downstream) {
BiConsumer<A, ? super T> accumulator = downstream.accumulator();
return Collector.of(downstream.supplier(),
(r, t) -> { if(predicate.test(t)) accumulator.accept(r, t); },
downstream.combiner(), downstream.finisher(),
downstream.characteristics().toArray(new Collector.Characteristics[0]));
}
myList.stream().collect(Collectors.groupingBy(MyObject::getField1, filtering(distinctByKey(MyObject::getField2), Collectors.counting())));
Upvotes: 7
Views: 10748
Reputation: 1254
You can try this:
myList.stream().map(obj -> Pair.of(obj.getField1(), obj.getField2()))
.distinct()
.collect(Collectors.groupingBy(Pair::getLeft, counting()));
Upvotes: 0
Reputation: 31908
An alternate to Deadpool's answer is to count distinctByKey
after groupingBy field1
while mapping to entries and then finally collecting to a Map
as:
Map<String, Long> r = myList.stream()
.collect(Collectors.groupingBy(MyObject::getField1))
.entrySet().stream()
.map(e -> new AbstractMap.SimpleEntry<>(e.getKey(),
e.getValue().stream().filter(distinctByKey(MyObject::getField2)).count()))
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
If you were on Java-9 or above, you could have used Collectors.filtering
as a downstream
with the Predicate
defined using the utility distinctByKey
such as :
Map<String, Long> result = myList.stream()
.collect(Collectors.groupingBy(MyObject::getField1,
Collectors.filtering(distinctByKey(MyObject::getField2),
Collectors.counting())));
Note: The above two approaches are quite different though, the former groups all the list items by one field (field1
) and then within each subgroup finds a distinct count by another specific field(field2
).
On the other hand, the latter groups all the distinct items by the key(field2
) and then groups these by another key(field1
) with counting reduction.
Upvotes: 2
Reputation: 40068
Actually i used Set
to eliminate the duplicates and Collectors.collectingAndThen
to get size
Map<String, Integer> res = list.stream()
.collect(Collectors.groupingBy(MyObject::getField1,
Collectors.mapping(MyObject::getField2,
Collectors.collectingAndThen(Collectors.toSet(), set->set.size()))));
As per suggestion by @Naman you can also use method reference Set::size
Collectors.collectingAndThen(Collectors.toSet(), Set::size))));
Upvotes: 7