聚合

SQL 具有 SUMMINMAXCOUNTAVG 聚合函数。

我们可以在 Ebean 中将这些函数用作动态公式或通过 @Aggregation@Sum 用作属性。

 

动态公式

单个属性

LocalDate maxDate =
  new QOrder()
    .select("max(orderDate)")
    .customer.name.equalTo("Rob")
    .findSingleAttribute();
select max(t0.order_date)
from orders t0
join customer t1 on t1.id = t0.customer_id
where t1.name = ? ; --bind(Rob)

对于仅选择单个属性的聚合查询,返回类型与属性类型匹配。在上述情况下,orderDate 是 LocalDate,因此这是返回的类型。

 

多个属性

当我们指定非聚合属性(以下示例中的status)时,将生成一个 GROUP BY 子句,其中包含所有非聚合属性。

List<Order> orders =
  new QOrder()
  .select("status, max(orderDate)")     // status is non-aggregate
  .customer.name.equalTo("Rob")
  .findList();
select t0.status, max(t0.order_date)
from orders t0
join customer t1 on t1.id = t0.customer_id
where t1.name = ?
group by t0.status  -- group by non-aggregate properties

聚合查询返回的 Bean 是部分填充的。它们不包含 @Id 属性,因此不支持延迟加载或持久化。

上述示例中的订单 Bean 将只加载其statusorderDate 属性。

 

Fetch

聚合查询可以包括 fetchfetchQuery 以加载相关 Bean。

List<Order> orders
  = new QOrder()
  .select("status, max(orderDate)")
  .customer.fetch("name")                                // (1) fetch
  .status.notEqualTo(Order.Status.NEW)
  .findList();
select t0.status, max(t0.order_date), t1.id, t1.name     -- (1) has customer id and name
from orders t0
join customer t1 on t1.id = t0.customer_id
where t0.status <> ?
group by t0.status, t1.id, t1.name                       -- (1) has customer id and name

当我们使用 fetchQuery 时,ORM 查询将作为 2 个 SQL 查询执行。

List<Order> orders
  = new QOrder()
  .select("status, max(orderDate)")
  .customer.fetchQuery("name")                           // (2) fetchQuery ...
  .status.notEqualTo(Order.Status.NEW)
  .findList();
-- Primary query
select t0.status, max(t0.order_date), t0.customer_id     -- (2) has customer id only
from orders t0
where t0.status <> ?
group by t0.status, t0.customer_id
-- Secondary query
select t0.id, t0.name                                    -- (2) customer id and name
from customer t0
where t0.id in (?, ?, ?, ?, ? )

当客户上有许多我们想要加载的属性时,使用 fetchQuery() 会很好(因为这些属性全部进入辅助 SQL 查询,而不是主查询,因此不属于 GROUP BY 的一部分)。

 

@Aggregation

我们可以使用带 @Aggregation 注释的属性对聚合进行建模。这是使用动态公式的替代方法。

@Aggregation 的属性必须明确包含在查询中(它们被视为瞬态)。

示例
@Entity
@Table(name = "orders")
public class Order extends BaseModel {

  ...

  LocalDate orderDate;

  @Aggregation("max(orderDate)")     // aggregation property
  LocalDate maxOrderDate;

  @Aggregation("count(*)")           // aggregation property
  Long totalCount;

添加了 maxOrderDatetotalCount 属性后,我们可以在查询中的 selecthaving 子句中使用它们。

QOrder o = QOrder.alias();

List<Order> orders = new QOrder()
  .select(o.status, o.maxOrderDate, o.totalCount)
  .findList();
select t0.status, max(t0.order_date), count(*)
from orders t0
group by t0.status

Having

对聚合属性的谓词应添加到 Having 子句中。

List<Order> orders = new QOrder()
  .select(o.status, o.maxOrderDate, o.totalCount)
  .status.notEqualTo(Order.Status.COMPLETE)             // (1) where clause - non aggregate properties
  .having()
  .totalCount.greaterThan(1)                            // (2) having clause - aggregate properties
  .findList();
select t0.status, max(t0.order_date), count(*)
from orders t0
where t0.status <> ?                                    // (1)
group by t0.status
having count(*) > ?                                     // (2)

 

聚合 Bean

如果我们只想要 1 或 2 个聚合属性,我们认为将它们添加到现有的实体 Bean 中是可以的。但是,如果我们有许多属性想要聚合,这种方法会“破坏我们的模型,并且看起来很丑”。

我们可以创建 聚合 Bean 来对属性进行建模 - 对总和/分组/聚合视图进行建模。

  • 使用 @View 而不是 @Table
  • 不扩展 Model,因为我们不保存或删除这些
  • 没有 @Id 或 @Version 属性
  • 具有我们想要聚合的属性(总和、最大值等)
  • 具有我们想要分组的属性(日期、状态、ManyToOne)

示例

假设我们有一个名为 MachineUse 的实体 Bean,其中包含

@Entity
@Table(name = "machine_use")
public class MachineUse extends Model {

  @Id
  private long id;

  @ManyToOne(optional = false)
  private DMachine machine;

  private LocalDate date;

  private long distanceKms;     // we want to sum() this ...

  private long timeSecs;        // we want to sum() this ...

  private BigDecimal fuel;      // we want to sum() this ...

  @Version
  private long version;
  ...

我们可以为此创建聚合 Bean,如下所示

@Entity
@View(name = "machine_use")
public class MachineUseAggregate {

  @ManyToOne(optional = false)
  private DMachine machine;              // group by, fetch, fetchQuery on ...

  private LocalDate date;                // group by on ...

  @Aggregation("sum(distanceKms)")
  private Long distanceKms;

  @Aggregation("sum(timeSecs)")
  private Long timeSecs;

  @Aggregation("sum(fuel)")
  private BigDecimal fuel;

  @Aggregation("count(*)")
  private Long count;
  ...

 

@Sum

当我们创建 聚合 Bean 时,我们经常看到我们对属性求和的模式,如下所示

@Aggregation("sum(<property name>)")
Type <property name>;

 

例如,我们看到类似这样的 Bean

@Aggregation("sum(distanceKms)")
BigDecimal distanceKms;

@Aggregation("sum(useSeconds)")
Long useSeconds;

@Aggregation("sum(cost)")
BigDecimal cost;

 

@Sum 是此模式的语法糖,上述内容变为

@Sum
BigDecimal distanceKms;

@Sum
Long useSeconds;

@Sum
BigDecimal cost;