当前位置: 首页 > 面试题库 >

在JPA条件查询的“具有”子句中使用“ case ... when ... then ... else ... end”构造

严阳成
2023-03-14
问题内容

以下条件查询计算不同产品组的平均评分。

CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple>criteriaQuery=criteriaBuilder.createQuery(Tuple.class);
Metamodel metamodel=entityManager.getMetamodel();
EntityType<Product>entityType=metamodel.entity(Product.class);
Root<Product>root=criteriaQuery.from(entityType);
SetJoin<Product, Rating> join = root.join(Product_.ratingSet, JoinType.LEFT);

Expression<Number> quotExpression = criteriaBuilder.quot(criteriaBuilder.sum(join.get(Rating_.ratingNum)), criteriaBuilder.count(join.get(Rating_.ratingNum)));
Expression<Integer> roundExpression = criteriaBuilder.function("round", Integer.class, quotExpression);
Expression<Object> selectExpression = criteriaBuilder.selectCase().when(quotExpression.isNull(), 0).otherwise(roundExpression );

criteriaQuery.select(criteriaBuilder.tuple(root.get(Product_.prodId).alias("prodId"), selectExpression.alias("rating")));
criteriaQuery.groupBy(root.get(Product_.prodId));

criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(roundExpression, 0));
criteriaQuery.orderBy(criteriaBuilder.desc(root.get(Product_.prodId)));

TypedQuery<Tuple> typedQuery = entityManager.createQuery(criteriaQuery);
List<Tuple> tuples = typedQuery.getResultList();

它生成以下SQL查询:

SELECT product0_.prod_id AS col_0_0_, 
       CASE 
         WHEN Sum(ratingset1_.rating_num) / Count(ratingset1_.rating_num) IS 
              NULL THEN 
         0 
         ELSE Round(Sum(ratingset1_.rating_num) / Count(ratingset1_.rating_num)) 
       END AS col_1_0_ 
FROM   social_networking.product product0_ 
       LEFT OUTER JOIN social_networking.rating ratingset1_ 
                    ON product0_.prod_id = ratingset1_.prod_id 
GROUP  BY product0_.prod_id 
HAVING Round(Sum(ratingset1_.rating_num) / Count(ratingset1_.rating_num)) >= 0 
ORDER  BY product0_.prod_id DESC

case...when结构取代null值与0,如果在指定的表达式case子句进行评估以null

我需要case...whenhaving子句中使用相同的构造,以便group by可以通过用该构造所计算的值列表中的替换null为来过滤由子句返回的行组。0``case...when

因此,该having子句应像

HAVING
    (CASE
        WHEN Sum(ratingset1_.rating_num)/Count(ratingset1_.rating_num) IS
             NULL THEN 0 
        ELSE Round(sum(ratingset1_.rating_num)/Count(ratingset1_.rating_num))
    END)>=0

如果在greaterThanOrEqualTo()方法中,可以给出selectExpression代替,roundExpression但是这是不可能的。这样做会生成一个编译时错误,指示Expression<Integer>和之间的类型不匹配Expression<Object>

那么case...when,该having子句中如何与该子句具有相同的结构select

我也尝试过删除Object表达式的泛型类型参数,Expression selectExpression但这样做NullPointerException会引发。

此外,别名(prodIdrating作为中给出)select子句具有在生成的SQL没有影响,因为可以看到。为什么列在这里没有别名?我想念什么吗?

如果列是别名,那么应该可以having像下面这样写子句。

having rating>=0

having在标准的查询应该如下,

criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(join.<Integer>get("rating"), 0));

但是由于在select子句中列没有别名,因此会引发异常。

java.lang.IllegalArgumentException: Unable to resolve attribute [rating] against path [null]

如何解决这种情况?无论如何,Group by应该通过在子句中产生的值列表中替换null为来过滤由返回的行。0``case...when``select

我正在使用Hibernate 4.2.7 final提供的JPA 2.0。

编辑:

我尝试使用以下表达式:

Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase()
                                       .when(quotExpression.isNull(), 0)
                                       .<Integer>otherwise(roundExpression);

但是它导致抛出以下异常:

Caused by: java.lang.NullPointerException
    at java.lang.Class.isAssignableFrom(Native Method)
    at org.hibernate.ejb.criteria.ValueHandlerFactory.isNumeric(ValueHandlerFactory.java:69)
    at org.hibernate.ejb.criteria.predicate.ComparisonPredicate.<init>(ComparisonPredicate.java:69)
    at org.hibernate.ejb.criteria.CriteriaBuilderImpl.greaterThanOrEqualTo(CriteriaBuilderImpl.java:468)

接下来的表达式如何工作,

Expression<Integer> roundExpression = criteriaBuilder
                              .function("round", Integer.class, quotExpression);

两者具有相同的类型?

有没有办法将case...when结构放在having子句中?

编辑

将表达式类型更改为

Expression<Integer> selectExpression = criteriaBuilder
                                       .<Integer>selectCase()
                                       .when(quotExpression.isNull(), 0)
                                       .<Integer>otherwise(roundExpression);

因此,在 EclipseLink(2.3.2)中 工作时,可以在该having子句中使用它。

对于Hibernate提供程序,NullPoiterExcpetion如果尝试更改的表达式类型selectCase()Expression<Object>默认返回),则抛出。

更新:

这个问题在Hibernate 5.0.5 final中仍然存在。


问题答案:

这不太可能是Hibernate中的错误。在制作给定的标准查询时存在技术错误。以相同的示例但形式更简单。

假设我们对生成以下SQL查询感兴趣。

SELECT
    p.prod_id,
    p.prod_name,
    CASE
        WHEN sum(r.rating_num)/count(DISTINCT r.rating_id) IS NULL THEN 0
        ELSE round(sum(r.rating_num)/count(DISTINCT r.rating_id))
    END AS avg_rating
FROM
    product p
LEFT OUTER JOIN
    rating r
        ON p.prod_id=r.prod_id
GROUP BY
    p.prod_id,
    p.prod_name 
HAVING
    CASE
        WHEN sum(r.rating_num)/count(DISTINCT r.rating_id) IS NULL THEN 0
        ELSE round(sum(r.rating_num)/count(DISTINCT r.rating_id))
    END>=1

基于下表在MySQL中。

mysql> desc rating;
+-------------+---------------------+------+-----+---------+----------------+
| Field       | Type                | Null | Key | Default | Extra          |
+-------------+---------------------+------+-----+---------+----------------+
| rating_id   | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| prod_id     | bigint(20) unsigned | YES  | MUL | NULL    |                |
| rating_num  | int(10) unsigned    | YES  |     | NULL    |                |
| ip_address  | varchar(45)         | YES  |     | NULL    |                |
| row_version | bigint(20) unsigned | NO   |     | 0       |                |
+-------------+---------------------+------+-----+---------+----------------+
5 rows in set (0.08 sec)

此表rating与另一个表明显多到一的关系productprod_id是外键引用的主键prod_idproduct表)。

在这个问题中,我们只对子句中的CASE构造感兴趣HAVING

以下条件查询,

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
Root<Product> root = criteriaQuery.from(entityManager.getMetamodel().entity(Product.class));
ListJoin<Product, Rating> prodRatingJoin = root.join(Product_.ratingList, JoinType.LEFT);

List<Expression<?>> expressions = new ArrayList<Expression<?>>();
expressions.add(root.get(Product_.prodId));
expressions.add(root.get(Product_.prodName));

Expression<Integer> sum = criteriaBuilder.sum(prodRatingJoin.get(Rating_.ratingNum));
Expression<Long> count = criteriaBuilder.countDistinct(prodRatingJoin.get(Rating_.ratingId));

Expression<Number> quotExpression = criteriaBuilder.quot(sum, count);
Expression<Integer> roundExpression = criteriaBuilder.function("round", Integer.class, quotExpression);
Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase().when(quotExpression.isNull(), criteriaBuilder.literal(0)).otherwise(roundExpression);
expressions.add(selectExpression);

criteriaQuery.multiselect(expressions.toArray(new Expression[0]));
expressions.remove(expressions.size() - 1);

criteriaQuery.groupBy(expressions.toArray(new Expression[0]));
criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, criteriaBuilder.literal(1)));

List<Tuple> list = entityManager.createQuery(criteriaQuery).getResultList();

for (Tuple tuple : list) {
    System.out.println(tuple.get(0) + " : " + tuple.get(1) + " : " + tuple.get(2));
}

按预期生成以下正确的SQL查询。

select
    product0_.prod_id as col_0_0_,
    product0_.prod_name as col_1_0_,
    case 
        when sum(ratinglist1_.rating_num)/count(distinct ratinglist1_.rating_id) is null then 0 
        else round(sum(ratinglist1_.rating_num)/count(distinct ratinglist1_.rating_id)) 
    end as col_2_0_ 
from
    projectdb.product product0_ 
left outer join
    projectdb.rating ratinglist1_ 
        on product0_.prod_id=ratinglist1_.prod_id 
group by
    product0_.prod_id ,
    product0_.prod_name 
having
    case 
        when sum(ratinglist1_.rating_num)/count(distinct ratinglist1_.rating_id) is null then 0 
        else round(sum(ratinglist1_.rating_num)/count(distinct ratinglist1_.rating_id)) 
    end>=1

从技术角度来看,请查看上述条件查询中的以下行。

criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, criteriaBuilder.literal(1)));

问题中类似的代码如下。

createQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, 1));

看到问题中的原始表达式做的完全相同:

Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase()
                                       .when(quotExpression.isNull(), 0)
                                       .<Integer>otherwise(roundExpression);

尝试将该表达式传递给criteriaBuilder.greaterThanOrEqualTo()以下对象。

criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, 0));

请特别注意greaterThanOrEqualTo()上面的第二个参数。是的0criteriaBuilder.literal(0)因此,应该是问题中提到的例外。

因此,CriteriaBuilder#literal(T value)CriteriaBuilder#selectCase()构造中使用表达式时,请始终坚持在上述必要时始终使用文字值。

在Hibernate 4.3.6最终版,Hibernate 5.0.5最终版上进行测试
稍后,我将尝试在EclipseLink(最终版2.6.1)上运行相同的查询。 不再有古怪之处。

EclipseLink的查询的修改版本完全没有问题,只不过Object如果使用构造函数表达式代替Tuple该问题,则它需要在构造函数参数(形式参数)中使用类型参数。这是EclipseLink中一个长期存在的错误,尚待修复-



 类似资料:
  • 本文向大家介绍sql学习之CASE WHEN THEN ELSE END的用法,包括了sql学习之CASE WHEN THEN ELSE END的用法的使用技巧和注意事项,需要的朋友参考一下 超强:SQL命令中的case...when...then...else...end条件查询(不同于where) 与 类型转换的用法 case...when...then...else...end,是在from

  • 问题内容: 这个问题已经在这里有了答案 : MySQL更新案例帮助 (6个答案) 6年前关闭。 我正在尝试使用CLI脚本更新LARGE MyISAM表(2500万条记录)。该表没有被其他任何东西锁定/使用。 我想而不是对每个记录执行单个UPDATE查询,我不妨利用CASE功能。 该字段为PRIMARY。我怀疑以下查询应花费毫秒。 瞧,查询占用了CPU,并且永远无法完成。 然后,令我惊讶的是,我发现

  • 对于上面的查询,我得到了错误

  • 在使用querydsl时,我遇到了一个问题,需要生成一个case语句,其中包含不同数量的when/then语句和一个最终的else表达式。 正如这里所描述的,我已经能够轻松地生成带有固定数量的when/then语句的case语句,如下所示-

  • 问题内容: 我想看看下面是否有更好的查询方法。我想做的是创建一个摘要报告,按日期编译统计信息。 这只是查询的一个示例(在报表中使用),因为还有许多其他列,并且其他统计信息的逻辑更加复杂。是否有更优雅的方法来获取此类信息/撰写此类报告? 问题答案: 我将通过以下方式更改查询: 在子查询中进行聚合。这样可以利用有关表格的更多信息来优化。 合并第二个和第三个子查询。它们聚集在同一列上。这需要使用来确保所

  • 问题内容: 我们正在开发一个使用EJB连接到数据库的Web应用程序。 在我们的数据库模型中,我们有一个移动设备表,另一个有功能表,最后一个有手机模型映射功能值的表。模型(id_model,…)功能(id_feature,…)model_features(id_model,id_feature,值) 我们想要执行一个查询,以按匹配特征的数量对模型进行排序。就是说,我们传递了一个要匹配的功能列表(即从