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

Jac*_*mok 5 postgresql hibernate jpa criteria-api spring-boot

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

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

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

这个例子讨论了对我的“月”间隔进行分组(因此按年+月分组),但最后我正在寻找一种可以让我按任何间隔分组的解决方案,例如“年”、“日”或“分钟” ”。

我想要实现的是以下查询,但使用 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用to_charfunction 而 notMONTH和 similar的原因是我需要像这样的实体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
Run Code Online (Sandbox Code Playgroud)

对我来说,这是由于整个表达式被放入查询的“group by”部分,而不仅仅是别名。现在,我尝试为表达式分配一个别名(它返回Selection<T>并且 groupBy 接受表达式,因此我只能在 中真正使用它multiselect),但这并没有影响查询的执行方式 - 没有任何改变。

如何使用 Criteria API 实现上述按年和月分组?也许除了使用之外还有其他不同的方式to_char?也许有一种方法可以为方法提供别名,groupBy使其按别名而不是整个表达式进行分组?

per*_*ssf 1

我认为这是 PostgreSQL 中的一个错误(错误来自那里,而不是来自 Hibernate)。我已经尝试使用 EclipseLink + Derby 对您的代码进行了稍微修改的版本,并且运行良好。请注意,我必须使用数字而不是字符串,因为 Derby DB 没有等效的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);
Run Code Online (Sandbox Code Playgroud)

这将返回以下 SQL:

SELECT (MONTH(MY_DATE) + (12 * YEAR(MY_DATE))) 
FROM MY_DATE_TABLE 
GROUP BY (MONTH(MY_DATE) + (12 * YEAR(MY_DATE)))
Run Code Online (Sandbox Code Playgroud)

请注意,没有用于在 JPA 标准查询中操作日期的可移植解决方案。如果要同时查询的组数不太高,我会采用更实用的方法,在 Java 中查找日期并将它们作为文字传递给查询生成器。

另一种解决方法是使用 a 进行查询groupBy(root.get("sellDate")),然后根据所需的时间段在 Java 中聚合结果。

Post Scriptum:我认为这不相关,但是我将查询的返回类型从 修改Object[]Object