聚合
SQL 具有 SUM
、MIN
、MAX
、COUNT
和 AVG
聚合函数。
我们可以在 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 将只加载其status 和 orderDate 属性。
Fetch
聚合查询可以包括 fetch
或 fetchQuery
以加载相关 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;
添加了 maxOrderDate 和 totalCount 属性后,我们可以在查询中的 select
和 having
子句中使用它们。
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;