如何使用querydsl从子查询平均值集合中获取总平均值

How to get total average value from subquery average collection using querydsl

我有下一个实体:

@Entity
@Table(name = "search_request_items")
public class SearchRequestItem extends LongIdEntity {
        
   @Column(name = "date")
   private Instant date;
       
   @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "user_id")
   private User user;
        
   @Column(name = "result_count")
   private Long resultCount;
        
   /**
    * Request's text.
    */
   @Column(name = "request")
   private String request;
        
   /**
    * Request's quality. It may take 0 or 1.
    */
   @Column(name = "quality")
   private Integer quality;

   ...
}    

然后我有下一个 queryDSL 查询,return 按请求文本分组的质量平均值和用户数的集合:

public JPAQuery<Tuple> prepareTotalQuery() {
    QSearchRequestItem requestItem = QSearchRequestItem.searchRequestItem;
    QUser user = QUser.user;

    NumberExpression<Double> qualityAvgExpression = requestItem.quality.avg();
    NumberExpression<Long> qualityCountExpression = requestItem.user.countDistinct();

    JPAQuery<Tuple> query = queryFactory
        .select(qualityAvgExpression, qualityCountExpression)
        .from(requestItem)
        .leftJoin(requestItem.user, user)
        .groupBy(requestItem.request)
        .having(qualityAvgExpression.isNotNull(),
            qualityCountExpression.gt(2));

    return query;
}

但我需要 return 这个集合的总平均值,就像这个本机查询一样:

select avg(n1.avg_quality) 
from (select count(distinct user_id), avg(quality) as avg_quality 
      from search_request_items
      group by request
      having avg(quality) is not null and count(distinct user_id) > 2
     ) n1;

那么,如何更新我的 querydsl 查询以获得此结果?

这里的问题是您使用的是 JPA,而 JPA 不允许在 from 子句中使用子查询作为连接目标。

Blaze-Persistence is an extension of JPA and integrates well with Hibernate. It adds Common Table Expressions and subselect (even lateral) joins to JPQL. Blaze-Persistence also has a Querydsl integration,允许您编写如下查询:

List<Number> fetch = new BlazeJPAQuery<>(entityManager, cbf)
     .with(cteType, new BlazeJPAQuery<>()
         .bind(cteType.avgQuantity, requestItem.quality.avg())
         .from(requestItem)
         .leftJoin(requestItem.user, user)
         .groupBy(requestItem.request)
         .having(qualityAvgExpression.isNotNull(), qualityCountExpression.gt(2))))
     )
    .select(cteType.avgQuantity.avg())
    .from(cteType)         ​
   ​.fetch();

但是,对于普通的 JPA 和 Hibernate,没有简单的方法可以做到这一点。

但前提是你只是对一组数字进行平均,这些数字在 JDBC 上的序列化并不密集,并且不会遇到潜在的 N+1 问题,我建议只做最后的记忆中的平均步数:

queryFactory
        .select(qualityCountExpression)
        .from(requestItem)
        .leftJoin(requestItem.user, user)
        .groupBy(requestItem.request)
        .having(qualityAvgExpression.isNotNull(),
            qualityCountExpression.gt(2))
        .stream()
        .collect(Collectors.averagingDouble(i -> i.doubleValue()))