简体   繁体   中英

JPA Criteria - Count on WHERE Clause

I'm trying to create the following query in a JPA Criteria Query:

SELECT V.* FROM VENDA V
WHERE 
( SELECT COUNT(D.ID) FROM VENDADETALHE D WHERE D.ID IN
( SELECT CVD.DETALHES_ID FROM VENDA_VENDADETALHE CVD WHERE CVD.VENDA_ID = V.ID ) 
AND ( SELECT I.TIPO FROM ITEM I WHERE I.ID=D.IDITEM ) <> 'SERVICO' ) = 0
AND
( SELECT COUNT(D.ID) FROM VENDADETALHE D WHERE D.ID IN
( SELECT CVD.DETALHES_ID FROM VENDA_VENDADETALHE CVD WHERE CVD.VENDA_ID = V.ID ) 
AND ( SELECT I.TIPO FROM ITEM I WHERE I.ID=D.IDITEM ) = 'SERVICO' ) > 0

I have the following class structure

public class Venda {
  /* other class attributes ( id & etc... ) */
  @OneToMany( orphanRemoval=true, cascade = CascadeType.ALL, fetch =FetchType.LAZY )
  @JoinTable
  private List<VendaDetalhe> detalhes;
}

public class VendaDetalhe{
  /* other class attributes */
  @ManyToOne
  @JoinColumn( name = "iditem", referencedColumnName = "id")
  private Item item;
}

public class Item{
  /* other class attributes */
  @Enumerated( EnumType.STRING )
  private ETipo tipo;
}
public enum ETipo{
  PRODUTO,
  SERVICO;
}

And the following code for the query:

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Venda> qry = builder.createQuery( Venda.class );
Root<Venda> root = qry.from( Venda.class );
Join<Venda,VendaDetalhe> join = root.join( "detalhes", JoinType.INNER );
List<Predicate> p = new ArrayList<>();
p.add( builder.greatherThan( builder.count( builder.equal( join.get( "item" ).get("tipo"), ETipo.SERVICO ) ), 0L ) );
p.add( builder.equal( builder.count( builder.equal( join.get("item" ).get("tipo"),ETipo.PRODUTO) ), 0L) );
qry.where( p.toArray( new Predicate[ p.size() ] );
em.createQuery( qry ).getResultList();

But this generates an QuerySyntaxException , saying that a CLOSE is expected but = found. Is my CriteriaQuery syntax correct? I've searched the internet about using count on the WHERE clause, but couldn't find anything.

The exception message is the following:

org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE, found '=' near line 1, 
column 249 
[select generatedAlias0 from
 br.com.koinonia.habil.model.user.movimentacoes.compraevenda.Venda 
as generatedAlias0 inner join generatedAlias0.detalhes as generatedAlias1 where 
( generatedAlias0.empresa=:param0 ) and ( count(generatedAlias1.item.tipo=:param1)>0L ) 
and ( count(generatedAlias1.item.tipo=:param2)=0L )]

Don't run a count query when you could run an exists query . Even when you stay within the SQL language (or any language that translates to SQL, such as JPQL), chances are that your optimiser might not be able to pick up the possibility of transforming COUNT(*) == 0 to NOT EXISTS() and COUNT(*) > 0 to EXISTS() . Imagine one of the counts results in 1 million. Do you really need for the database to figure out the exact count value? Or can the database stop as soon as it knows whether there exists (or not) a given row?

Your original query could be re-written to this:

SELECT V.* 
FROM VENDA V
WHERE NOT EXISTS ( 
  SELECT 1 
  FROM VENDADETALHE D 
  WHERE D.ID IN ( 
    SELECT CVD.DETALHES_ID 
    FROM VENDA_VENDADETALHE CVD 
    WHERE CVD.VENDA_ID = V.ID 
  ) 
  AND ( 
    SELECT I.TIPO FROM ITEM I WHERE I.ID=D.IDITEM 
  ) <> 'SERVICO' 
)
AND EXISTS ( 
  SELECT 1 
  FROM VENDADETALHE D 
  WHERE D.ID IN ( 
    SELECT CVD.DETALHES_ID 
    FROM VENDA_VENDADETALHE CVD 
    WHERE CVD.VENDA_ID = V.ID 
  ) 
  AND ( 
    SELECT I.TIPO FROM ITEM I WHERE I.ID=D.IDITEM 
  ) = 'SERVICO' 
)

Of course, your correlated subqueries that are compared with 'SERVICO' could be further transformed to inner joins, but I'm not sure if that's causing trouble here.

Perhaps, this is now easier for you to write in JPQL, but why not just run a SQL query through EntityManager.createNativeQuery(String, Class) . Since you're projecting an entity ( V.* ), this would work just fine.

count in Hibernate QL is apparently very finicky about what goes inside the function and cannot accept nested queries. So this line:

builder.count( builder.equal( join.get( "item" ).get("tipo"), ETipo.SERVICO ) )

Needs to be peeled apart a bit so that the equal comparison is somewhere else and count is just called on a single query.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM