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.
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 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.
I would like to return for a given node-id related nodes and their relationships props
For example:
-> defines a bi direction relationship with property timestamp
1234->777
777->1234
1234->999
999->1234
1234->888
888->1234
1234,777,888,999 are node-ids
When I execute this:
final PreparedStatement ps = conn.prepareStatement("start a = node(1234) match (a)-[k:nearby*]->(b) where a<>b return DISTINCT b, k");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
Map result = (Map<String, Object>) rs.getObject("b");
System.out.println(result.toString());
}
} catch (SQLException e) {
e.printStackTrace();
logger.error("Error returning userId=" + userIdInput, e);
}
return null;
}
I get:
{userId=777}
{userId=999}
{userId=888}
{userId=888}
{userId=999}
{userId=999}
{userId=777}
{userId=888}
{userId=888}
{userId=777}
{userId=888}
{userId=777}
{userId=999}
{userId=999}
{userId=777}
How I do get distinct results only (777,888,999)
How to retrieve the relationship props of 1234 to the dest node? I expect to get the timestamp prop which defined on each relationship
Thank you,
ray.
I'm not sure what language you're using so I'll focus on the Cypher. Firstly I would replace the START query with a MATCH with a WHERE on ID(a):
MATCH (a)-[k:nearby*]->(b)
WHERE ID(a) = 1234 AND a<>b
RETURN DISTINCT b, k
Secondly I'm pretty sure you don't need the a<>b because Cypher paths won't loop back on the same nodes:
MATCH (a)-[k:nearby*]->(b)
WHERE ID(a) = 1234
RETURN DISTINCT b, k
Lastly, and to your question, I suspect the reason that you're getting duplicates is because you have multiple relationships. If so you can return the result node and an array of the relationships like so:
MATCH (a)-[k:nearby*]->(b)
WHERE ID(a) = 1234
RETURN collect(b), k
That should return you node/relationship objects (with properties on both). Depending on your language/library you might get Maps or you might get objects wrapping the data
If your library doesn't return the start/end nodes for relationships for you, you can do something like this:
MATCH (a)-[k:nearby*]->(b)
WHERE ID(a) = 1234
RETURN collect({rel: b, startnode: startnode(b), endnode: endnode(b)}), k
Hopefully that helps!
You get non distinct results, because you return both b and k
If you only want to get distinct b's use:
MATCH (a)-[k:nearby*]->(b)
WHERE ID(a) = 1234 AND a<>b
RETURN DISTINCT b
You should also use parameters!
MATCH (a)-[k:nearby*]->(b)
WHERE ID(a) = {1} AND a<>b
RETURN DISTINCT b
ps.setInt(1,1234);
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)