0

I have two tables in MySQL database.

  • product
  • order_item

Customers place orders of products which are stored into the order_item table - a one-to-many relationship from product to order_item.


Currently, I'm executing the following query.

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 

Criteria query that produces this SQL is as follows.

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();

This query sums up quantities of each group of products in the order_item table.

It displays a list of rows that looks like the following.

prod_id       qunatity_ordered

 6            11
 8             8
26             8
 7             7
31             7
12             6
27             6
24             5
 9             5

Is it possible to just count the number rows which are produced by this query - 9 in this case?

I'm on JPA 2.1 provided by EclipseLink 2.5.2 and Hibernate 4.3.6 final.

Tiny
  • 27,221
  • 105
  • 339
  • 599

2 Answers2

0

You have two options:

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

OR:

list.size();
Arth
  • 12,789
  • 5
  • 37
  • 69
  • Subqueries in the `FROM` clause are not supported by ORMs (until now). – Tiny Aug 13 '14 at 10:57
  • Fair enough.. Is their any reason why you can't use `list.size()`? – Arth Aug 13 '14 at 11:02
  • `list.size()` requires the entire list to be loaded into the memory which may impose a performance bottleneck and may cause a memory leak, when the list is too large. – Tiny Aug 13 '14 at 11:05
  • It would have to be a super large list, and if you just `SELECT 1` (or the `t0.prod_id`) and don't include the ORDER BY it is essentially just an array of integers. If you are still worried, I'm sure your system must have a way of running custom SQL. – Arth Aug 13 '14 at 11:10
0

One way to count such rows is to wrap the given query in another query that counts the rows and make the given query a subquery as follows.

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) >= ?)) 

The criteria query that produces the above 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);

I generally avoid using IN() subqueries and use EXISTS() subqueries. Nevertheless, the same query can be rewritten using IN() as follows.

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) >= ?)) 

The corresponding criteria query.

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);

Regarding restrictions in ORMs, I can't find a better alternative than this.

Tiny
  • 27,221
  • 105
  • 339
  • 599