计算在JPA中使用“group by”和“have”过滤的行数
Posted
技术标签:
【中文标题】计算在JPA中使用“group by”和“have”过滤的行数【英文标题】:Count the number of rows filtered by using "group by" and "having" in JPA 【发布时间】:2014-08-13 10:37:12 【问题描述】:我在 mysql 数据库中有两个表。
产品 order_item客户下订单的产品存储在order_item
表中 - 从product
到order_item
的一对多关系。
目前,我正在执行以下查询。
SELECT t0.prod_id,
sum(t1.quantity_ordered)
FROM projectdb.product t0,
projectdb.order_item t1
WHERE (t0.prod_id = t1.prod_id)
GROUP BY t0.prod_id
HAVING (sum(t1.quantity_ordered) >= ?)
ORDER BY sum(t1.quantity_ordered) DESC
产生此 SQL 的条件查询如下。
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]>criteriaQuery=criteriaBuilder.createQuery(Object[].class);
Metamodel metamodel = entityManager.getMetamodel();
Root<OrderItem> root = criteriaQuery.from(metamodel.entity(OrderItem.class));
Join<OrderItem, Product> orderItemProdJoin = root.join(OrderItem_.prodId, JoinType.INNER);
List<Expression<?>>expressions=new ArrayList<Expression<?>>();
expressions.add(orderItemProdJoin.get(Product_.prodId));
expressions.add(criteriaBuilder.sum(root.get(OrderItem_.quantityOrdered)));
criteriaQuery.multiselect(expressions.toArray(new Expression[0]));
criteriaQuery.groupBy(orderItemProdJoin.get(Product_.prodId));
criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(criteriaBuilder.sum(root.get(OrderItem_.quantityOrdered)), criteriaBuilder.literal(5)));
criteriaQuery.orderBy(criteriaBuilder.desc(criteriaBuilder.sum(root.get(OrderItem_.quantityOrdered))));
List<Object[]> list = entityManager.createQuery(criteriaQuery).getResultList();
此查询汇总order_item
表中每组产品的数量。
它显示如下所示的行列表。
prod_id qunatity_ordered
6 11
8 8
26 8
7 7
31 7
12 6
27 6
24 5
9 5
是否可以只计算此查询产生的行数 - 在这种情况下为 9?
我正在使用 EclipseLink 2.5.2 和 Hibernate 4.3.6 final 提供的 JPA 2.1。
【问题讨论】:
【参考方案1】:你有两个选择:
SELECT COUNT(*)
FROM (
SELECT 1,
FROM projectdb.product t0,
projectdb.order_item t1
WHERE (t0.prod_id = t1.prod_id) /* I prefer not to use Implicit Joins */
GROUP BY t0.prod_id
HAVING (sum(t1.quantity_ordered) >= ?)
) groups
或者:
list.size();
【讨论】:
ORM 不支持FROM
子句中的子查询(直到现在)。
很公平......他们有什么理由不能使用list.size()
吗?
list.size()
要求将整个列表加载到内存中,当列表太大时,这可能会造成性能瓶颈并可能导致内存泄漏。
它必须是一个超级大的列表,如果你只是SELECT 1
(或t0.prod_id
)并且不包括ORDER BY,它本质上只是一个整数数组。如果您仍然担心,我相信您的系统一定有运行自定义 SQL 的方法。【参考方案2】:
计算此类行数的一种方法是将给定查询包装在另一个计算行数的查询中,并使给定查询成为子查询,如下所示。
SELECT count(DISTINCT(t0.prod_id))
FROM projectdb.product t0
WHERE EXISTS (SELECT t1.prod_id
FROM projectdb.order_item t2,
projectdb.product t1
WHERE ((t1.prod_id = t0.prod_id )
AND ( t1.prod_id = t2.prod_id))
GROUP BY t1.prod_id
HAVING (sum(t2.quantity_ordered) >= ?))
产生上述 SQL 的条件查询。
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Long>criteriaQuery=criteriaBuilder.createQuery(Long.class);
Metamodel metamodel = entityManager.getMetamodel();
Root<Product> root = criteriaQuery.from(metamodel.entity(Product.class));
criteriaQuery.select(criteriaBuilder.countDistinct(root));
Subquery<Long> orderItemSubquery = criteriaQuery.subquery(Long.class);
Root<OrderItem> orderItemRoot = orderItemSubquery.from(metamodel.entity(OrderItem.class));
Join<OrderItem, Product> orderItemProdJoin = orderItemRoot.join(OrderItem_.prodId, JoinType.INNER);
orderItemSubquery.select(orderItemProdJoin.get(Product_.prodId));
orderItemSubquery.where(criteriaBuilder.equal(root, orderItemRoot.get(OrderItem_.prodId)));
orderItemSubquery.groupBy(orderItemProdJoin.get(Product_.prodId));
orderItemSubquery.having(criteriaBuilder.greaterThanOrEqualTo(criteriaBuilder.sum(orderItemRoot.get(OrderItem_.quantityOrdered)), criteriaBuilder.literal(5)));
criteriaQuery.where(criteriaBuilder.exists(orderItemSubquery));
Long count = entityManager.createQuery(criteriaQuery).getSingleResult();
System.out.println("count = "+count);
我通常避免使用IN()
子查询并使用EXISTS()
子查询。不过,可以使用IN()
重写相同的查询,如下所示。
SELECT count(DISTINCT(t0.prod_id))
FROM projectdb.product t0
WHERE t0.prod_id IN (SELECT t1.prod_id
FROM projectdb.order_item t2,
projectdb.product t1
WHERE (t1.prod_id = t2.prod_id)
GROUP BY t1.prod_id
HAVING (sum(t2.quantity_ordered) >= ?))
对应的条件查询。
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Long>criteriaQuery=criteriaBuilder.createQuery(Long.class);
Metamodel metamodel = entityManager.getMetamodel();
Root<Product> root = criteriaQuery.from(metamodel.entity(Product.class));
criteriaQuery.select(criteriaBuilder.countDistinct(root));
Subquery<Long> orderItemSubquery = criteriaQuery.subquery(Long.class);
Root<OrderItem> orderItemRoot = orderItemSubquery.from(metamodel.entity(OrderItem.class));
Join<OrderItem, Product> orderItemProdJoin = orderItemRoot.join(OrderItem_.prodId, JoinType.INNER);
orderItemSubquery.select(orderItemProdJoin.get(Product_.prodId));
orderItemSubquery.groupBy(orderItemProdJoin.get(Product_.prodId));
orderItemSubquery.having(criteriaBuilder.greaterThanOrEqualTo(criteriaBuilder.sum(orderItemRoot.get(OrderItem_.quantityOrdered)), criteriaBuilder.literal(5)));
criteriaQuery.where(criteriaBuilder.in(root.get(Product_.prodId)).value(orderItemSubquery));
Long count = entityManager.createQuery(criteriaQuery).getSingleResult();
System.out.println("count = "+count);
关于 ORM 的限制,我找不到比这更好的选择。
【讨论】:
以上是关于计算在JPA中使用“group by”和“have”过滤的行数的主要内容,如果未能解决你的问题,请参考以下文章
SQL Group By and Have 子句和 exists 子句
LINQ to Sql 左外连接与 Group By 和 Have 子句