我如何将来自子查询转换为 Hibernate Criteria 语句

How can i convert a from-subquery into a Hibernate Criteria statement

型号

我有以下三个模型 类。 Class A包含1个B,可以包含0个或多个C。 类 B 和 C 都包含我想在 A 的范围内加起来的金额。

class TableA {
    @Id
    Id id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "table_b_id", nullable = false)
    TableB tableB;

    @OneToMany(mappedBy = "tableA", fetch = FetchType.LAZY)
    Set<TableC> tableCs;
}

class TableB {
    @Id
    Id id;

    @Column(name = "amount_b")
    Long amountB;
}

class TableC {
    @Id
    Id id;

    @Column(name = "amount_c", nullable = false)
    Long amountC;

    @JoinColumn(name = "table_a_id")
    TableA tableA;
}

问题

我的任务是计算两个值的组合 "sum(c.amount) + b.amount" 并查看它们是否在定义的 min/max 区间内,例如[0, 100]。结果是符合此条件的 A 列表。我被要求仅在 Hibernate Criteria 中执行此操作。

为此,我构建了一个 SQL 语句来表达此要求:

select compositeSum from 
(SELECT sum(tableC.amountC) + tableB.amountB as 'compositeSum'
FROM tableA A
left join tableB B on A.b_id = B.id
left join tableC C on C.a_id = A.id) SUBQUERY
where compositeSum between 0 and 100;

尝试

因为有聚合函数"sum()",还有算术运算“+”,我试过用子查询来解决问题。 我得到的最接近的是以下解决方案:

Long min = 0l;
Long max = 100l;

DetachedCriteria subquery = DetachedCriteria.forClass(TableA.class, "inner")
                .createAlias("tableC", "tableC", JoinType.LEFT_OUTER_JOIN)
                .createAlias("tableB", "tableB")
                .setProjection(Projections.sqlProjection("coalesce(sum(amountC), 0) + amountB", new String[] {"compositeSum"}, new Type[] {StandardBasicTypes.LONG}));

Criteria criteria = session().createCriteria(TableA.class, "outer")
                .add(Subqueries.ge(max, subquery))
                .add(Subqueries.le(min, subquery));

问题

问题是这会转化为 where 子句中使用的子查询。

select * from A this_ 
where ? >= 
(select sum(amountC) + amountB from table_A tableA_ 
inner join table_B tableB2_ on tableA_.table_b_id=tableB2_.id 
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id) 
and ? <= 
(select sum(amountC) + amountB from table_A tableA_ 
inner join tableB tableB2_ on tableA_.table_B_id=tableB2_.id 
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id)

我想构建一个可以提供给主查询的 from 子句的子查询。相反,我得到了一个提供给 where 子句的子查询。这不是我想要的,结果很奇怪。

有谁知道是否可以将子查询提供给 Hibernate Criteria 中的 from 子句?非常感谢!

解决方案

(更新)

显然,在指向外部查询的内部查询中添加一个额外的过滤器解决了这个问题。要从子查询中引用父查询实体,请使用魔术关键字 "this".

Long min = 0l;
Long max = 100l;

DetachedCriteria subquery = DetachedCriteria.forClass(TableA.class, "inner")
                .createAlias("tableC", "tableC", JoinType.LEFT_OUTER_JOIN)
                .createAlias("tableB", "tableB")
                .setProjection(Projections.sqlProjection("coalesce(sum(amountC), 0) + amountB", new String[] {"compositeSum"}, new Type[] {StandardBasicTypes.LONG}))
                .add(Restrictions.eqProperty("id", "this.id"));

Criteria criteria = session().createCriteria(TableA.class, "outer")
                .setProjection(Projections.property("id"))
                .add(Subqueries.ge(max, subquery))
                .add(Subqueries.le(min, subquery));

添加额外过滤器背后的逻辑是 min/max 是一个标量,您希望子查询 return 另一个标量而不是列表进行比较,否则导致不稳定的行为。

这就变成了一个相关的子查询:

select * from A this_ 
where ? >= 
(select sum(amountC) + amountB from table_A tableA_ 
inner join table_B tableB2_ on tableA_.table_b_id=tableB2_.id 
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id 
where tableA_.id = this_.id) 
and ? <= 
(select sum(amountC) + amountB from table_A tableA_ 
inner join tableB tableB2_ on tableA_.table_B_id=tableB2_.id 
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id
where tableA_.id = this_.id)

此解决方案是一种不同类型的子查询,由于您要执行两次子查询,因此可能会慢一些,但它可以完成工作。

Having 从句

请注意,SQL 解决方案也可以使用 having 子句。 在下面,您可以找到将上述内容转换为 having 子句的情况。 唯一的问题是 Hibernate Criteria 不支持 having-clauses。

SELECT coalesce(sum(amountC),0) + amountB as 'compositeSum'
FROM table_A tableA
left join table_B tableB on tableA.table_b_id = tableB.id
left join table_C tableC on tableC.table_a_id = tableA.id

group by m.id
having compositeSum between 0 and 100

您不需要子查询。像下面这样简化您的查询:

SELECT sum(tableC.amountC) + tableB.amountB as 'compositeSum'
FROM tableA A
left join tableB B on A.b_id = B.id
left join tableC C on C.a_id = A.id
where (sum(tableC.amountC) + tableB.amountB) between 0 and 100;

现在相应地更改您的休眠代码。

显然,在指向外部查询的内部查询中添加一个额外的过滤器解决了这个问题。要从子查询中引用父查询实体,请使用魔术关键字 "this".

Long min = 0l;
Long max = 100l;

DetachedCriteria subquery = DetachedCriteria.forClass(TableA.class, "inner")
                .createAlias("tableC", "tableC", JoinType.LEFT_OUTER_JOIN)
                .createAlias("tableB", "tableB")
                .setProjection(Projections.sqlProjection("coalesce(sum(amountC), 0) + amountB", new String[] {"compositeSum"}, new Type[] {StandardBasicTypes.LONG}))
                .add(Restrictions.eqProperty("id", "this.id"));

Criteria criteria = session().createCriteria(TableA.class, "outer")
                .setProjection(Projections.property("id"))
                .add(Subqueries.ge(max, subquery))
                .add(Subqueries.le(min, subquery));

添加额外过滤器背后的逻辑是 min/max 是一个标量,您希望子查询 return 另一个标量而不是列表进行比较,否则导致不稳定的行为。

这就变成了一个相关的子查询:

select * from A this_ 
where ? >= 
(select sum(amountC) + amountB from table_A tableA_ 
inner join table_B tableB2_ on tableA_.table_b_id=tableB2_.id 
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id 
where tableA_.id = this_.id) 
and ? <= 
(select sum(amountC) + amountB from table_A tableA_ 
inner join tableB tableB2_ on tableA_.table_B_id=tableB2_.id 
inner join table_C tableC1_ on tableA_.id=tableC1_.table_a_id
where tableA_.id = this_.id)

Having-clause

请注意,SQL 解决方案也可以使用 having-clause。 在下面,您可以找到上面的转换为 having-clause。 唯一的问题是 Hibernate Criteria 不支持 having-clauses.

SELECT coalesce(sum(amountC),0) + amountB as 'compositeSum'
FROM table_A tableA
left join table_B tableB on tableA.table_b_id = tableB.id
left join table_C tableC on tableC.table_a_id = tableA.id

group by m.id
having compositeSum between 0 and 100