Good morning,
I want to build a structure in Neo4J where I can handle my users and groups (kind of ACL). The idea is to have for each user and for each group a node with all the details. The groups shall become a graph where a root group will have sub-groups that can have also sub-groups without limit. The relation will be -[:IS_SUBGROUP_OF]- - so far nothing exciting. Every user will be related to a group with -[:IS_MEMBER_OF]- to have a clear assignment. Of course a user can be a member of 1 or more groups. Some users will have a different relation like -[:IS_LEADER_OF]- to identify teamlead of the groups.
My tasks:
Assignment: I can query each member of a group with a simple query, I can even query members of the subgroups using the current logged in and asking user:
MATCH (d1:Group:Local) -- (c:User)
MATCH (d:User) -[:IS_MEMBER_OF|IS_LEADER_OF]- (g:Group:Local)-[:IS_SUBGROUP_OF*0..]->(d1)
WHERE c.login = userLogin
RETURN DISTINCT d.lastname, d.firstname
I get every related user to every group of the current user and below (subgroups). Maybe you have a hint how I cna improve the query or the model.
Approval
Here I am stucked as I want to have all users of the current group from the querying user and all members of all subgroups - except the leader of the current group. The reason behind is that a teamlead shall not be able to approve actions for himself but though for every other member of his group and all members of subgroups including their teamleads.
I tried to use the relations -[:IS_LEADER_OF]- to exclude them but than I loose also the teamleads of the subgroups. Does anyone has an idea how I would either change the model or how I can query the graph to get all users except the teamlead of the current group?
Thanks for your time,
Balael
* EDIT *
I think I am getting close, I just need to understand the results of those both queries:
MATCH (d:User) -- (g:Group) WHERE g.uuid = "xx"
RETURN d.lastname, d.firstname
Returns all user in this group no matter what relationship (leader / member)
MATCH (d:User) -- (g:Group), (g)--(c:User{uuid:"yy"})
RETURN d.lastname, d.firstname
Returns all user of that group except the user c. I would have expected to get c as well in the list with d-users as c is part of that group and should be found with (d:User).
I do not understand the difference between both queries, maybe someone has a hint for me?
You can simplify your query slightly (however this should not have an impact on performance):
MATCH (d:User) -[:IS_MEMBER_OF|IS_LEADER_OF]- (g:Group:Local)-[:IS_SUBGROUP_OF*0..]->(d1:Group:Local)--(c:User{login:"userlogin"})
RETURN DISTINCT d.lastname, d.firstname
Don't completely understand your question, but I assume you want to make sure that d1 and c are not connected by a IS_LEADER_OF relationship. If so, try:
MATCH (d:User) -[:IS_MEMBER_OF|IS_LEADER_OF]- (g:Group:Local)-[:IS_SUBGROUP_OF*0..]->(d1:Group:Local)-[r]-(c:User{login:"userlogin"})
WHERE type(r)<>'IS_LEADER_OF'
RETURN DISTINCT d.lastname, d.firstname
following up on * EDIT * in the question
In a MATCH you specify a path. By definition a path does not use the same relationship twice. Otherwise there is a danger to run into infinite recursion. Looking at the second query in the "EDIT" section above: the right part matches yy's relationship to the group whereas the left part matches all user related to this group. To prevent multiple usage of the same relationship the left part does not hit use yy
Related
I am building a social network that has a specialized audience.
Users are related to each other by three primary relationship types.
[:FRIENDS]->(:USER),
[:WORKS_AT]->(:COMPANY),
[:WORKED_AT]->(:COMPANY),
[:FOLLOWS].
When working through a search scenario (a user wants to find another user), I've given each relationship a "priority" (so to speak).
For example, if a user wants to find another user named "Bart Simpson" - first, we will check co-worker relationships ([:WORKS_AT],[:WORKED_AT]). I've assigned those relationships a priority of 1. That way, "Bart Simpson" who works with me will appear in the search results before "Bart Simpson" - who lives hundreds of miles away in Springfield.
The second priority is [:FRIENDS]->(:USER). Do any of my friends have a friend named "Bart Simpson?" Priority #2.
The last priority is a global search. I don't have any co-workers named "Bart Simpson", my friends don't have any friends named "Bart Simpson" - but I met Bart at a conference, and I want to "friend" him. So, I've added a "Global" search. Find any users named "Bart Simpson".
So far, this is my Cypher:
optional match (u:USER {id:'1'})-[:WORKS_AT|:WORKED_AT]-(w:COMPANY)-[r]-(f:USER)
with collect(f{.*, priority:1,relationship:r.title,type:type(r)}) as user
optional match (u:USER {id: '1'})-[:FRIENDS]-(:USER)-[r:FRIENDS]-(f:USER)
with user + collect(f{.*, priority:2,relationship:r.title,type:type(r)}) as user
optional match (f:USER)
where f.id <> '1'
with user + collect(f{.*, priority:3,relationship:'',type:''}) as user
unwind user as users
with users as user
where toLower(user.last_name) STARTS WITH toLower('Sc') OR toLower(user.first_name) STARTS WITH toLower('Sc')
return distinct user
This is fantastic - however, a user could work at the same company, as well as
be friends, as well as appear in the global search. So - we have the potential for three (or more) "copies" of the same user - with different relationship attributes. The relationship attributes are important because in the app, they provide important context to the search. "Bart Simpson - Works at XYZ Company."
So what I'm really looking for is the ability to either return the user record with the highest priority - and do that based on the "ID" field. If that doesn't work, I could see a situation where we try to update the property of a node. So, when the query hits the priority 2 search, if there is already a user in the collection with the same "ID", it just appends the P2 relationship type to the record. Either is fine with me.
I'm open to suggestions and listening!
So, I've made some progress!
MATCH
(subject:USER {id:'1'})
MATCH
(subject)-[:WORKS_AT|:WORKED_AT]-(w:COMPANY)-[r]-(f1:USER)
WHERE
toLower(f1.last_name) STARTS WITH toLower('Sc') or
toLower(f1.first_name) STARTS WITH toLower('Sc')
WITH
COLLECT(f1.id) AS userIds,
COLLECT(f1{.*,priority:1,rType:type(r), title:r.title, detail:w.name}) AS users
OPTIONAL MATCH
(subject)-[:FRIEND]-(fw:USER)-[r:FRIEND]-(f2:USER)
WHERE
NOT(f2.id in userIds) AND
(
toLower(f2.last_name) STARTS WITH toLower('Sc') or
toLower(f2.first_name) STARTS WITH toLower('Sc')
)
WITH
users + COLLECT(f2{.*,priority:2,rType:"FRIEND", title:"Friends with " + fw.first_name + " " + fw.last_name, detail:''}) AS users,
userIds + collect(f2.id) AS userIds
OPTIONAL MATCH
(f3:USER)
WHERE
NOT(f3.id in userIds) AND
(
toLower(f3.last_name) starts with toLower('Sc') OR
toLower(f3.first_name) starts with toLower('Sc')
)
WITH
users + COLLECT(f3{.*,priority:3,rType:"GLOBAL", title:"", detail:''}) AS users
RETURN
users
The query has evolved a bit. Essentially, at the first stage, we collect the userIds of the items that were returned. At each subsequent stage, the results returned are compared against the running list of ids. If the id of the result is already in the list of ids, it is filtered out - thus ensuring a unique id in the set.
This is working - and for now, I'm going to run with it. Is this the most efficient query, or is there a better way to deal with this scenario?
I have a possibly bone-headed question, but I'm just starting out with Neo4j, and I hope someone can help me out with learning Cypher syntax, which I've just started learning and evaluating.
I have two User nodes, and a single NewsPost node. Both users LIKE the NewsPost. I'm able to construct a Cypher query to count the likes for the post, but I'm wondering if it's also possible to check if the current user has liked the post in the same query.
What I have so far for a Cypher query is
match (p:NewsPost)<-[r:LIKES]-(u:User)
where id(p) = 1
return p, count(*)
Which returns the post and like count, but I can't figure out the other part of "has the current user liked this post". I know you're not supposed to filter on <id>, but I learned that after the fact and I'll go back and fix it later.
So first, is it possible to answer the "has the current user liked this post" question in the same query? And if so, how do I modify my query to do that?
The smallest change to your query that adds a true/false test for a particular user liking the news post would be
MATCH (p:NewsPost)<-[r:LIKES]-(u:User)
WHERE ID(p) = 1
RETURN p, count(r), 0 < size(p<-[:LIKES]-(:User {email:"michael#nero.com"}))
This returns, in addition to your query, the comparison of 0 being less than the size of the path from the news post node via an incoming likes relationship to a user node with email address michael#nero.com. If there is no such path you get false, if there is one or more such paths you get true.
If that does what you want you can go ahead and change the query a little, for instance use RETURN ... AS ... to get nicer result identifiers, and so on.
What you are looking for is Case.
In your database you should have something unique for each user (id property, email or maybe login, I don't know), so you have to match this user, and then match the relation to the post you want, using case you can return a boolean.
Example:
Optional Match (u:User{login:"Michael"})-[r:LIKES]-(p:newPost{id:1})
return CASE WHEN r IS NULL THEN false ELSE true END as userLikesTopic
If you want to get the relation directly (to get a property in it as example) you can remove the CASE part and directly return r, if it does not exist, null will be returned from the query.
my relationships look like this
A-[:CHATS_WITH]->B - denotes that the user have sent at least 1 mesg to the other user
then messages
A-[:FROM]->message-[:SENT_TO]->B
and vice versa
B-[:FROM]->message-[:SENT_TO]->A
and so on
now i would like to select all users a given user chats with together with the latest message between the two.
for now i have managed to get all messages between two users with this query
MATCH (me:user)-[:CHATS_WITH]->(other:user) WHERE me.nick = 'bazo'
WITH me, other
MATCH me-[:FROM|:SENT_TO]-(m:message)-[:FROM|:SENT_TO]-other
RETURN other,m ORDER BY m.timestamp DESC
how can I return just the latest message for each conversation?
Taking what you already have do you just want to tag LIMIT 1 to the end of the query?
The preferential way in a graph store is to manually manage a linked list to model the interaction stream in which case you'd just select the head or tail of the list. This is because you are playing to the graphs strengths (traversal) rather than reading data out of every Message node.
EDIT - Last message to each distinct contact.
I think you'll have to collect all the messages into an ordered collection and then return the head, but this sounds like it get get very slow if you have many friends/messages.
MATCH (me:user)-[:CHATS_WITH]->(other:user) WHERE me.nick = 'bazo'
WITH me, other
MATCH me-[:FROM|:SENT_TO]-(m:message)-[:FROM|:SENT_TO]-other
WITH other, m
ORDER BY m.timestamp DESC
RETURN other, HEAD(COLLECT(m))
See: Neo Linked Lists and Neo Modelling a Newsfeed.
I have a graph which looks like this:
User->Friend->Area
| (Which area does this friend live in)
------------->Area<-Friend<-Other users
(Which area does this user live in)
If i like to find all areas where i dont have friends, i do this
start user=node(reference)
match user-->friend-->area<-[r?:HAVE_A]-friend<--user
where r is null
return area
Works great!
But how to order the results so i get the area which have the most users first?
If I'm understanding your question correctly, you are getting the correct areas returned but want to organize them by the count of users.
In that case you need a WITH clause to get the users, COUNT them, and an ORDER BY to sort them:
START user=node(reference)
MATCH user-->friend-->area<-[r?:HAVE_A]-friend<--user
WHERE r is null
WITH area
MATCH area<--users
RETURN area, COUNT(users) as cnt
ORDER BY cnt DESC
Does it possible to have an order by "property" with a where clause and now the "index/position" of the result?
I mean, when using order for sorting we need to be able to know the position of the result in the sort.
Imagine a scoreboard with 1 million user node, i do an order by on user node.score with a where "name = user_name" and i wan't to know the current rank of the user. I do not find how to do this using order by ...
start game=node(1)
match game-[:has_child_user]->user
with user
order by user.score
with user
where user.name = "my_user"
return user , "the position in the sort";
the expected result would be :
node_user | rank
(i don't want to fetch one million entries at client side to know the current rank/position of a node in the ORDER BY!)
This functionality does not exist today in Cypher. Do you have an example of what this would look like in SQL? Would the below be something that fits the bill? (just a sketch, not working!)
(your code)
start game=node(1)
match game-[:has_child_user]->user
with user
order by user.score
(+ this code)
with user, index() as rank
return user.name, rank;
If you have more thoughts or want to start hacking on this please open an issue at https://github.com/neo4j/neo4j/issues
For the time being there is a work around that you can do:
start n=node(0),rank_node=node(1)
match n-[r:rank]->rn
where rn.score <= rank_node.score
return rank_node,count(*) as pos;
For live example see: http://console.neo4j.org/?id=bela20