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.
Related
cql1:MATCH(A:SYSTEM{NAME:'HR'})-[R]-(B:SYSTEM{NAME:'ERP'})RETURN type(R)
cql2:MATCH(A:SYSTEM{NAME:'HR'})-[R]-(B:SYSTEM{NAME:'ERP'})RETURN startNode(R),endNode(R),R
cql3:MATCH(A:SYSTEM{NAME:'HR'})-[R]-(B:SYSTEM{NAME:'ERP'})RETURN startNode(R),endNode(R),R,type(R)
cql1:I can package it in List ,and the cq2,I can use a RelationshipEntity to pack;
but cql3,I try to get the type of relationship ,it fail;and said:Unable to find property: type(R);
my entity like that
#Relationship(type = "T_OUT")
private OUTBasis outBasis ;//pack startNode(R),endNode(R),R
#Property
private String relationType ;//pack type(R)
so,how can i recive the cql3 result by the entity ?
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
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 an Account object that has a "Incoming" relation with other Account Objects.
I need a couple of cypher queries that retrieve a complete list of accounts sorted by the number of followers each Account.
Query 1: If, Account One has to 200 followers and Account Two has 100, then Account One will be at the top of the list. The parameter resultSize will be the size of the top n result.
#Query("...")
List<Account> findSortedAccountByFollowers(int resultSize)
Query 2: In addition to above, this takes 2 parameters (maxCount and resultSize). It should return only those accounts that have followers less then maxCount e.g. if maxCount is 200 then return those accounts that have followers less 200 sorted by highest to low. Result Size is defined by the parameter resultSize
Here is the Model Class
#NodeEntity
public class Account implements Serializable{
#GraphId
private Long id;
...
#Fetch
#RelatedTo(type="follows",
direction= Direction.OUTGOING,
elementClass = Account.class)
private Set<Account> following = new HashSet<Account>;
#Fetch
#RelatedTo(type="follows",
direction= Direction.INCOMING,
elementClass = Account.class)
private Set<Account> followers = new HashSet<Account>;
...
}
[UPDATED]
To implement this:
List<Account> findSortedAccountByFollowers(int resultSize)
this query should work:
MATCH (a:Account)<-[:follows]-(b:Account)
WITH a, COLLECT(b) AS bs
ORDER BY SIZE(bs) DESC
RETURN a
LIMIT {0};
To implement this:
List<Account> findCappedSortedAccountByFollowers(int maxCount, int resultSize)
this query should work:
MATCH (a:Account)<-[:follows]-(b:Account)
WITH a, COLLECT(b) AS bs
WHERE SIZE(bs) <= {0}
RETURN a
ORDER BY SIZE(bs) DESC
LIMIT {1};
I have a two-way [:FRIENDSHIP] relationship between User nodes:
(UserA)<-[:FRIENDSHIP {approved:true}]->(UserB)
Here's a little test function in Java to setup the relationship:
public void approveFriendship(User requestor, User requestee) {
Friendship friendship = new Friendship(requestor, requestee);
friendship.setApproved(true);
Friendship invertedFriendship = new Friendship(requestee, requestor);
invertedFriendship.setApproved(true);
requestor.getFriendships().add(friendship);
requestee.getFriendships().add(invertedFriendship);
userRepository.save(requestor);
userRepository.save(requestee);
}
This cypher query returns the friends of UserA, and works fine:
start user=node({0})
match (user)-[r?:FRIENDSHIP]->(friends)
where r.approved = true
return friends
This cypher query returns the posts of a friend, and does not work (returns empty result):
start n=node({0})
match (n)<-[r?:FRIENDSHIP]->(friend)<-[:AUTHOR]-(friendposts)
where r.approved = true
return friendposts order by friendposts.createdAt
When omitting the where r.approved = true line or changing it to where r.approved = false it returns posts of friends without the approved status in both cases.
Can anybody spot if I'm doing something wrong here? Much obliged.
Solved it.
Dunno why, but in a one-to-many type relationship (eg. User to Post) Neo4J prefers outgoing relationships on the Collection, and the incoming on the single entity. I had it the other way round...
Now my classes look like this:
public class User {
#RelatedTo(type = "AUTHOR")
private Set<Post> posts;
}
public class POST {
#RelatedTo(type = "AUTHOR", direction = Direction.INCOMING)
private User author;
}
And of course the cypher query has to change to reflect the new relationship direction (note arrow behind author):
start n=node({0})
match (n)<-[r?:FRIENDSHIP]->(friend)-[:AUTHOR]->(friendposts)
where r.approved = true
return friendposts order by friendposts.createdAt