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();
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 have a class Sponsor who has a Collection<Campaign>. And each Campaign has just one Sponsor.
For example, if I have it:
SELECT MIN(s.campaigns.size) FROM Sponsor s;
It returns the campaigns.size of the Sponsor who has the minimum campaigns.
But if I want to COUNT the campaigns 'c' whose c.attribute=null per Sponsor, and then return the minimum of it?
A visual example of what I want to get could be:
select min(select count(c) from Campaign c where c.sponsor.id=s.id and c.finishMoment is null) from Sponsor s;
The thing is that it's not possible to include a SELECT into the function MIN.
Hard to say what your looking for. Here's something along those lines.
** Sorry, not a jpql guy, but apparently you can take the first result, so you could do the select and order it by the count and just take the first result
Sorry, your going to have to work out the join, but you get the idea.
SELECT Campaign.id,COUNT(Campaign.id)
FROM Campaign
JOIN Sponsor on (Sponsor.id = Campaign.id)
WHERE finishMoment is null
GROUP BY Campaign.id
ORDER BY COUNT(Campaign.id)
**This is SQL
SELECT MIN(sponsorCount)
FROM (SELECT Campaign.id,COUNT(*) sponsorCount
FROM Campaign
JOIN Sponsor on (Sponsor.id = Campaign.id)
WHERE finishMoment is null
GROUP BY Campaign.id);
Let's say I have a property "name" of nodes in neo4j. Now I want to enforce that there is maximally one node for a given name by identifying all nodes with the same name. More precisely: If there are three nodes where name is "dog", I want them to be replaced by just one node with name "dog", which:
Gathers all properties from all the original three nodes.
Has all arcs that were attached to the original three nodes.
The background for this is the following: In my graph, there are often several nodes of the same name which should considered as "equal" (although some have richer property information than others). Putting a.name = b.name in a WHERE clause is extremely slow.
EDIT: I forgot to mention that my Neo4j is of version 2.3.7 currently (I cannot update it).
SECOND EDIT: There is a known list of labels for the nodes and for the possible arcs. The type of the nodes is known.
THIRD EDIT: I want to call above "node collapse" procedure from Java, so a mixture of Cypher queries and procedural code would also be a useful solution.
I have made a testcase with following schema:
CREATE (n1:TestX {name:'A', val1:1})
CREATE (n2:TestX {name:'B', val2:2})
CREATE (n3:TestX {name:'B', val3:3})
CREATE (n4:TestX {name:'B', val4:4})
CREATE (n5:TestX {name:'C', val5:5})
MATCH (n6:TestX {name:'A', val1:1}) MATCH (m7:TestX {name:'B', val2:2}) CREATE (n6)-[:TEST]->(m7)
MATCH (n8:TestX {name:'C', val5:5}) MATCH (m10:TestX {name:'B', val3:3}) CREATE (n8)<-[:TEST]-(m10)
What results in following output:
Where the nodes B are really the same nodes. And here is my solution:
//copy all properties
MATCH (n:TestX), (m:TestX) WHERE n.name = m.name AND ID(n)<ID(m) WITH n, m SET n += m;
//copy all outgoing relations
MATCH (n:TestX), (m:TestX)-[r:TEST]->(endnode) WHERE n.name = m.name AND ID(n)<ID(m) WITH n, collect(endnode) as endnodes
FOREACH (x in endnodes | CREATE (n)-[:TEST]->(x));
//copy all incoming relations
MATCH (n:TestX), (m:TestX)<-[r:TEST]-(endnode) WHERE n.name = m.name AND ID(n)<ID(m) WITH n, collect(endnode) as endnodes
FOREACH (x in endnodes | CREATE (n)<-[:TEST]-(x));
//delete duplicates
MATCH (n:TestX), (m:TestX) WHERE n.name = m.name AND ID(n)<ID(m) detach delete m;
The resulting output looks like this:
It has to be marked that you have to know the type of the various relationships.
All the properties are copied from the nodes with "higher" IDs to the nodes with the "lower" IDs.
I think you need something like a synonym of nodes.
1) Go through all nodes and create a node synonym:
MATCH (N)
WITH N
MERGE (S:Synonym {name: N.name})
MERGE (S)<-[:hasSynonym]-(N)
RETURN count(S);
2) Remove the synonyms with only one node:
MATCH (S:Synonym)
WITH S
MATCH (S)<-[:hasSynonym]-(N)
WITH S, count(N) as count
WITH S WHERE count = 1
DETACH DELETE S;
3) Transport properties and relationships for the remaining synonyms (with apoc):
MATCH (S:Synonym)
WITH S
MATCH (S)<-[:hasSynonym]-(N)
WITH [S] + collect(N) as nodesForMerge
CALL apoc.refactor.mergeNodes( nodesForMerge );
4) Remove Synonym label:
MATCH (S:Synonym)<-[:hasSynonym]-(N)
CALL apoc.create.removeLabels( [S], ['Synonym'] );
I have a query that select a set from one database and would like to turn that into a temp table that I can transform with some aggregate functions. I would like to do this in memory using MyBatis, and I can't for the life of me seem to get it sorted out. In my mapper xml, I have:
<select id="selectExample" resultType="POJOclass">
SELECT DISTINCT anon_cookie,
search,
site
FROM
T1
WHERE
is_cookied_user = 1
and is_iab_robot = 0
and is_pattern_match_robot = 0
and has_site_search_phrase = 1
and day_dt >= current_date - 1
</select>
In my java class I am retrieving this as a list of the bean class,
List<POJO> list = selectList("selectExample", className);
I would like to find a way to query this result set, either as map, or casting it to the POJO class or perhaps creating a temp table. I can't seem to find a good way to do this using MyBatis... any suggestions?
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());