-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 7 posts ] 
Author Message
 Post subject: Many-to-Many query - simple with HQL impossible with Criteri
PostPosted: Sat Aug 04, 2007 1:52 pm 
Newbie

Joined: Sun Feb 11, 2007 3:42 pm
Posts: 8
I need help to migrate a trivial HQL to criteria :)

Let's say there is a many-to-many relationship between Company and Address. I need to select all Companies that have subsidaries in selected cities. E.g select all companies that are located in New York and Boston:

HQL:
from Company c where c.addresses.city = 'New York' and c.addresses.city = 'Boston'

"addresses" is a list of Address objects.

How to write this as criteria or detachedcriteria?

Thanks,

Christian

PS: I already searched the forums but cannot find the answer... Maybe it's too trivial :)). I'm using hibernate >= 3.2GA and mysql 5 with innodb dialect


Top
 Profile  
 
 Post subject:
PostPosted: Sun Aug 05, 2007 10:21 am 
Expert
Expert

Joined: Fri Jul 13, 2007 8:18 am
Posts: 370
Location: london
Quote:
from Company c where c.addresses.city = 'New York' and c.addresses.city = 'Boston'

This HQL won't work for 2 reasons. Firstly, you can't navigate through a collection (addresses) using the dot notation - you have to create an aliased join.
Secondly, each returned row would contain a single city from one of the company's addresses. Its impossible for that city to be BOTH new york and boston - your query would return no results.

I think what you want is something like this:
Code:
from Company c where 2 = (select count(distinct city) from Company c1 join c1.addresses a1 where c1.id=c.id and city in ('New York', 'Boston'))

"distinct" just incase a company has, say, 2 offices in New York.

Translating this into a Criteria _should_ be simple:
Code:
String[] cities = new String[] { "New York", "Boston" };

DetachedCriteria subquery = DetachedCriteria.forClass(Company.class, "c1")
.createCriteria("addresses")
.add(Restrictions.in("city", cities))
.add(Property.forName("c1.id").eqProperty("c.id"))
.setProjection(Projections.countDistinct("city"));
      
Criteria criteria = session.createCriteria(Company.class, "c")
.add(Subqueries.eq(cities.length, subquery));


However, there's currently a bug in hibernate...
http://opensource.atlassian.com/project ... se/HHH-952
...which means subqueries containing joins don't work. The good news is its fixed in head so should be in the 3.3 release. There's a patch in the JIRA issue if you need to fix this bug before 3.3.

To get around this you can use an SQL restriction in the criteria. Its not pretty though. I've assumed your main tables are called Company and Address and have ID columns called id. The many-to-many mapping table I've called Company_Address with columns company_id and address_id.
Code:
String[] cities = new String[] { "New York", "Boston" };

Criteria criteria = session.createCriteria(Company.class, "c")
.add(createCitiesRestriction(cities));

...
private Criterion createCitiesRestriction(String[] cities) {
   List<Object> paramsList = new ArrayList<Object>();
   List<Type> typesList = new ArrayList<Type>();
   StringBuffer inClause = new StringBuffer();
      
   // Add the count param
   paramsList.add(new Integer(cities.length));
   typesList.add(Hibernate.INTEGER);
      
   // Create cities params
   for (int i=0; i<cities.length; i++) {
      paramsList.add(cities[i]);
      typesList.add(Hibernate.STRING);
      if (i != 0) {
         inClause.append(", ");
      }
      inClause.append("? ");

   }
   StringBuffer query = new StringBuffer();
   query.append("? = (select count(distinct city) from Company c1 ");
   query.append("inner join Company_Address ca on c1.id=ca.company_id ");
   query.append("inner join Address a on ca.address_id=a.id ");
   query.append("where c1.id = {alias}.id ");
   query.append("and city in (");
   query.append(inClause);
   query.append("))");
   return Restrictions.sqlRestriction(query.toString(),
         paramsList.toArray(),
         typesList.toArray(new Type[0]));
}




Top
 Profile  
 
 Post subject:
PostPosted: Sun Aug 05, 2007 4:21 pm 
Newbie

Joined: Sun Feb 11, 2007 3:42 pm
Posts: 8
thanks for your awesome answer! I really appreciate it. But :-)...

My HQL works! It generates something like:

select
company0_.ID as ID5_,
company0_.name as name5_,
from
company company0_,
company_address address1_,
address address2_,
company_address address3_,
address address4_
where
company0_.ID=address3_.company_id
and address3_.address_id=address4_.id
and company0_.ID=address1_.company_id
and address1_.tag_id=address2_.id
and address2_.city='New York'
and address4_.city='Boston'

This is exactly what's exptected. My 2-cities condition is mapped to 2 SQL joins. One join for each city.

But let me try your suggested criterion! It definitly looks very promising. I'll come back and report!

- Chris


Top
 Profile  
 
 Post subject:
PostPosted: Sun Aug 05, 2007 4:39 pm 
Expert
Expert

Joined: Fri Jul 13, 2007 8:18 am
Posts: 370
Location: london
Hi Chris

That's very interesting. I tried your style of HQL query an got an error message saying "illegal attempt to dereference collection...". What version of hibernate are you using? Maybe my mappings are somehow different to yours.

I hadn't thought about doing a double join with the address table - its probably much more efficient than using a subselect. Wonder if the criteria API supports this? Think I'll give it a go.

Mike


Top
 Profile  
 
 Post subject:
PostPosted: Sun Aug 05, 2007 4:50 pm 
Expert
Expert

Joined: Fri Jul 13, 2007 8:18 am
Posts: 370
Location: london
Ok, double joining to the same table with criteria doesn't work. Seems like subselects are the way to go. There's been an open JIRA issue about double joins for nearly 2 years now. Probably not a priority if there are workarounds.
http://opensource.atlassian.com/project ... se/HHH-879


Top
 Profile  
 
 Post subject:
PostPosted: Sun Aug 05, 2007 4:54 pm 
Newbie

Joined: Sun Feb 11, 2007 3:42 pm
Posts: 8
Hey Mike -

I executed the HQL statement within Eclipse using hibernate tools. The current tools beta uses a pretty old Hibernate version (3.2.0.cr4). Maybe my HQL doesn't work any longer in newer versions.

Anyway, I tried your criterion and it rocks!! Great stuff. That definitly helped. You think we can find an even better solutions (without SQL and joiins instead of the subquery)?

- Chris


Top
 Profile  
 
 Post subject:
PostPosted: Sun Aug 05, 2007 5:10 pm 
Expert
Expert

Joined: Fri Jul 13, 2007 8:18 am
Posts: 370
Location: london
This is probably less efficient than doing the work in the database but another option...
You could retrieve the list of matching companies for each city of interest in turn (multiple queries) then do a set intersection to find those that are present in all result sets.
i.e.
Code:
Criteria c1 = session.createCriteria(Company.class)
   .createCriteria("addresses")
   .add(Restrictions.eq("city", "New York"));
Criteria c2 = session.createCriteria(Company.class)
   .createCriteria("addresses")
   .add(Restrictions.eq("city", "Boston"));
Set matchesAll = new HashSet();
matchesAll.addAll(c1.list());
matchesAll.retainAll(c2.list());


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 7 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.