在 JPA 标准查询的“having”子句中使用“case...when...then...else...end”构造

2023-12-01

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

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 

The case...when结构取代null价值观与0,如果指定的表达式case子句被评估为null.

我需要同样的case...when构造在having子句,以便返回的行组group by可以通过替换来过滤子句null with 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> and Expression<Object>.

那么我怎样才能拥有相同的case...when结构中的having条款如select clause?

I have also tried by removing the generic type parameter Object of the expression like Expression selectExpression but doing so, caused the NullPointerException to be thrown.


此外,别名(prodId, rating)如给出的select可以看出,子句对生成的 SQL 没有影响。为什么这里的列没有别名?我错过了什么吗?

如果列有别名,那么应该可以写having子句如下。

having rating>=0

and 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 with 0在产生的值列表中case...when in the select clause.


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


EDIT:

我尝试过使用以下表达式:

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 clause?


EDIT

将表达式类型更改为

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

in EclipseLink (2.3.2)因此,它可以在having条款。

如果是 Hibernate 提供程序,它会抛出NullPoiterExcpetion,如果尝试更改表达式类型selectCase()(返回Expression<Object>默认情况下)。


Update :

这个问题在 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与另一个表有明显的多对一关系product (prod_id是引用主键的外键prod_id in the product table).

在这个问题中,我们只关心CASE构造在HAVING clause.

以下条件查询,

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()多于。这是0。本来应该是criteriaBuilder.literal(0)相反,问题中提到的例外。

因此,始终坚持使用CriteriaBuilder#literal(T value)在必要时使用文字值,如上所述,同时在CriteriaBuilder#selectCase()构造。


Tested on Hibernate 4.3.6 final, Hibernate 5.0.5 final alternatively. I will try to run the same query on EclipseLink (2.6.1 final) later on. There should not be a quirk anymore.

EclipseLink has no problem at all with the modified version of the query except that it requires an Object type parameter to the constructor argument (formal parameter), if constructor expressions are used in place of Tuple which this question has nothing to do with after all. This is a long-standing bug in EclipseLink still to be fixed - an analogous example.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 JPA 标准查询的“having”子句中使用“case...when...then...else...end”构造 的相关文章

随机推荐

  • python 中多线程应用程序中的分段错误

    我在 python 中有一个多线程应用程序 其中我创建了多个生产者线程 它们从数据库中提取数据 数据以块的形式提取 因此 线程创建具有限制值的sql语句的部分被保留在锁内 为了让线程同时执行查询 query 函数被保留在锁之外 然后结果获取
  • C#/WPF - 我无法从后台工作人员更新 UI

    我有一个代码可以使用以下命令从特定 Twitter 帐户获取推文推特锐利库 创建自定义实例UserControl并发布推文UserControl然后将其添加到StackPanel 但是 我必须收到很多推文 并且在向应用程序添加用户控件时 应
  • 按自定义顺序按键对数组进行排序

    我有以下多维数组 June 2015 gt LOW gt 160 50 MEDIUM gt 0 00 HIGH gt 60 80 July 2015 gt MEDIUM gt 226 00 HIGH gt 263 00 LOW gt 121
  • 如何将日期格式从 YYYY/MM/DD 更改为 DD/MM/YYYY

    我有一列日期 读作character值 是的 它们应该是相同的 str df date date chr 30 08 2017 30 08 2017 30 08 2017 30 08 2017 然后我将这些值转换为Date format s
  • 复制到剪贴板,无需 Flash

    我找到了许多复制到剪贴板的解决方案 但它们要么带有闪存 要么用于网站端 我正在寻找自动复制到剪贴板的方法 无需闪存 对于用户端 它用于用户脚本 当然还有跨浏览器 如果没有 Flash 这在大多数浏览器中都是不可能的 用户的剪贴板是与安全相关
  • htaccess:删除 .php 扩展名

    我有一个名为 Show php 的文件 我想删除这个的 php 扩展名 如果有人请求 Show php 将他重定向到没有 php 扩展名的页面 这是我的 htaccess 但它不会将用户重定向到没有扩展名的页面 RewriteCond RE
  • 将字符串中的所有字母加 1 [关闭]

    Closed 这个问题需要多问focused 目前不接受答案 当我输入 abc 我想得到 bcd 作为输出 所以我想要A to be B and B to be C等等直到Z这将是A 那么我该怎么做呢 我一点也不知道 您可以使用transl
  • 如何修改 Elastislide 使其无限循环

    我一直在寻找一种图像轮播 它可以一次显示多个图像 具有响应能力并且可以无限循环 Elastislide 似乎是最合适的 http tympanus net Development Elastislide index2 html 我能找到的唯
  • 未捕获的类型错误:无法调用未定义的方法“请求”

    在我的 JavaScript 代码中 我不断收到以下错误 Uncaught TypeError Cannot call method request of undefined 我的 JavaScript 如下 任何帮助将不胜感激 myJso
  • 如何以(线程)安全的方式跟踪 TPL 管道中的故障项

    我正在使用 TPL 管道设计和 Stephen Cleary 的管道设计尝试库简而言之 它包装了值 异常并将其沿着管道浮动 因此 即使是在处理方法中抛出异常的项目 最后当我await resultsBlock Completion have
  • Pyplot:绘制一侧带有刻度的曲线

    我可以使用 matplotlib pyplot 中的plot 函数来绘制这样一侧有刻度的曲线吗 Upgrade根据答案here我可以扩展这个例子 def f x return x x np exp x x def get parameter
  • Ubuntu rake 中止了! NameError:未初始化的常量 ActionDispatch::XmlParamsParser

    我正在尝试在 16 04 ubuntu 的机器上安装 redmine 的插件 现在我正在尝试安装敏捷插件 我收到了有关如何安装插件的官方文档 我按照逐步安装的步骤进行操作 但遇到了错误 重要的 我正在使用 Ruby 2 3 1 Steps
  • NextJS 环境变量不起作用

    我已经研究这个问题至少一个小时了 到目前为止我还很困惑 也许我不理解文档或媒体文章 但是 据我了解 NextJS 我已经安装了最新版本 提供了一个内置的环境变量解决方案 所以 没有必要dotenv包裹 由于 NextJS 已设置 我需要做的
  • 我无法使用 subclipse 提交 .so 库文件

    这是一个 Android 应用程序 我在文件夹中添加了一个 so 本机库 库 armeabi libc so 但是 我无法将其提交到存储库 当我右键单击 提交 时 没有任何更改反映我添加了 so 本机库文件 我该如何解决这个问题 好的 我找
  • 向远程 WebDriver 服务器发送 URL 请求 - Chrome

    我正面临着向远程 WebDriver 服务器发送 URL 请求在 Chrome 中执行 selenium 脚本时出错 我使用 Selenium 与 C 和最新版本的 Chrome 驱动程序 Chrome 66 0 3359 181 和 Se
  • Swift 位数组到字节数组(UInt8 数组)

    我有位数组 var bits Bit 以及如何将其转换为字节数组 var bytes UInt8 例如 我有 280 位 字节数组中应该有 35 个 UInt8 我可以想到的解决方案是 我采用 8 位并检查第一个是否为真 第二个是否为真 等
  • 填充 os.Stdin 作为从中读取的函数

    如何在测试中填写 os Stdin 以使用扫描仪读取其中的函数 我使用以下函数通过扫描仪请求用户命令行输入 func userInput error scanner bufio NewScanner os Stdin println Wha
  • 重叠/叠加多个内嵌图像

    我有一个尝试重叠的图像列表 以便它们看起来与此类似 My code avatar img border radius 50 position relative left 5px z index 1 div class avatars spa
  • Powershell 更换周期

    谁能告诉我他们是否认为这个 Powershell 脚本有问题 Dir where extension eq txt Rename Item NewName name replace 对于当前目录中的每个文本文件 我尝试用连字符替换文件名中的
  • 在 JPA 标准查询的“having”子句中使用“case...when...then...else...end”构造

    以下条件查询计算不同产品组的评分平均值 CriteriaBuilder criteriaBuilder entityManager getCriteriaBuilder CriteriaQuery