views:

1088

answers:

3

I'd like to start by apologizing for my unfamiliarity with Hibernate. I'm only recently getting into it and am far from an expert.

I have three tables: Contract, Products, and a link table between them to define a many to many relationship.

I'm trying to write an HQL query to return all contracts that contain a range of products. Unfortunately, the IN syntax works like an Any instead of an All. So if I want all contracts that have ProductA, ProductB, and ProductC, the IN keyword will return me contracts that have any individual one of those products, instead of contracts that have all of them.

How should I structure my HQL query?

+1  A: 

Why are you expecting IN to behave like a AND? To my knowledge, IN is a kind of OR, not a AND. IN might thus not be what you're looking for. Have a look at Hibernate's Expressions and especially:

  • HQL functions that take collection-valued path expressions: size(), minelement(), maxelement(), minindex(), maxindex(), along with the special elements() and indices functions that can be quantified using some, all, exists, any, in.

[...]

The SQL functions any, some, all, exists, in are supported when passed the element or index set of a collection (elements and indices functions) or the result of a subquery (see below):

[...]

from Show show where 'fizard' in indices(show.acts)
Pascal Thivent
So what would I be looking for?
JustLoren
Can you elaborate how the above query would resolve OP's question?`indices()` will not return property values for entity collection elements AFAIK; and even if it did that won't ensure that ALL products in question are in the collection.
ChssPly76
+1  A: 

You can use group by / having:

select c
  from Contract c join c.products p
 where p.name in ('A', 'B', 'C')
 group by c.id, // list ALL Contract properties
 having count(*) = 3

Alternatively you can use a subquery to avoid listing all properties in group by:

from Contract c where c.id in (
select c.id
  from Contract c join c.products p
 where p.name in ('A', 'B', 'C')
 group by c.id
 having count(*) = 3
)

Obviously "3" will have to be replaced with the actual number of product names you supply in in clause.

ChssPly76
Wouldn't it return contracts that have 3 associated products one of which is either A, B or C?
serg
No, it won't. It would, however, return contracts that have, say, products A, A and B associated with them; if that's the case (relationship allows duplicates) the only way to resolve this would be to generate nested query with 3 explicit joins.
ChssPly76
Ok I just tested our queries. Yours selects all contracts that have at least A,B,C among their products (so it will select ABCD and XYZABC), mine selects exactly ABC. It is not clear from the question which one is needed, so both are good for different tasks.
serg
I guess I misunderstood your earlier comment then. Yes, my queries will return all contracts that have A,B,C products whether or not they have some other products also associated with them. That's what I think OP wanted.
ChssPly76
A: 

In the blog I went over such hibernate queries, take a look at example #4.

Here is a snapshot (replace Articles with Contracts and Tags with Products):

String[] tags = {"Java", "Hibernate"};
String hql = "select a from Article a " +
                "join a.tags t " +
                "where t.name in (:tags) " +
                "and a.id in (" +
                    "select a2.id " +
                    "from Article a2 " +
                    "join a2.tags t2 " +
                    "group by a2 " +
                    "having count(t2)=:tag_count) " +
                "group by a " +
                "having count(t)=:tag_count";
Query query = session.createQuery(hql);
query.setParameterList("tags", tags);
query.setInteger("tag_count", tags.length);
List<Article> articles = query.list();
serg
`group by a` is illegal: http://docs.jboss.org/hibernate/stable/core/reference/en/html/queryhql.html#queryhql-grouping
ChssPly76
You can write "group by a.id" it would be the same, but current one also works fine, just tested it under MySQL.
serg
MySQL is not exactly known for its strict treatment of `group by` clause. Try the same trick on Postgres or Oracle :-) `group by a.id` would be equally illegal because SQL standard does not allow you to have non-aggregated columns in `select` clause with aggregate function unless they are also in `group by` clause. In your case article name would not be. Mind you I'm not saying your query is bad; just that you'll need to explicitly list all the columns.
ChssPly76