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
Related
In my Spring Boot, Neo4j, SDN 5 OGM project I have the following composite entity:
#NodeEntity
public class User extends BaseEntity {
private static final String REFERRED_BY = "REFERRED_BY";
#Relationship(type = REFERRED_BY, direction = Relationship.OUTGOING)
private User referrerUser;
}
As you may see, one user may be referred by another.
Now, I'd like to receive a paginated result:
#Query(value = "MATCH (u:User) " +
"OPTIONAL MATCH (u)-[ruru:REFERRED_BY]->(ru:User) " +
"RETURN u, ruru, ru",
countQuery = "MATCH (u:User) " +
"OPTIONAL MATCH (u)-[ruru:REFERRED_BY]->(ru:User) " +
"RETURN count(u)")
Page<User> findUsers(Pageable pageable);
I request 0-50 users. But in the real data - one of them are referred by another:
Pageable pageable = PageRequest.of(0, 50, Sort.by(Sort.Direction.DESC, "u.createdAt"));
Page<User> userPage = userRepository.findUsers(pageable);
I expect not more than 50 items but as the result I receive 51 users in the Page.getContent()...
Is this possible to hint somehow to no include User.referrerUser into the Page.content collection and just leave it as another user entity property (reference)?
What am I doing wrong and hot to fix it?
I solved the issue via #QueryResult
my relationship graph as below pic
Using MATCH (n:Person {name:'1'})-[]-()-[r]-(m) RETURN m,r will return "4" and "5",but cannot get the relationship between "4" and "5",actually,"4" follow "5","r" just represent the friends of 4(4-->2) and 5(5-->3).
In spring data neo4j
domain.java
#NodeEntity(label = "Person")
public class PersonTest {
#Id
private Long id;
#Property(name = "name")
private String name;
#Relationship(type = "Follow") //direction=Relationship.DIRECTION
private List<PersonTest> friends;
Repository.java
public interface PersonRepository extends Neo4jRepository<PersonTest,Long> {
#Query("MATCH (n:Person {name:{name}})-[]-()-[r]-(m) RETURN m,r")
Collection<PersonTest> graph(#Param("name") String name);
}
Service.java
Collection<PersonTest> persons = personRepository.graph(name);
Iterator<PersonTest> result = persons.iterator();
while (result.hasNext()) {
for (PersonTest friend : p.getFriends()) {
//........here will get 2 and 3!
}
}
How to resolve this problem??get the relationship between "4" and "5".
To find related children at a certain level, you can use a two-sided pattern of variable length:
MATCH (n:Person {name:'1'})-[:Follow*2]->(m)-[r:Follow]-()<-[:Follow*2]-(n)
RETURN m,r
http://console.neo4j.org/r/el2x80
Update. I think that this is a more correct query:
MATCH (n:Person {name:'1'})-[:Follow*2]->(m:Person)-[r:Follow]-(k:Person)
WHERE (n)-[:Follow*2]->(k)
RETURN m, r
http://console.neo4j.org/r/bv2u8k
We have an entity like this
#NodeEntity(label = "User")
public class UserEntity {
#GraphId
private Long mId;
#Property(name = "address")
private String address;
#Relationship(type="FRIEND_WITH", direction = Relationship.INCOMING)
private List<UserEntity> friends;
#Relationship(type="OWNS")
private List<CarEntity> cars;
and we would like to retrieve a list of User hydrated up to one level with the collections ordered by a property (creationDate).
We started with this but we don't know how to order the collections
MATCH p = (u:User) - [*0..1] - () WHERE <condition> RETURN nodes(p), relationships(p)
Keeping order of results in mapped collections becomes ambiguous when your path has length > 2 and can have cycles.
If you want to impose order on collections of relationships you have several options:
use a -SortedSet- and implement Comparable in your entities
sort relationships in setter method
You can sort either by a property of a related node or by relationship property - for that you need a relationship entity.
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
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.