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)
Related
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 }
I have a following SDN 4 entities:
Decision, Characteristic and Value:
#NodeEntity
public class Value extends Votable {
private final static String SET_FOR = "SET_FOR";
private final static String SET_ON = "SET_ON";
private final static String CONTAINS = "CONTAINS";
#Relationship(type = SET_FOR, direction = Relationship.OUTGOING)
private Decision decision;
#Relationship(type = SET_ON, direction = Relationship.OUTGOING)
private Characteristic characteristic;
#Index(unique = false)
private Object value;
...
}
I have created 3 Decicion(Decicion1, Decision2, Decisison3) nodes and 1 Characteristic(Characteristic1).
For Decicion1, Decicion2 and Characteristic1 I have created a Double Value, for example:
Decision1 + Characteristic1 = Value(500d)
Decision2 + Characteristic1 = Value(1000d)
Decicion3 doesn't have any Value on Characteristic1
I need to create a query that will return all Decision which have Value in the defined range, for example 100 <= value <= 50000
Based on the example above this query should return Decision1 and Decicion2 only.
Right now I have a following query:
MATCH (parentD)-[:CONTAINS]->(childD:Decision)-[ru:CREATED_BY]->(u:User)
WHERE id(parentD) = {decisionId}
AND ALL(key IN keys({rangeFilters})
WHERE size(
[(childD)<-[:SET_FOR]-(filterValue)-[:SET_ON]->(filterCharacteristic) WHERE id(filterCharacteristic) = toInt(key) AND ({rangeFilters}[key])[0] <= filterValue.value <= ({rangeFilters}[key])[1] | 1]
) > 0)
RETURN ru, u, childD AS decision SKIP 0 LIMIT 100
where rangeFilters is a Map<String, Double[]> where key is a Characteristic ID and value new Double[] { new Double(100.d), new Double(50000d) }
But this query returns all 3 Decision, even the Decision3 that don't have any values associated with Characteristic1 .
How to fix this query in order to return only Decisions that match a condition ?
UPDATED
This is an example that highlights the issue: http://console.neo4j.org/?id=6bv9y5
UPDATED
I'm tried to apply solution described by Tezra. This is my curent query:
MATCH (parentD)-[:CONTAINS]->(childD:Decision)-[ru:CREATED_BY]->(u:User)
WHERE id(parentD) = {decisionId}
MATCH (childD)<-[:SET_FOR]-(filterValue)-[:SET_ON]->(filterCharacteristic)
WHERE
ALL(key IN keys({equalFilters}) WHERE id(filterCharacteristic) = toInt(key) AND filterValue.value = ({equalFilters}[key]))
AND
ALL(key IN keys({rangeFilters}) WHERE id(filterCharacteristic) = toInt(key) AND ({rangeFilters}[key])[0] <= filterValue.value <= ({rangeFilters}[key])[1])
RETURN ru, u, childD AS decision SKIP 0 LIMIT 100
Unfortunately assertion in my tests fails on this query.
This query works fine only when I specify the single filter condition, for example:
ALL(key IN keys({equalFilters}) WHERE id(filterCharacteristic) = toInt(key) AND filterValue.value = ({equalFilters}[key]))
or
ALL(key IN keys({rangeFilters}) WHERE id(filterCharacteristic) = toInt(key) AND ({rangeFilters}[key])[0] <= filterValue.value <= ({rangeFilters}[key])[1])
but doesn't work when both of them are present. What am I doing wrong ?
Based on the example data, you just need to link all the checks correctly. (the parent stuff isn't in the example, but I think it was just the key-range part messing you up)
WITH {c1:[100,50000]} AS rangeFilters
MATCH (childD:Decision)<--(fv:FilterValue)-->(c:FilterCharacteristic)
WHERE ALL(key IN keys(rangeFilters)
WHERE c.id=key AND rangeFilters[key][0] < fv.value < rangeFilters[key][1])
RETURN childD
Although you could also make FilterValue a relationship since relationships can have properties too.
UPDATE:
As for your problem with WHERE ALL( equals...) AND ALL(in_range...); That reads "Where all equal filters are true and all range filters are true". So I'm guessing that you actually want is any of them to be true which would be WHERE ANY (equals...) OR ANY(in_range) (available predicates), as it is impossible for filter.value to equal 7 and 9 at the same time.
Also, as a side note, don't use id() in your queries, as Neo4j reserves the right to change those as much or little as it likes. Set your own id fields, and use a UUID instead. A UUID is also much more reliable if you need to merge data sets.
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.
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.