集計

SQLには集計関数のSUMMINMAXCOUNTAVGがあります。

動的数式または@Aggregation@Sumを介してプロパティとして、Ebeanでこれらの関数を使用できます。

 

動的数式

単一属性

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

集計クエリによって返されるビーンは部分的に設定されたものです。@Idプロパティが含まれず、したがって遅延読み込みや保存はサポートされません。

上の例のOrderビーンには、statusorderDateプロパティのみがロードされます。

 

Fetch

集計クエリには、関連するビーンをロードするためにfetchまたはfetchQueryを含めることができます。

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 (?, ?, ?, ?, ? )

ロードする必要がある多くのプロパティがcustomerにある場合、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プロパティを追加すると、クエリの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)

 

集計ビーン

1つか2つの集計プロパティのみが必要な場合は、それらを既存のエンティティビーンに追加しても問題ありません。ただし、代わりに多くのプロパティを集計する必要がある場合は、このアプローチはモデルをいじっており、見苦しくなり始めます。

集計ビーンを作成してプロパティをモデル化できます - sum / group by / 集計ビューをモデル化します。

  • @Tableではなく@Viewを使用します。
  • 保存したり削除したりしないので、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;