Neo4J checking if a user follows me back - neo4j

I am building a follower model with neo4j. User A [:Follows] -> user B and user B can do the same.
A is follower of B. Getting this information is straight forward. However I also want to get information at the same time if B [:Follows] -> A or not.
Is there a way to pull out this information?

this query will give you the followers of A and a true|false for each of them indicating if (s)he follows back
MATCH (ua:User {name:"bob"})-[:Follows]->(ub:User)
RETURN ub.name AS ubName,
EXISTS((ub)-[:Follows]->(ua)) AS followsBack

You can do it by adding an OPTIONAL MATCH of the reverse path and then assigning this path to a variable. Then you test the length of the variable in the RETURN using a CASE statement to get the results
E.G.
MATCH (ua:User {name:"bob"})-[:Follows]->(ub:User {name:"Bill"})
OPTIONAL MATCH p = (ub)-[r:Follows]->(ua)
RETURN ua.name, ub.name, CASE WHEN LENGTH(p) > 0 THEN 'yes' ELSE 'no' END AS follows_back
This would return either:
"bob", "bill", "yes"
or
"bob", "bill", "no"

Related

Fallback traversal method when no value is found in Node

I'm quite new to Neo4J, so pardon if my question looks silly, but I'm wondering if it is possible to do a fallback match if no value (null) is found in my original query.
This is how the graph looks like:
User1 - linked -> Channel 1
User1 - linked -> Channel 2
Channel1 - has -> property1_channel_1, null , property3_channel_1
Channel2 - has -> property1_channel_2, property2_channel_2, property3_channel_2
In this example I'd expect this response to be returned when I query for data User1, Channel1:
property1_channel_1, property2_channel_2, property3_channel_1
Is that possible?
Edit:
How can I structure my query so that if a property - let's say quantity, in one of my products is missing to get its default value from the same product in BASE. Is that possible? In the example the trousers in the BASE is complete entity, but the trousers in the EN channel is incomplete.
I guess something like this
// get all the channels the user is linked to
MATCH (u:User)-[:LINK]->(c:Channel)
WHERE u.name='User1'
// put them in the right order
WITH c ORDER BY c.name
// merge the property maps using apoc.map.mergeList()
RETURN apoc.map.mergeList(COLLECT(properties(c)) AS properties

Join Keys from 2 nodes in a single return value in Neo4j 3.2.15 and cypher-3.1

I'm trying to get a "Friend Recommendation" query. The nodes have the next sequence
(Node) - [FRIEND] - (Node) - [INFO] - (P_info)
where every node have a INFOrelation with a P_info node asociated. I can get a list of recommended friends of a Node but i need to include the P_info keys into recommended friends keys to return all together.
This is my query at the moment:
match (person:Account{_id:"185860469"})
match (person)-[:FRIEND]-()-[:FRIEND]-(potentialFriend)
where not (person)-[:FRIEND]-(potentialFriend)
match (potentialFriend)-[:INFO]-(information:P_info)
with person,potentialFriend, COUNT(*) AS friendsInCommon,information
where friendsInCommon > 5
return {user:person,recommend:collect(potentialFriend)},{info:information}
but the information of "info" is not asociated with "potentialFriend" at the response.
I want to do something like this:
return {user:person,collect(potentialFriend,information)} but i don't know if it's possible, cypher says:
Too many parameters for function 'collect'
thanks in advance.
I did it, just adding an additional WITH.I leave the answer in case someone helps
WITH person,{friend_id:potentialFriend._id,friend_name:information.name} AS Recommended_friend
RETURN person._id,collect(Recommended_friend)
this will return an unique response with person id and an array with all the recommended friends for him.

Query/double OPTIONAL MATCH issue

I think this is a long question for what's likely a simple answer. However I thought it wise to include the full context in case there's something wrong with my query logic (excuse the formatting if it's off - I've renamed the vars and it may be malformed, I need help with the theory and not the structure)
An organisation can have a sub office
(o:Organisation)-[:sub_office]->(an:Organisation)
Or a head office
(o)-[:head_office]->(ho:Organisation)
Persons in different sub offices can be employees or ex-employee
EX1
(o)-[:employee]->(p:Person{name:'person1'})<-[:ex_employee]-(an)
Persons can be related to other people through the management relationships. These management links can be variable length.
EX2
(o)-[:employee]->(p:Person{name:'person2'})-[:managed]->(p:Person{name:'person3'})<-[:ex_employee]-(an)
(o)-[:ex_employee]->(p:Person{name:'person4'})-[:managed]->(p:Person{name:'NOT_RETURNED1'})-[:managed]->(p:Person{name:'person5'})<-[:employee]-(an)
(o)-[:ex_employee]->(p:Person{name:'person6'})<-[:managed]-(p:Person{name:'NOT_RETURNED2'})<-[:managed]-(p:Person{name:'person8'})<-[:employee]-(an)
(o)-[:ex_employee]->(p:Person{name:'person9'})-[:managed]->(p:Person{name:'NOT_RETURNED4'})-[:managed]->(p:Person{name:'NOT_RETURNED5'})<-[:managed]-(p:Person{name:'person11'})<-[:employee]-(an)
....
I'm querying:
-organisation,
-sub office,
-how they're related
These are all working fine (I think...)
The issues I'm having is with returning Persons associated with the orgs (employees or ex employees) and their relationships to the organisation but only if they are connected to the other organisation directly (as in EX1) or through a managed chain (all of EX2 - I've tried to make it clearer by marking the Persons who won't be returned by the query as name 'NOT_RETURNED')
I've created the following:
MATCH (queryOrganisation:Organisation{name:'BigCorp'})-[orgRel]-(relatedOrganisation:Organisation)
WITH queryOrganisation, orgRel, relatedOrganisation
MATCH (queryOrganisation)-[employmentRel]->(queryPerson:Person)
OPTIONAL MATCH (queryPerson)<-[relatedOrgRel]-(relatedOrganisation)
OPTIONAL MATCH (queryPerson)-[:managed*1..]-(relatedPerson:Person)<-[relatedOrgRel]-(relatedOrganisation)
WITH queryOrganisation, orgRel, relatedOrganisation, employmentRel, queryPerson, relatedOrgRel, relatedPerson
WHERE NOT queryOrganisation.name = relatedOrganisation.name
RETURN ID(queryOrganisation) as queryOrganisationID,
ID(startNode(orgRel))as startNodeId, type(orgRel)as orgRel, ID(endNode(orgRel))as endNodeId,
ID(relatedOrganisation)as relatedOrganisationId, relatedOrganisation.name as relatedOrganisationName
COLLECT({
queryPerson:{endpoint:{ID:ID(queryPerson)}, endpointrelationship: type(employmentRel)},
relatedPerson:{endpoint:{ID:coalesce(ID(relatedPerson),ID(queryPerson))}, endpointrelationship:type(relatedOrgRel)}
}) as rels
I would have expected all the collected results to look like:
{
"startEmp":{
"ID":2715,
"startrelationship":"employee"
},
"relatedEmp":{
"ID":2722,
"endrelationship":"ex employee"
}
}
However the directly connected node results (same node ID) appear like:
{
"startEmp":{
"ID":2716,
"startrelationship":"employee"
},
"relatedEmp":{
"ID":2716,
"endrelationship":null
}
}
Why is that null appearing for type(relatedOrgRel)? Am I misunderstanding whats happening in the OPTIONAL MATCH and the relatedOrgRel gets overwritten by null during the second OPTIONAL MATCH? If so, how can I remedy?
Thanks
No, the OPTIONAL MATCHes cannot overwrite variables that are already defined.
I think the cause of the problems is when your second OPTIONAL MATCH doesn't match anything, but this is partially covered up by the COALESCE used in the collecting of persons in your return hides some of the conseque:
...
relatedPerson:{endpoint:{ID:coalesce(ID(relatedPerson),ID(queryPerson))}, endpointrelationship:type(relatedOrgRel)}
...
If relatedPerson is null, as it will be if your second OPTIONAL MATCH fails, then you're falling back to the id of queryPerson, but since you're not using a COALESCE for relatedOrgRel, this will still be null. You'll need a COALESCE here, or otherwise you'll need to figure out a better way to deal with the null variables in your OPTIONAL MATCHES in cases where they fail.

Any way to whitelist or blacklist properties returned from a node?

Nodes returned in neo4j seem to be special, in that they output as JSON objects, and they don't appear at all if they're null.
An example:
I have a :Person object, and they can have 0 or more :Friend relationships to another :Person.
Let's say that a :Person has the following properties: ID, firstName, lastName, sensitiveThing.
sensitiveThing is a property that might be used by our system, or could be personally accessible to the user themselves, but we don't want to return it to any other user.
If I want a query to give me back data of my friends, and friends of those friends, I might use a query like this
MATCH (me:User{ID:777})-[:Friend]-(friend:User)
WITH me, friend
OPTIONAL MATCH (friend)-[:Friend]-(foaf:User)
WHERE me <> foaf
RETURN friend.ID, friend.firstName, friend.lastName, COLLECT(foaf) AS FriendOfAFriend
While this lets me nicely bundle up friends of friends as JSON objects within a JSON array, and correctly emits an empty array if a friend doesn't have any other friends besides me, I don't want to return sensitiveThing with this query.
If I try to replace COLLECT(foaf) with a custom object only including fields I care about, like this:
COLLECT({ID:(foaf.ID), firstName:(foaf.firstName), lastName:(foaf.lastName)})
then I get what I want...until I hit the case where there are no friends of friends. When I was working with nodes before, the object wouldn't even be emitted. But now, I would get something like this returned to me:
[{ID: (null), firstName: (null), lastName: (null)}]
This is obviously not what I want.
Ideally, I'm looking for a way to return a node as before, but whitelist or blacklist properties I want to emit, so I can retain the correct null handling if the node is null (from an optional match)
If I can't have that, then I'd like a way to use a custom object, but not return the object at all if all its fields are null.
Any other workarounds or tips for dealing with optional matches are more than welcome.
You can use apoc.map.removeKeys:
WITH {p1: 1, p2: 2, p3: 3, p4: 4} as node
CALL apoc.map.removeKeys( node, ['p2', 'p4'] ) YIELD value
RETURN value
I've never seen a way to whitelist or blacklist properties in the documentation.
However, you can return your custom object by chaining collect with extract:
MATCH (me:User{ID:777})-[:Friend]-(friend:User)
WITH me, friend
OPTIONAL MATCH (friend)-[:Friend]-(foaf:User)
WHERE me <> foaf
WITH friend, collect(foaf) AS FriendOfAFriend
RETURN friend.ID, friend.firstName, friend.lastName,
extract(foaf in FriendOfAFriend | {ID:(foaf.ID), firstName:(foaf.firstName), lastName:(foaf.lastName)}) AS FriendOfAFriend
collect will return an empty list if there are no friends, extract will keep it that way.

Returning like count and whether current user liked a post in Cypher

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.

Resources