使用JPA的Criteria API按日期间隔进行分组。

问题描述 投票:2回答:1

我正试图使用JPA的Criteria API按日期间隔对实体进行分组。我使用这种方式查询实体,因为这是服务于API请求的一部分,它可能会询问任何实体的任何字段,包括排序、过滤、分组和聚合。除了按日期字段进行分组外,一切都很正常。我的底层DBMS是PostgreSQL。

举个最小的例子,这是我的实体类。

@Entity
@Table(name = "receipts")
public class DbReceipt {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private Date sellDate;
    // Many other fields
}

这个例子讨论了对 "月 "区间的分组(因此按年+月进行分组),但最终我在寻找一个解决方案,让我按任何区间进行分组,如 "年"、"日 "或 "分钟"。

我试图实现的是下面的查询,但使用Criteria API。

SELECT TO_CHAR(sell_date, 'YYYY-MM') AS alias1 FROM receipts GROUP BY alias1;

我的尝试是这样的

@Service
public class ReceiptServiceImpl extends ReceiptService {
    @Autowired
    private EntityManager em;

    @Override
    public void test() {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
        Root<?> root = query.from(DbReceipt.class);
        Expression<?> expr = cb.function("to_char", String.class, root.get("sellDate"), cb.literal("YYYY-MM"));

        query.groupBy(expr);
        query.multiselect(expr);

        TypedQuery<Object[]> typedQuery = em.createQuery(query);
        List<Object[]> resultList = typedQuery.getResultList();
    }
}

我之所以使用 to_char 功能而非 MONTH 和类似的是,我需要像 2019-052020-05 以免被归为一组。我还将这个例子缩小到只有年和月,以保持简短,但目标是按任何日期间隔进行分组。

上面的代码创建了以下查询(启用了SQL日志),结果出现了一个错误。

Hibernate: select to_char(dbreceipt0_.sell_date, ?) as col_0_0_ from receipts dbreceipt0_ group by to_char(dbreceipt0_.sell_date, ?)
24-05-2020 12:16:30.071 [http-nio-1234-exec-5] WARN  o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - SQL Error: 0, SQLState: 42803
24-05-2020 12:16:30.071 [http-nio-1234-exec-5] ERROR o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - ERROR: column "dbreceipt0_.sell_date" must appear in the GROUP BY clause or be used in an aggregate function
  Position: 16

对我来说,这是由于整个表达式被放到了查询的 "group by "部分,而不仅仅是一个别名造成的。现在,我试着给表达式分配一个别名(它返回的是 Selection<T> 和groupBy接受表达式,因此,我只能真正在 multiselect),但这并不影响查询的执行方式--没有任何改变。

如何使用Criteria API实现上述的按年月分组?也许除了使用 to_char? 也许有一种方法可以给这个名字起个别名 groupBy 方法,从而导致它通过别名而不是整个表达式进行分组?

postgresql spring-boot jpa criteria criteria-api
1个回答
1
投票

我认为这是PostgreSQL中的一个错误(错误来自那里,而不是Hibernate)。我试过用EclipseLink+Derby对你的代码稍加修改,就能完美地工作了。TO_CHAR 函数。

Expression<Integer> year = cb.function("YEAR", Integer.class, root.get("sellDate"));
Expression<Integer> month = cb.function("MONTH", Integer.class, root.get("sellDate"));
Expression<Integer> expr = cb.sum(month, cb.prod(12, year));
query.groupBy(expr);
query.multiselect(expr);

这将返回以下SQL。

SELECT (MONTH(MY_DATE) + (12 * YEAR(MY_DATE))) 
FROM MY_DATE_TABLE 
GROUP BY (MONTH(MY_DATE) + (12 * YEAR(MY_DATE)))

请注意,在JPA标准查询中,没有可移植的解决方案来操作日期。如果要同时查询的组数不是太多,我会选择一个更实用的方法,在Java中找到日期,并将它们作为字面值传递给查询构建器。

另一个变通的方法是用 groupBy(root.get("sellDate")) 然后根据所需的时间段在Java中汇总结果。

Post Scriptum: 我认为这并不相关,不过我修改了查询的返回类型,将其从 Object[]Object.

© www.soinside.com 2019 - 2024. All rights reserved.