hibernate中的hql不支持sum(distinct)语句

在进行数据统计时,经常会使用聚合函数,在hibernate中也支持聚合函数。如进行以下的统计查询:

select sum(distinct 奖牌) from 成绩 inner join 参赛运动员 inner join 运动员的学校 group by 学校

在上面的数据模型中,成绩与参赛运动员为一对多关系,即可能为多个运动员以团体参赛的形式参加一个项目,最终取得一条成绩。运动员与学校为多对一关系。在上面的查询中,为避免在同一个成绩中,由团体参赛的运动员来自同一个学校,因为只能记为一个奖牌,而不是N(N为参赛运动员数量)。因此,需要使用distinct对重复的成绩进行过滤,并按学校分组。

在正常的sql查询下,现在的数据库已经支持在sum聚合函数中进行distinct操作了。而在hibernate中,使用如此的查询会报一个如下的错误:

org.hibernate.hql.ast.QuerySyntaxException: unexpected token: distinct

即不支持在sum函数中带有distinct语句,在相关的bug列表中。网页:https://hibernate.onjira.com/browse/HHH-6311 也描述了这一问题,且值到现在还没有解决。那么为什么会产生这个错误呢,这是由hibernate将hql转换为sql的过程中产生的,即在hibernate的语法树中,就不支持这样的写法。相关的语法描述中,针对聚合函数是这样的(在源文件hql.g中)

:aggregate
	: ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! additiveExpression CLOSE! { #aggregate.setType(AGGREGATE); }
	// Special case for count - It's 'parameters' can be keywords.
	|  COUNT^ OPEN! ( STAR { #STAR.setType(ROW_STAR); } | ( ( DISTINCT | ALL )? ( path | collectionExpr ) ) ) CLOSE!
	|  collectionExpr
	;

在上面的描述中,只有count支持带distinct或all描述语句,而其它的如sum,avg,max,min均不支持,因此在进行转换时即会产生异常。因为,只能采用sql的方法实现上面的统计查询了。好在使用session.createSQLQuery,只需要将hql进行简单的人工转换再加上addScalar进行结果类型转换即能实现操作,实现上难度不大。

参考文章:Antlr–看Hibernate3如何解释HQL语言

Hibernate中criteria一对多关联查询时distinct的分页和数量问题

数据模型
班级:clazz(id,name,studentList)
学生:student(id,name,address,clazz)

经典查询
查询有地址在成都的班级信息,并分页显示。

一般情况下,以上的条件更多,也更复杂,且查询主体必须从班级入手。那么,使用Hibernate的criteria查询的话,一般情况下,编码如下:

DetachedCriteria criteria = DetachedCriteria.forClass(Clazz,"c");
criteria.createAlias("studentList", "sl");
criteria.add(Restrictions.eq("sl.address", "成都");

//保证Distinct
criteria.setResultTransformer(DetachedCriteria.DISTINCT_ROOT_ENTITY);

//分页
listByCriteria(criteria,page);

单就逻辑本身,看似没有什么问题,且在数据较少(如没有分页数据时),会返回正确的结果。但是一旦出现分页数据时,你就会发现,分页数据是错的。表现结果为page对象所表现的数据总数明确不对,且每页所显示的数据也并不是分页信息中的20条,而是不确定的几条(明显少于默认的20条)。这就表明,在这个查询中,肯定有一步出问题了。

没错,在这个查询中,我们要求返回的主体为班级,即Clazz。但Hibernate出生的sql却并不能如我们所愿,它直接使用连接来组装sql语句,并尝试返回包括学生在类的一个复合对象,然后再组装成班级对象,最后根据集合信息进行distinct操作,最后就形成了不正确的结果。整个操作看起来如下所示:

//第一步:生成sql,使用联接查询
select a.*,b.* from clazz a inner join student b on b.aId = a.id where b.address = '成都'

//第二步:带参数 page信息,查询前20条,返回结果为 page.setTotal(XX条),当页数据条数:20
班级一 李一 成都
班级一 李二 成都
班级一 李三 成都
班级二 李四 成都
班级二 李五 成都
班级三 李六 成都
......
//第三步:组装班级对象,去重,此步操作在hibernate程序中进行,返回结果
班级一
班级二
班级三

//返回结果,实际情况:当前page显示数量:20,实际所看到数量 3!

继续阅读“Hibernate中criteria一对多关联查询时distinct的分页和数量问题”