我正在尝试使用JPA的Criteria API按日期间隔对实体进行分组。我使用这种查询实体的方式,因为这是服务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
}
[此示例讨论了对“月”间隔进行分组(因此按年+月分组),但最终,我正在寻找一种解决方案,该解决方案可以让我按任意间隔进行分组,例如“年”,“天”或“ “分钟”。
我想要实现的是以下查询,但使用的是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-05
和2020-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
方法赋予别名,从而使它按别名而不是整个表达式分组?
我认为这是休眠的错误。我已经使用EclipseLink尝试过您的代码,并且可以正常工作。让我显示我的代码,请记住,我必须使用数字而不是字符串,因为我使用的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);
这将返回以下SQL:
SELECT (MONTH(MY_DATE) + (12 * YEAR(MY_DATE)))
FROM MY_DATE_TABLE
GROUP BY (MONTH(MY_DATE) + (12 * YEAR(MY_DATE)))
Post Scriptum:我认为这无关紧要,但是我将查询的返回类型从Object[]
修改为Object
。