In my SDN 4 project I have a #QueryResult POJO:
#QueryResult
public class WeightedDecision {
private Decision decision;
private double weight;
public Decision getDecision() {
return decision;
}
public void setDecision(Decision decision) {
this.decision = decision;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
}
And a lot of Spring Data Neo4j repository methods that work fine with this WeightedDecision query result.
Right now I'm trying to manually construct a Cypher query where I'm going to return the list of WeightedDecision as a result.
I use following method for this purpose:
return (List<WeightedDecision>) session.query(WeightedDecision.class, cypher, parameters);
my Cypher query looks like:
MATCH (parentD)-[:CONTAINS]->(childD:Decision)-[ru:CREATED_BY]->(u:User) WHERE id(parentD) = {decisionId} OPTIONAL MATCH (childD)<-[:VOTED_FOR]-(vg:VoteGroup)-[:VOTED_ON]->(c:Criterion) WHERE id(c) IN {criteriaIds} WITH childD, ru, u, vg.avgVotesWeight as weight RETURN childD AS decision, ru, u, sum(weight) as weight ORDER BY weight DESC
but the result of session.query is empty in this case.
If I'm changing the session.query method parameters to:
return (List<WeightedDecision>) session.query(Decision.class, cypher, parameters);
where Decision is a Node Entity everything works fine and returns List of Decision.
How to get it working with a #QueryResult type ?
#QueryResult is not an OGM concept and hence is not supported by the OGM Session.
It is only supported on repository queries.
Related
I have the following SDN 4 entity:
#NodeEntity
public class Decision {
#Relationship(type = CONTAINS, direction = Relationship.INCOMING)
private Set<Decision> parentDecisions;
...
}
I'd like to find this entity by id and return all its parentDecisions with a following SDN4 repository method and the custom Cypher query:
MATCH (d:Decision) WHERE d.id = {decisionId} OPTIONAL MATCH (d)<-[rdp:CONTAINS]-(parentD:Decision) RETURN d, rdp, parentD
Decision getById(#Param("decisionId") Long decisionId);
right now in case of existing parentD it fails with the following exception:
java.lang.RuntimeException: Result not of expected size. Expected 1 row but found 3
at org.neo4j.ogm.session.delegates.ExecuteQueriesDelegate.queryForObject(ExecuteQueriesDelegate.java:73)
at org.neo4j.ogm.session.Neo4jSession.queryForObject(Neo4jSession.java:382)
at sun.reflect.GeneratedMethodAccessor111.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
How to properly implement this logic with SDN4 repository method and the custom Cypher query ?
UPDATED
I tested approach suggested by frant.hartm. Unfortunately it still doesn't work.
For example the following SDN4 repository method correctly returns 2 Decision:
#Query("MATCH (d:Decision)-[drd:FOLLOWS]->(following:Decision) WHERE d.id = {decisionId} RETURN following")
List<Decision> test(#Param("decisionId") Long decisionId);
but the next one:
#Query("MATCH (d:Decision) WHERE d.id = {decisionId} OPTIONAL MATCH (d)-[rdf:FOLLOWS]->(followD:Decision) RETURN d as decision, collect(rdf), collect(followD) ")
DecisionHolder test1(#Param("decisionId") Long decisionId);
returns only main decision (holder.getDecision()) but with empty collection of followD (holder.getDecision().getFollowDecisions()) Decision.
This is Decision entity:
#NodeEntity
public class Decision {
private static final String FOLLOWS = "FOLLOWS";
#Relationship(type = FOLLOWS, direction = Relationship.INCOMING)
private Set<Decision> followDecisions;
#Relationship(type = FOLLOWS, direction = Relationship.INCOMING)
public Set<Decision> getFollowDecisions() {
return followDecisions;
}
#Relationship(type = FOLLOWS, direction = Relationship.INCOMING)
public void setFollowDecisions(Set<Decision> followDecisions) {
this.followDecisions = followDecisions;
}
....
}
What am I doing wrong ?
The core issue is that your query returns multiple Decision instances (under different variables - d and parentD), so it can't just arbitrarily chose one. Use #QueryResult to extract desired value. Also use COLLECT to get a single result.
#QueryResult
public class DecisionHolder {
Decision d;
}
MATCH (d:Decision) WHERE d.id = {decisionId} " +
"OPTIONAL MATCH (d)<-[rdp:CONTAINS]-(parentD:Decision) " +
"RETURN d, collect(rdp), collect(parentD)")
DecisionHolder getById(#Param("decisionId") Long decisionId);
Update:
For the relationships to map correctly relationship entities and simple relationships with same type must not be mixed.
If you have a simple relationship and relationship entity with same type
when they represent same relationship type use relationship entity class in your relationship definition (e.g. Set<ParentDecisionRelationship> parentDecisions)
when they represent different types rename one of them
I have a Decision node with a collection of Tag:
#NodeEntity
public class Decision {
#Relationship(type = BELONGS_TO, direction = Relationship.OUTGOING)
private Set<Tag> tags;
....
}
Based on the issue described at the following question SDN4/OGM Cypher query and duplicates at Result I have created the following query in order to select Decision + it's Tags:
MATCH (parentD)-[:CONTAINS]->(childD:Decision)
WHERE parentD.id = {decisionId}
WITH childD
SKIP 0 LIMIT 100
RETURN childD AS decision,
[ (childD)-[rdt:BELONGS_TO]->(t:Tag) | t ] AS tags
Is it possible to change the RETURN statement in order to place tags inside decision(as decision.tags) instead of having both of them at the same level ?
Sure that's easy, it's then just not a node anymore, but a map.
MATCH (parentD:Decision)-[:CONTAINS]->(childD:Decision)
WHERE parentD.id = {decisionId}
WITH childD
SKIP 0 LIMIT 100
RETURN childD {.*, tags: [ (childD)-[:BELONGS_TO]->(t:Tag) | t ] } AS decision
This uses map expressions, where you have a map constructed by:
variable { .property, .*, foo:"bar", bar:nested-expression }
In my Neo4j/SDN4 project I have a following node entity:
#NodeEntity
public class Nomination extends Commentable {
private final static String CONTAINS = "CONTAINS";
private final static String DEFINED_BY = "DEFINED_BY";
private String name;
#Relationship(type = CONTAINS, direction = Relationship.OUTGOING)
private Set<Criterion> criteria = new HashSet<>();
...
}
I need to implement a Cypher query that will try to find Nomination by exact collection of associated criteria (by criterion ids).
Right now I have a following query:
MATCH (n:Nomination)-[:CONTAINS]->(c:Criterion) WHERE id(n) = {nominationId} AND id(c) IN {criterionIds} RETURN n
but it is not enough because of Nomination can contain less criteria that was provided in {criterionIds} but I need to check exact match(order of criteria doesn't matter)
How to reimplement this query in order to do this ?
Use COLLECT and then ALL function.
https://neo4j.com/docs/developer-manual/current/cypher/functions/predicates/#functions-all
MATCH (n:Nomination)-[:CONTAINS]->(c:Criterion)
WHERE id(n) = {nominationId}
WITH n,COLLECT(id(c)) AS foundCritIds
WHERE ALL (id IN {criterionIds} WHERE id in foundCritIds)
RETURN n
Here's an alternate approach, you may want to PROFILE each to see which works best for you:
MATCH (c:Criterion)
WHERE id(c) in {criterionIds}
WITH COLLECT(c) as criterion
WITH criterion, head(criterion) as firstC
MATCH (firstC)<-[:CONTAINS]-(n:Nomination)
WHERE SIZE((n)-[:CONTAINS]->(:Criterion)) = SIZE(criterion)
AND ALL(crit in criterion[1..] WHERE (n)-[:CONTAINS]->(crit))
RETURN n
I have a following Cypher query/Spring Data Neo4j 4 that returns a list of Criterion with their average weight which were added on these Criterion for a specific childDecisionId.
MATCH (parentD:Decision)-[:CONTAINS]->(childD:Decision)
WHERE id(childD) = {childDecisionId} AND id(parentD) = {parentDecisionId}
WITH childD, parentD MATCH (parentD)<-[:DEFINED_BY]-(c:Criterion)<-[:VOTED_ON]-(vg:VoteGroup)-[:VOTED_FOR]->(childD)
RETURN c AS criterion, vg.avgVotesWeight AS weight
#Query("MATCH (parentD:Decision)-[:CONTAINS]->(childD:Decision) WHERE id(childD) = {childDecisionId} AND id(parentD) = {parentDecisionId} WITH childD, parentD MATCH (parentD)<-[:DEFINED_BY]-(c:Criterion)<-[:VOTED_ON]-(vg:VoteGroup)-[:VOTED_FOR]->(childD) RETURN c AS criterion, vg.avgVotesWeight AS weight")
List<WeightedCriterion> getCriteriaWithAvgVotesWeightForChildDecision(#Param("childDecisionId") Long childDecisionId, #Param("parentDecisionId") Long parentDecisionId);
This is WeightedCriterion QueryResult:
#QueryResult
public class WeightedCriterion {
private Criterion criterion;
private Double weight;
}
I need to change this query in order to return a list of Criterion with their average weight not only for a single child decision but for all child decisions of a parent parentDecisionId.. in other word this query should return something like this:
List<DecisionWeightedCriterion>
where DecisionWeightedCriterion looks like for example:
#QueryResult
public class DecisionWeightedCriterion {
private Decision decision;
private List<WeightedCriterion> weightedCriteria;
}
Is it possible and if so, could you please help with this Cypher query ?
I have implemented this query in a following way:
MATCH (parentD:Decision)-[:CONTAINS]->(childD:Decision)
WHERE id(parentD) = {parentDecisionId}
WITH childD, parentD MATCH (parentD)<-[:DEFINED_BY]-(c:Criterion)<-[:VOTED_ON]-(vg:VoteGroup)-[:VOTED_FOR]->(childD) WITH childD, {criterion: c, weight: vg.avgVotesWeight} AS weightedCriterion
RETURN childD AS decision, collect(weightedCriterion) as weightedCriteria
Please correct me if I'm wrong.
I have a Spring Neo4j repository method getAvgVotesWeightForCriterion
#Query("MATCH (d:Decision)<-[:VOTED_FOR]-(v:Vote)-[:VOTED_ON]->(c:Criterion) WHERE id(d) = {0} AND id(c) = {1} RETURN avg(v.weight)")
double getAvgVotesWeightForCriterion(Decision decision, Criterion criterion);
and also I have another method calculateWeight completely written in Java. This method internally in loop uses getAvgVotesWeightForCriterion
public double calculateWeight(Decision decision, List<Criterion> criteria) {
double weight = 0;
for (Criterion criterion : criteria) {
weight += getAvgVotesWeightForCriterion(decision, criterion);
}
return weight;
}
Is it possible to move logic inside calculateWeight method completely to Cypher query ? In order to get something like this:
#Query ....
double calculateWeight(Decision decision, List<Criterion> criteria)
This should do what you want to do.
You group the avg - aggregation per criterion and then just sum the weights.
MATCH (d:Decision)<-[:VOTED_FOR]-(v:Vote)-[:VOTED_ON]->(c:Criterion)
WHERE id(d) = {0} AND id(c) IN {1}
WITH c, avg(v.weight) as weight
RETURN sum(weight)