I have a one-to-many mapping between a parent entity and child entities. Now I need to find the number of children associated with each parent for a list of parents. I am trying to do this with HQL but I am not sure how I can get the list of parents in there. Also, I don't know how I can return the entity itself and not just its ID. My current HQL query is:
select new map(parent.id as parentId, count(*) as childCount)
from Parent parent left join parent.children children
group by parent.id
but this only returns the ID and does not filter on specific parents.
EDIT
Based on Pascal's answer I have modified the query to
select new map(parent as parent, count(elements(parent.children)) as childCount)
from Parent parent
group by parent
That does work, but is prohibitively slow: 30 seconds instead of 400 ms on the same database.
I'm not 100% sure that but what about this:
select new map(parent.id, count(elements(parent.children)))
from Parent parent group by parent.id
Thank you. I was looking for the number of children for a particular parent and your posts got me heading in the right direction.
In case anyone else needs to find the number of children for a particular parent, you could use something like this:
Query query = this.sessionFactory.getCurrentSession().createQuery("select count(elements(parent.children)) from Parent parent where name = :name");
query.setString("name", parentName);
System.out.println("count = " + query.uniqueResult());
Related
How do I implement ordering objects by aggregated nested property?
I have Photographer entity which one has a lot of PhotographerPrice entities (One to Many) with BigDecimal property called pricePerHour. When I retrieving photographers I want to sort them by the minimal price of whole prices they have.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Photographer> cq = cb.createQuery(Photographer.class);
Root<Photographer> root = cq.from(Photographer.class);
List<Predicate> predicates = new ArrayList<>(); // I have a lot of predicates which set if data was present by client
I tried to make a subquery to PhotographerPrice and than sort in root
Subquery<BigDecimal> subquery = cq.subquery(BigDecimal.class);
Root<PhotographerPrice> from = subquery.from(PhotographerPrice.class);
Predicate and = cb.and(
cb.equal(root.get(Photographer_.id), from.get(PhotographerPrice_.photographer).get(Photographer_.id)),
cb.isNotNull(from.get(PhotographerPrice_.pricePerHour))
);
subquery.correlate(root);
subquery.where(and);
subquery.select(cb.min(from.get(PhotographerPrice_.pricePerHour)));
subquery.groupBy(from.get(PhotographerPrice_.photographer).get(Photographer_.id));
...
cq.orderBy(cb.asc(subquery));
But, as I realized, it's not allowed to use a subquery in order by clause.
So, how do I can implement something like this using Criteria API:
select *
from photographer p
order by (
select min(price_per_hour) minPrice
from photographer_price pp
where p.id = pp.photographer_id
and pp.photo_per_hour is not null
group by photographer_id
);
When I tried to implement it with Join approach I've got duplicates in my result list.
Is it possible to implement it using Criteria API? Maybe there is another tool to make filtering for entities from DB more convenient? I have a lot of different parameters for filtering and sorting which related to nested properties, sometimes even related to nested in a nested property.
The only way I found to solve it:
ListJoin<Photographer, PhotographerPrice> join = root.join(Photographer_.photographerPrices);
Expression<BigDecimal> min = cb.min(join.get(PhotographerPrice_.pricePerHour));
cq.orderBy(cb.desc(min));
cq.groupBy(root.get(Photographer_.id));
But I don't sure about group by. Is it possible some troubleshooting appear later?
The approach I found that works in my case
Do left join to PhotographerPrice with Min aggregate function after that make an order based by result of this aggregate:
ListJoin<Photographer, PhotographerPrice> photographerPriceJoin = root.join(Photographer_.photographerPrices);
Expression<BigDecimal> min = cb.min(photographerPriceJoin.get(PhotographerPrice_.pricePerHour));
if (photographerCatalogFilter.getDirection().isDescending()) {
orderList.add(cb.desc(min));
} else {
orderList.add(cb.asc(min));
}
I'm using Hibernate Criteria, I want to select the maximun value for each group of values, this maximun value must be contained in a given list.
Example:
select t.field1, max(t.field2) as total
from table t
where t.field2 in :givenList
group by t.field1
order by total desc;
I've tried doing this:
Criteria criteria = session.createCriteria(Table.class);
DetachedCriteria maxNo = DetachedCriteria.forClass(Table.class);
ProjectionList projection = Projections.projectionList();
projection.add(Projections.groupProperty("id.field1"));
projection.add(Projections.max("id.field2"));
maxNo.setProjection(projection);
criteria.add(Subqueries.propertiesIn(new String[] {"id.field1", "id.field2"}, maxNo));
This code works perfectly but it returns only the max value for each group.
If I try to add other constrains like:
criteria.add(Property.forName("id.field2").in(givenList));
The request doesn't work properly. Basic case, the maximun for the group is 2, and the given list is (0,1), in this case we receive nothing for this group.
I hope you could help me. Thanks in advance!
I've found the solution.
maxNo.add(Restrictions.in("id.field2", givenList));
Actually, I make a mistake placing the Restrictionsin the Criteria. We should place the Restrictions in the DetachedCriteria.
Thank you anyway
I have the below node structure in JCR repo. I am struggling to retrieve the parent (12345) node given child node(789).
/12345/items/789
I have tried:
SELECT parent.*
FROM [nt:base] AS parent
INNER JOIN [nt:base] AS child ON ISCHILDNODE(child,parent)
WHERE name(child) = '789'
but I keep getting "This query result contains more than one selector" result.
I have to do this using sql or jcr-sql2, I don't want to use java for example to retrieve parent of parent.
thanks in advance
As Randall mention above - the query is perfectly valid.
I would not have guessed, but since you tagged this as Magnolia I presume you tried to execute this query via AdminCentral/Dev/JCRQueries app. This app doesn't support selectors. That's the whole issue here. If you run it via groovy console using script similar to the one below, it will work just fine.
query = "SELECT parent.* FROM [nt:base] AS parent INNER JOIN [nt:base] AS child ON ISCHILDNODE(child,parent) WHERE name(child) = '789'"
MgnlContext.getJCRSession("your_workspace").workspace.queryManager.createQuery(query,"JCR-SQL2").execute()
HTH,
Jan
The JCR SQL language does not support joins.
On the other hand, the JCR-SQL2 language does support joins. In fact, I just tried this exact JCR-SQL2 query, and it works perfectly well in ModeShape.
So, I'm getting a number of instances of a particular entity by id:
for(Integer songId:songGroup.getSongIds()) {
session = HibernateUtil.getSession();
Song song = (Song) session.get(Song.class,id);
processSong(song);
}
This generates a SQL query for each id, so it occurred to me that I should do this in one, but I couldn't find a way to get multiple entities in one call except by running a query. So I wrote a query
return (List) session.createCriteria(Song.class)
.add(Restrictions.in("id",ids)).list();
But, if I enable 2nd level caching doesn't that mean that my old method would be able to return the objects from the 2nd level cache (if they had been requested before) but my query would always go to the database.
What the correct way to do this?
What you're asking to do here is for Hibernate to do special case handling for your Criteria, which is kind of a lot to ask.
You'll have to do it yourself, but it's not hard. Using SessionFactory.getCache(), you can get a reference to the actual storage for cached objects. Do something like the following:
for (Long id : allRequiredIds) {
if (!sessionFactory.getCache().containsEntity(Song.class, id)) {
idsToQueryDatabaseFor.add(id)
} else {
songs.add(session.get(Song.class, id));
}
}
List<Song> fetchedSongs = session.createCriteria(Song.class).add(Restrictions.in("id",idsToQueryDatabaseFor).list();
songs.addAll(fetchedSongs);
Then the Songs from the cache get retrieved from there, and the ones that are not get pulled with a single select.
If you know that the IDs exist, you can use load(..) to create a proxy without actually hitting the DB:
Return the persistent instance of the given entity class with the given identifier, obtaining the specified lock mode, assuming the instance exists.
List<Song> list = new ArrayList<>(ids.size());
for (Integer id : ids)
list.add(session.load(Song.class, id, LockOptions.NONE));
Once you access a non-identifier accessor, Hibernate will check the caches and fallback to DB if needed, using batch-fetching if configured.
If the ID doesn't exists, a ObjectNotFoundException will occur once the object is loaded. This might be somewhere in your code where you wouldn't really expect an exception - you're using a simple accessor in the end. So either be 100% sure the ID exists or at least force a ObjectNotFoundException early where you'd expect it, e.g. right after populating the list.
There is a difference between hibernate 2nd level cache to hibernate query cache.
The following link explains it really well: http://www.javalobby.org/java/forums/t48846.html
In a nutshell,
If you are using the same query many times with the same parameters then you can reduce database hits using a combination of both.
Another thing that you could do is to sort the list of ids, and identify subsequences of consecutive ids and then query each of those subsequences in a single query. For example, given List<Long> ids, do the following (assuming that you have a Pair class in Java):
List<Pair> pairs=new LinkedList<Pair>();
List<Object> results=new LinkedList<Object>();
Collections.sort(ids);
Iterator<Long> it=ids.iterator();
Long previous=-1L;
Long sequence_start=-1L;
while (it.hasNext()){
Long next=it.next();
if (next>previous+1) {
pairs.add(new Pair(sequence_start, previous));
sequence_start=next;
}
previous=next;
}
pairs.add(new Pair(sequence_start, previous));
for (Pair pair : pairs){
Query query=session.createQuery("from Person p where p.id>=:start_id and p.id<=:end_id");
query.setLong("start_id", pair.getStart());
query.setLong("end_id", pair.getEnd());
results.addAll((List<Object>)query.list());
}
Fetching each entity one by one in a loop can lead to N+1 query issues.
Therefore, it's much more efficient to fetch all entities at once and do the processing afterward.
Now, in your proposed solution, you were using the legacy Hibernate Criteria, but since it's been deprecated since Hibernate 4 and will probably be removed in Hibernate 6, so it's better to use one of the following alternatives.
JPQL
You can use a JPQL query like the following one:
List<Song> songs = entityManager
.createQuery(
"select s " +
"from Song s " +
"where s.id in (:ids)", Song.class)
.setParameter("ids", songGroup.getSongIds())
.getResultList();
Criteria API
If you want to build the query dynamically, then you can use a Criteria API query:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Song> query = builder.createQuery(Song.class);
ParameterExpression<List> ids = builder.parameter(List.class);
Root<Song> root = query
.from(Song.class);
query
.where(
root.get("id").in(
ids
)
);
List<Song> songs = entityManager
.createQuery(query)
.setParameter(ids, songGroup.getSongIds())
.getResultList();
Hibernate-specific multiLoad
List<Song> songs = entityManager
.unwrap(Session.class)
.byMultipleIds(Song.class)
.multiLoad(songGroup.getSongIds());
Now, the JPQL and Criteria API can benefit from the hibernate.query.in_clause_parameter_padding optimization as well, which allows you to increase the SQL statement caching mechanism.
For more details about loading multiple entities by their identifier, check out this article.
I have a Hibernate criteria call that I want to execute in one SQL statement. What I'm trying to do is select instances of Parent that have Children with a property in a range of values (SQL IN clause), all while loading the children using an outer join. Here's what I have so far:
Criteria c = session.createCriteria(Parent.class);
c.createAlias("children", "c", CriteriaSpecification.LEFT_JOIN)
.setFetchMode("c", FetchMode.JOIN)
.add(Restrictions.in("c.property", properties));
c.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
return c.list();
Here's some sample data:
Parent
Parent ID
A
B
C
Children
Child ID Parent ID property
... A 0
... A 2
... A 7
... B 1
... C 1
... C 2
... C 3
What I want to do is return the parents and ALL their children if one of the children has a property equal to my bind parameter(s). Let's assume properties is an array containing {2}. In this case, the call will return parents A and C but their child collections will contain only element 2. I.e. Parent[Children]:
A[2] & C[2]
What I want is:
A[0, 2, 7] & C[1, 2 3]
If this is not a bug, it seems to be a broken semantic. I don't see how calling A.getChildren() or C.getChildren() and returning 1 record would ever be considered correct -- this is not a projection. I.e. if I augment the query to use the default select fetch, it returns the proper children collections, albiet with a multitude of queries:
c.createAlias("children", "c").add(
Restrictions.in("c.property", properties));
Is this a bug? If not, how can I achieve my desired result?
Criteria c = session.createCriteria(Parent.class);
c.createAlias("children", "children");
c.add(Restrictions.in("children.property", properties));
c.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
return c.list();
getChildren() is just the name of the getter/setter, your query will determine how the objects get populated.
I'm going to guess here that the first part spits out
SELECT * FROM Parent
INNER JOIN Child c ON ...
WHERE c.property in (x,y,z)
which doesn't get you what you want. What'd you'd want to do if you were writing this in raw SQL is this:
SELECT * FROM Parent
WHERE ParentID IN (SELECT DISTINCT parentID FROM Child WHERE c.property in (x,y,z))
rearranging your criteria appropriately might do the trick if the last one isn't producing this query. (Could you also post what hibernate is generating for each?)
I would start the Criteria with the child class. You'll get a list with all the children, and then you can iterate and get the parent for each children.
This can be done in work around way.
Criteria c1 = session.createCriteria(Child.class);
c1.add(Restrictions.in("property", properties));
c1.setProjection( Projections.distinct( Projections.property( "parentId" ) ) );
List<Integer> parentIds = c1.list();
Criteria c2 = session.createCriteria(Parent.class);
c2.createAlias("children", "children");
c2.add(Restrictions.in("id", parentIds));
return c2.list();