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.