使用数据库中的Java流制作数据透视表

来自数据库行和表的原始数据不能为人类读者提供太多了解。 相反,如果我们对数据执行某种聚合,则人类更有可能看到数据模式在向我们展示之前。 数据透视表是聚合的一种特定形式,我们可以在其中应用排序,求平均值或求和之类的操作,也可以经常对列值进行分组。

在本文中,我将展示如何在不编写SQL的情况下从纯Java中计算数据库的数据透视表。 您可以轻松地重用和修改本文中的示例,以满足您自己的特定需求。

在下面的示例中,我使用了开源Speedment (它是Java Stream ORM)和MySQL的开源Sakila电影数据库内容。 Speedment适用于任何主要的关系数据库类型,例如MySQL,PostgreSQL,Oracle,MariaDB,Microsoft SQL Server,DB2,AS400等。

旋转

我将构造一个MapActor对象,并为每个Actor ,相应的List电影,一个特殊的电影分级的Actor出现在这里是为特定的枢轴如何进入一个例子。 Actor可能看起来像口头上表示:

“约翰·多伊(John Doe)参加了9部评级为'PG-13'的电影和4部评级为'R'的电影”。

我们将计算数据库中所有参与者的枢轴值。 Sakila数据库具有此特定应用程序感兴趣的三个表:

1)“电影”包含所有电影以及如何评价电影(例如“ PG-13”,“ R”等)。2)包含(组成)演员的“演员”(例如“ MICHAEL BOLGER”,“ LAURA BRODY”等)。3)“电影演员”,以多对多的关系将电影和演员联系在一起。

解决方案的第一部分涉及将这三个表连接在一起。 联接是使用Speedment的JoinComponent创建的,可以通过以下方式获得:

// Visit https://github.com/speedment/speedment
// to see how a Speedment app is created. It is easy!
Speedment app = …;

JoinComponent joinComponent = app.getOrThrow(JoinComponent.class);

一旦有了JoinComponent ,就可以开始定义计算关系表所需的Join关系:

Join<Tuple3<FilmActor, Film, Actor>> join = joinComponent
        .from(FilmActorManager.IDENTIFIER)
        .innerJoinOn(Film.FILM_ID).equal(FilmActor.FILM_ID)
        .innerJoinOn(Actor.ACTOR_ID).equal(FilmActor.ACTOR_ID)
        .build(Tuples::of);

build()采用方法引用Tuples::of ,该方法引用将解析为采用三个类型的实体的构造函数。 FilmActorFilmActor ,这将创建一个包含这些特定实体的复合不可变Tuple3 。 元组内置于Speedment中。

有了Join对象,我们现在可以使用从Joi​​n对象获得的标准Java Stream创建数据透视图:

Map<Actor, Map<String, Long>> pivot = join.stream()
    .collect(
        groupingBy(
            // Applies Actor as a first classifier
            Tuple3::get2,
            groupingBy(
                // Applies rating as second level classifier
                tu -> tu.get1().getRating().get(),
                counting() // Counts the elements 
                )
            )
        );

现在已经计算了枢轴Map ,我们可以像这样打印其内容:

// pivot keys: Actor, values: Map<String, Long>
pivot.forEach((k, v) -> { 
    System.out.format(
        "%22s  %5s %n",
        k.getFirstName() + " " + k.getLastName(),
        V
    );
});

这将产生以下输出:

MICHAEL BOLGER  {PG-13=9, R=3, NC-17=6, PG=4, G=8} 
           LAURA BRODY  {PG-13=8, R=3, NC-17=6, PG=6, G=3} 
     CAMERON ZELLWEGER  {PG-13=8, R=2, NC-17=3, PG=15, G=5}
...

任务完成! 在上面的代码中, Tuple3::get2方法将从元组( Actor )中检索第三个元素,而tu.get1()方法将从元组( Film )中检索第二个元素。

Speedment将自动从Java渲染SQL代码,并将结果转换为Java Stream。 如果启用流日志记录,我们可以确切地看到SQL的呈现方式:

SELECT 
    A.`actor_id`,A.`film_id`,A.`last_update`, 
    B.`film_id`,B.`title`,B.`description`,
    B.`release_year`,B.`language_id`,B.`original_language_id`,
    B.`rental_duration`,B.`rental_rate`,B.`length`,
    B.`replacement_cost`,B.`rating`,B.`special_features`,
    B.`last_update`, C.`actor_id`,C.`first_name`,
    C.`last_name`,C.`last_update`
FROM 
    `sakila`.`film_actor` AS A
INNER JOIN 
    `sakila`.`film` AS B ON (B.`film_id` = A.`film_id`) 
INNER JOIN 
    `sakila`.`actor` AS C ON (C.`actor_id` = A.`actor_id`)

加入自定义元组

正如我们在上面的示例中注意到的那样,由于在连接阶段仅将FilmActor对象用于将FilmActor实体链接在一起,因此在Stream中没有实际使用FilmActor对象。 此外,通用Tuple3有一般get0() get1()get2()是没有说他们装的是什么东西的方法。

所有这些都可以通过定义我们自己的自定义“元组”(称为ActorRating如下所示:

private static class ActorRating {
    private final Actor actor;
    private final String rating;

    public ActorRating(FilmActor fa, Film film, Actor actor) {
        // fa is not used. See below why
        this.actor = actor;
        this.rating = film.getRating().get();
    }

    public Actor actor() {
        return actor;
    }

    public String rating() {
        return rating;
    }

}

当使用build()方法build() Join对象时,我们可以提供一个自定义构造函数,该构造函数要应用于数据库的传入实体。 这是我们将要使用的功能,如下所示:

Join<ActorRating> join = joinComponent
    .from(FilmActorManager.IDENTIFIER)
    .innerJoinOn(Film.FILM_ID).equal(FilmActor.FILM_ID)
    .innerJoinOn(Actor.ACTOR_ID).equal(FilmActor.ACTOR_ID)
    .build(ActorRating::new); // Use a custom constructor

Map<Actor, Map<String, Long>> pivot = join.stream()
    .collect(
        groupingBy(
            ActorRating::actor,
            groupingBy(
                ActorRating::rating,
                counting()
            )
         )
    );

在此示例中,我们证明了带有构造函数的类(方法参考ActorRating:new解析为new ActorRating(fa, actor, film) ),该FilmActor函数完全丢弃了链接的FilmActor对象。 该类还为其属性提供了更好的名称,这使代码更具可读性。 使用自定义ActorRating类的解决方案将产生与第一个示例完全相同的输出结果,但使用时看起来要好得多。 我认为在大多数情况下,与使用通用元组相比,编写自定义元组值得付出额外的精力。

使用平行旋转

Speedment的一件很酷的事情是,它开箱即用地支持Stream方法parallel() 。 因此,如果您的服务器具有许多CPU,则在运行数据库查询和联接时可以利用所有这些CPU内核。 这就是并行枢轴的样子:

Map<Actor, Map<String, Long>> pivot = join.stream()
    .parallel()  // Make our Stream parallel
    .collect(
        groupingBy(
            ActorRating::actor,
            groupingBy(
                ActorRating::rating,
                counting()
            )
         )
    );

我们只需添加一行代码即可进行并行聚合。 当我们达到1024个元素时,将启动默认的并行拆分策略。 因此,并行枢转将仅在大于此值的表或联接上进行。 应该注意的是,Sakila数据库仅包含1000部电影,因此我们必须在更大的数据库上运行代码才能真正受益于并行性。

旋转一下吧!

在本文中,我们展示了如何在不编写任何SQL代码的情况下,就可以使用Java从数据库计算数据透视表。 访问GitHub上的Speedment开源以了解更多信息。

《用户指南》中阅读有关其他功能的更多信息。

翻译自: https://www.javacodegeeks.com/2018/05/making-pivot-tables-with-java-streams-from-databases.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
杰笛数据透视表格 Pivot Grid 杰笛数据透视表格利用Java/Swing实现了在Microsoft Excel里面非常著名的数据透视表(PivotTable)功能。这个功能也在很多数据分析和OLAP应用程序里出现过。数据透视表的特点就是帮助您从一堆看似杂乱的数据里找到您想要的趋势或者规律,所以把Pivot Table翻译成数据透视表,倒是很贴切。您可以组织、统计、比较、排序,来找到对您感兴趣的方面有用的信息,就好像透视一样。有了杰笛数据透视表,您就可以把这样一个有用的工具集成到您的Java应用程序里,而不需要借助于第三方应用程序。该产品是建立在杰笛表格基础上的,并且充分利用了杰笛表格提供的各种表格功能。 功能介绍 支持从任何TableModel读取数据。至于TableModel,您可以从很多途径来产生,比如数据库,XML,文件,网络等等都可以。 用户可以用拖放来调整布局 自动计算出统计结果,包括SUM、MAX、MIN、MEAN、VAR、STDDEV、COUNT等等统计,您也可以引进自己的统计结果。 自动对行和列进行分类汇总和全部汇总。 对原始数据分类。 支持表格色彩、字体等等的调节,突出重点。 数据过滤功能。 数据排序功能。 支持衍生域,所谓衍生域就是从其它现有的域算出来的新的域,计算方法可以是一个自定义表达式。 支持保存布局到XML文件。 单独的AggregateTable控件,支持对数据进行分类合并,可以展开折叠。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值