Let\'s say I have two tables, Books and Reviews. Reviews has a column, stars, that can have a value between 1 and 5. A Book can have many Reviews.
How would I select al
Answering my own question...
I'm actually surprised at how difficult this is. All solutions that involve querying for Books and each Book having its list of top and bottom Reviews results in N queries per Book on some databases, and for mine in particular (MySql) requires 3N. Of course, someone may be able to figure out a clever way around this, but my playing with the code, raw SQL, researching what others have done, database limitations, etc. appears to always come back to that.
So... I ended up flipping the problem around. Instead of calculating the top and bottom Reviews whenever I query for Books (which is a very frequent query - FYI Books/Reviews are for example purposes - the actual domain is different) I calculate the top/bottom whenever a new Review is submitted. This changes submitting a Review from one "query" into three queries, but submitting a Review is fairly infrequent compared to querying for Books.
To do this, I added two new properties to Book, topReviews and bottomReviews and mapped them as one-to-many lists on Review. I then updated the code that saves new Reviews to query for the top and bottom Reviews for the Book in question (2 queries), sets the top/bottom Reviews properties on the Book (overwriting the previous), and saves the Book. Any Book queries now return these "cached" top and bottom Reviews. I still have a lazy "reviews" property that will have all reviews (not just top/bottom) if desired - another one-to-many mapping.
I am open to other recommendations. Most answers here are in the ball park but miss the problem or won't work due to database limitations or require too many queries.
Try this and let me know if it works for you:
// you should have the DAO class containing this queries
// extend HibernateDaoSupport
List<Tag> topList = getSession().createQuery("FROM Reviews r WHERE r.book = "
+ book + " ORDER BY r.stars asc").setMaxResults(3).list();
List<Tag> bottomList = getSession().createQuery("FROM Reviews r WHERE r.book = "
+ book + " ORDER BY r.stars desc").setMaxResults(3).list();
Two HQL queries using org.springframework.data.domain.Pageable like:
before in the middle-layer-service you create pageable and call repo:
Pageable pageableTop = new PageRequest(0, 3, Direction.ASC, "yourFieldToSortBy");
yourRepository.findTopOrderByYourEntity(pageableTop);
Pageable pageableBottom = new PageRequest(0, 3, Direction.DESC, "yourFieldToSortBy");
yourRepository.findTopOrderByYourEntity(pageableBottom);
the repo:
public interface YourRepository extends CrudRepository<YourEntity, String> {
@Query("FROM YourEntity")
List<YourEntity> findTopOrderByYourEntity(Pageable pageable);
}
I don't think you can do what you want using a single query. You can certainly do it in two, however (using EJBQL with named parameters):
SELECT r FROM Reviews r WHERE r.book = :book ORDER BY r.stars ASC LIMIT 3;
SELECT r FROM Reviews r WHERE r.book = :book ORDER BY r.stars DESC LIMIT 3;
And you could of course write a simple helper method that makes both of these calls and collates the results into whatever data structure you prefer.
This would typically be done with something like a union, which you can't do in HQL.
You can do this, though, in HQL
WHERE id in ([SELECT top 3]) or id in ([SELECT bottom 3])
I don't have a way to test this, but this might work too.
DetachedCriteria topN = [ your criteria ]
DetachedCriteria bottomN = [ your criteria ]
session.createCriteria(..._
.add( Property.or(Property.forName("id").in(topN),Property.forName("id").in(bottomN) )
.list();
How about do the mapping for Review and Book as many-to-many, and then on Book's reviews collection, specify: order-by="rating desc", and lazy="true"
Supposedly with lazy-fetching method, data will be fetched only when the object are to be fetched from the collection, but I'm not sure about this. To confirm, you can turn on the Hibernate SQL logging and monitor which queries have been made during runtime.