当前位置: 首页 > 知识库问答 >
问题:

像在SQL中使用Java lambdas那样分组by和sum对象?

易炳
2023-03-14

我有一个类foo包含以下字段:

ID:int/name;String/TargetCost:BigDecimal/ActualCost:BigDecimal

我得到了该类对象的arraylist。例如:

new Foo(1, "P1", 300, 400), 
new Foo(2, "P2", 600, 400),
new Foo(3, "P3", 30, 20),
new Foo(3, "P3", 70, 20),
new Foo(1, "P1", 360, 40),
new Foo(4, "P4", 320, 200),
new Foo(4, "P4", 500, 900)

我想通过创建“targetcost”和“actualcost”的总和并将“row”分组来转换这些值,例如。

new Foo(1, "P1", 660, 440),
new Foo(2, "P2", 600, 400),
new Foo(3, "P3", 100, 40),
new Foo(4, "P4", 820, 1100)

我现在所写的:

data.stream()
       .???
       .collect(Collectors.groupingBy(PlannedProjectPOJO::getId));

我怎么能那样做?

共有3个答案

简意
2023-03-14

仅仅使用JDK的streamAPI来实现这一点并不像其他答案所显示的那样非常简单。本文将说明如何在Java 8中实现groupby的SQL语义(使用标准聚合函数),并使用Joo,一个为这些用例扩展stream的库。

写:

import static org.jooq.lambda.tuple.Tuple.tuple;

import java.util.List;
import java.util.stream.Collectors;

import org.jooq.lambda.Seq;
import org.jooq.lambda.tuple.Tuple;
// ...

List<Foo> list =

// FROM Foo
Seq.of(
    new Foo(1, "P1", 300, 400),
    new Foo(2, "P2", 600, 400),
    new Foo(3, "P3", 30, 20),
    new Foo(3, "P3", 70, 20),
    new Foo(1, "P1", 360, 40),
    new Foo(4, "P4", 320, 200),
    new Foo(4, "P4", 500, 900))

// GROUP BY f1, f2
.groupBy(
    x -> tuple(x.f1, x.f2),

// SELECT SUM(f3), SUM(f4)
    Tuple.collectors(
        Collectors.summingInt(x -> x.f3),
        Collectors.summingInt(x -> x.f4)
    )
)

// Transform the Map<Tuple2<Integer, String>, Tuple2<Integer, Integer>> type to List<Foo>
.entrySet()
.stream()
.map(e -> new Foo(e.getKey().v1, e.getKey().v2, e.getValue().v1, e.getValue().v2))
.collect(Collectors.toList());

呼叫

System.out.println(list);

然后就会屈服

[Foo [f1=1, f2=P1, f3=660, f4=440],
 Foo [f1=2, f2=P2, f3=600, f4=400], 
 Foo [f1=3, f2=P3, f3=100, f4=40], 
 Foo [f1=4, f2=P4, f3=820, f4=1100]]
倪阳飇
2023-03-14

以下是一种可能的方法:

public class Test {
    private static class Foo {
        public int id, targetCost, actualCost;
        public String ref;

        public Foo(int id, String ref, int targetCost, int actualCost) {
            this.id = id;
            this.targetCost = targetCost;
            this.actualCost = actualCost;
            this.ref = ref;
        }

        @Override
        public String toString() {
            return String.format("Foo(%d,%s,%d,%d)",id,ref,targetCost,actualCost);
        }
    }

    public static void main(String[] args) {
        List<Foo> list = Arrays.asList(
            new Foo(1, "P1", 300, 400), 
            new Foo(2, "P2", 600, 400),
            new Foo(3, "P3", 30, 20),
            new Foo(3, "P3", 70, 20),
            new Foo(1, "P1", 360, 40),
            new Foo(4, "P4", 320, 200),
            new Foo(4, "P4", 500, 900));

        List<Foo> transform = list.stream()
            .collect(Collectors.groupingBy(foo -> foo.id))
            .entrySet().stream()
            .map(e -> e.getValue().stream()
                .reduce((f1,f2) -> new Foo(f1.id,f1.ref,f1.targetCost + f2.targetCost,f1.actualCost + f2.actualCost)))
                .map(f -> f.get())
                .collect(Collectors.toList());
        System.out.println(transform);
    }
}

输出:

[Foo(1,P1,660,440), Foo(2,P2,600,400), Foo(3,P3,100,40), Foo(4,P4,820,1100)]
经骁
2023-03-14

使用collectors.groupingby是正确的方法,但不是使用将为每个组创建所有项列表的单参数版本,而是使用两个参数版本,该版本采用另一个collector来确定如何聚合每个组的元素。

当您想要聚合元素的单个属性或只计算每组元素的数量时,这会特别流畅:

>

  • 计数:

    list.stream()
      .collect(Collectors.groupingBy(foo -> foo.id, Collectors.counting()))
      .forEach((id,count)->System.out.println(id+"\t"+count));
    

    总结一个属性:

    list.stream()
      .collect(Collectors.groupingBy(foo -> foo.id,
                                        Collectors.summingInt(foo->foo.targetCost)))
      .forEach((id,sumTargetCost)->System.out.println(id+"\t"+sumTargetCost));
    

    在您的情况下,当您希望聚合多个属性时,指定自定义缩减操作(如本答案中建议的)是正确的方法,但是,您可以在分组操作期间执行缩减权限,因此在执行缩减之前不需要将整个数据收集到map<…,List>:

    (我假设您现在使用导入静态java.util.Stream.Collectors.*;…)

    list.stream().collect(groupingBy(foo -> foo.id, collectingAndThen(reducing(
      (a,b)-> new Foo(a.id, a.ref, a.targetCost+b.targetCost, a.actualCost+b.actualCost)),
          Optional::get)))
      .forEach((id,foo)->System.out.println(foo));
    

    为了完整起见,这里给出了一个超出问题范围的问题的解决方案:如果要分组多列/属性,该怎么办?

    程序员首先想到的是使用groupingby提取流元素的属性,并创建/返回一个新的键对象。但这需要为关键属性提供一个适当的holder类(而且Java没有通用的元组类)。

    但还有一个选择。通过使用groupingby的三参数形式,我们可以为实际的map实现指定一个供应商,它将确定密钥相等性。通过使用带有比较器的排序映射来比较多个属性,我们不需要额外的类就可以获得所需的行为。我们只需注意不要使用比较器忽略的关键实例中的属性,因为它们将具有任意值:

    list.stream().collect(groupingBy(Function.identity(),
      ()->new TreeMap<>(
        // we are effectively grouping by [id, actualCost]
        Comparator.<Foo,Integer>comparing(foo->foo.id).thenComparing(foo->foo.actualCost)
      ), // and aggregating/ summing targetCost
      Collectors.summingInt(foo->foo.targetCost)))
    .forEach((group,targetCostSum) ->
        // take the id and actualCost from the group and actualCost from aggregation
        System.out.println(group.id+"\t"+group.actualCost+"\t"+targetCostSum));
    

  •  类似资料:
    • 问题内容: 我想使用此查询中的值进行更新,但这意味着它返回多个值。 消息512,级别16,状态1,行1 子查询返回的值大于1。当子查询遵循=,!=,<,<=,>,> =或将子查询用作表达式时,不允许这样做。 它如何返回不止一列? 问题答案: 如果此查询返回所需的信息: 然后,这可能是您想要的UPDATE查询: 我必须同意戈登的观点,您的分组似乎很奇怪。我不确定我是否正确(这就是为什么我强烈建议您先

    • 问题内容: 我想获取满足一定条件的组数。用SQL术语来说,我想在Elasticsearch中执行以下操作。 到目前为止,我可以通过术语聚合将senderResellerId分组。但是,当我应用过滤器时,它无法按预期工作。 弹性请求 实际反应 从上面的响应中可以看到,它正在返回代理商,但是 reseller_sale 聚合结果为零。 更多细节在这里。 问题答案: 实现类似HAVING的行为 您可以使

    • 问题内容: 我有这些结果: 与表类似: 这些按人分组。现在,我希望添加一列,其中包含根据每个人的总和计算得出的每个人的百分比。 例如:总和为300,因此我需要这样的结果: 我在网上查看了代码,并提出了这样的解决方案: 但是我不确定如何将交叉联接以及已经存在的分组/求和部分合并到联接中。或者这是否完全正确。 任何帮助将不胜感激- SQL小提琴http://sqlfiddle.com/#!9/80f9

    • 问题内容: 我有一个表(Transactions),其中包含包含Account_name和交易金额的记录。我想计算每个帐户的所有交易的总数,这些交易以“私人”开头且交易金额>1000。我想按名称降序排列这些帐户。 因此,SQL请求将如下所示: 我将如何在Swift中使用Core-DATA做到这一点。 谢谢 问题答案: 请记住,CoreData不是关系数据库,因此您应该考虑的实体不是“表”,而对象不

    • 15.6 像Java对象那样操作JDBC org.springframework.jdbc.object包能让你更加面向对象化的访问数据库。举个例子,用户可以执行查询并返回一个list, 该list作为一个结果集将把从数据库中取出的列数据映射到业务对象的属性上。你也可以执行存储过程,包括更新、删除、插入语句。 备注:许多Spring的开发者认为下面将描述的各种RDBMS操作类(StoredProc

    • 问题内容: 我在一个表中有类别 表 (cat_name,amount); 如何获得每个分组的总和 问题答案: