Neo4j - Specific relationship vs Label - neo4j

[newbie question] I'm kinda in doubt if it's better using specific relationships or labels, but first let me give you a little bit more context.
Suppose that the graph should be able to answer the following questions/queries:
Given a person, return all the emails associated;
Given a person, return all the contacts;
I've come up with these 2 possible models:
They're both able to fulfill the required requests with the following queries:
First model:
MATCH (p:Person {name:"Bob"})-[:REACHABLE_BY]-(e:Email)
RETURN p.name AS Name, e.contact​ AS Contact
MATCH (p:Person {name:"Bob"})-[:REACHABLE_BY]-(c:Contact)
RETURN p.name AS Name, c.contact​ AS Contact
Second model:
MATCH (p:Person {name:"Bob"})-[:REACHABLE_BY_EMAIL]-(c:Contact)
RETURN p.name AS Name, c.contact​ AS Contact
MATCH (p:Person {name:"Bob"})-[:REACHABLE_BY_FAX]-(c:Contact)
RETURN p.name AS Name, c.contact AS Contact
UNION ALL
MATCH (p)-[:REACHABLE_BY_EMAIL]-(c2:Contact)
RETURN p.name AS Name, c2.contact AS Contact
But I'm wondering if there's a best practice to follow in this case. I mean, I know that having specific relationships in some cases is better since we reduce the number of nodes involved in the query (instead of filtering later by some property), but I feel like that in this case we can achieve the same result (maybe also in performance) by considering different labels.

Both of your models will work fine. But to obtain the best performance, you can combine these two models into a single one. Like this:
All the Contact nodes that store emails, will have Email label as well.
All the Contact nodes that store faxes will have Fax label as well.
Relationship type between Person and Email types node will be REACHABLE_BY_EMAIL
Relationship type between Person and Fax types node will be REACHABLE_BY_FAX
Using this model, you can easily query a person's email or by these queries:
MATCH (p:Person)-[:REACHABLE_BY_EMAIL]->(email)
RETURN p, email
MATCH (p:Person)-[:REACHABLE_BY_FAX]->(fax)
RETURN p, fax
Note, that I have not specified Email or Fax labels in the query, as they are redundant.
Also, now you can query your the emails and faxes, using simply
MATCH (e:Email) RETURN e
MATCH (f:Fax) RETURN f
If the need arises.

You can also use
(:Person)-[:REACHABLE_BY]->(:Contact)-[:HAS_TYPE]->(:ContactType)
with 'Fax', 'Phone', 'Email' as ContactType nodes.
In your queries, using directions will help you speed up things. Your 2nd query of the second model can be written as
MATCH (p:Person {name:"Bob"})-->(c:Contact)
RETURN p.name AS Name, c.contact AS Contact
For the model I suggest, the queries would be:
MATCH (p:Person {name:"Bob"})-->(c:Contact)-->(:ContactType {name:'Email'})
RETURN p.name AS Name, c.contact​ AS Contact
and
MATCH (p:Person {name:"Bob"})-->(c:Contact)
RETURN p.name AS Name, c.contact​ AS Contact

Related

How to do this in a single Cypher Query?

So this is a very basic question. I am trying to make a cypher query that creates a node and connects it to multiple nodes.
As an example, let's say I have a database with towns and cars. I want to create a query that:
creates people, and
connects them with the town they live in and any cars they may own.
So here goes:
Here's one way I tried this query (I have WHERE clauses that specify which town and which cars, but to simplify):
MATCH (t: Town)
OPTIONAL MATCH (c: Car)
MERGE a = ((c) <-[:OWNS_CAR]- (p:Person {name: "John"}) -[:LIVES_IN]-> (t))
RETURN a
But this returns multiple people named John - one for each car he owns!
In two queries:
MATCH (t:Town)
MERGE a = ((p:Person {name: "John"}) -[:LIVES_IN]-> (t))
MATCH (p:Person {name: "John"})
OPTIONAL MATCH (c:Car)
MERGE a = ((p) -[:OWNS_CAR]-> (c))
This gives me the result I want, but I was wondering if I could do this in 1 query. I don't like the idea that I have to find John again! Any suggestions?
It took me a bit to wrap my head around why MERGE sometimes creates duplicate nodes when I didn't intend that. This article helped me.
The basic insight is that it would be best to merge the Person node first before you match the towns and cars. That way you won't get a new Person node for each relationship pattern.
If Person nodes are uniquely identified by their name properties, a unique constraint would prevent you from creating duplicates even if you run a mistaken query.
If a person can have multiple cars and residences in multiple towns, you also want to avoid a cartesian product of cars and towns in your result set before you do the merge. Try using the table output in Neo4j Browser to see how many rows are getting returned before you do the MERGE to create relationships.
Here's how I would approach your query.
MERGE (p:Person {name:"John"})
WITH p
OPTIONAL MATCH (c:Car)
WHERE c.licensePlate in ["xyz123", "999aaa"]
WITH p, COLLECT(c) as cars
OPTIONAL MATCH (t:Town)
WHERE t.name in ["Lexington", "Concord"]
WITH p, cars, COLLECT(t) as towns
FOREACH(car in cars | MERGE (p)-[:OWNS]->(car))
FOREACH(town in towns | MERGE (p)-[:LIVES_IN]->(town))
RETURN p, towns, cars

Return type matched from multiple relationships in Neo4j with Cypher

I know that you can match on multiple relationships in Neo4j, like this example in the docs:
MATCH (wallstreet {title: 'Wall Street'})<-[:ACTED_IN|:DIRECTED]-(person)
RETURN person.name
which returns nodes with an ACTED_IN or DIRECTED relationship to 'Wall Street'.
However, is there a way to get the type of the relationship in this query? That is, I would like to return not only the name, but also which relationship applies to him/her, in order to see if it was the ACTED_IN, or the DIRECTED relationship that caused the result to be output.
You can do the equivalent here:
MATCH (:Person {name: 'Oliver Stone'})-[r]->(movie)
RETURN type(r)
but that's just matching on any relationship. I would like to do this, but only with the two relationships specified in the clause.
Thanks
You no longer need additional colons in between valid edge types you are querying. otherwise you can use the variable just like you did in the unspecific edge case:
MATCH (:Movie{title: 'The Matrix'})<-[r:ACTED_IN|DIRECTED]-(person)
RETURN type(r), person.name

Unwind array and match with other nodes using Cypher on Neo4j graph database

I'm creating an app using GRANDstack on top of a Neo4j graph database and I'm struggling with writing a query that unwinds a property array, matches some IDs, pullsback names associated with the IDs and repackages them into a new array.
Essentially, I have Memory nodes (Mems) and Person nodes (Person) and their relationship is as follows: (Mems)-[WITH]->(Person). i.e. you can have a memory of something where you were WITH multiple people. The Mems nodes contain a personID property that is an array of IDs and the relationship in the graph database is built from the array of personIDs in Mems nodes that match the personIDs in the Person nodes.
The result I'm trying to achieve is like the below, however I haven't found a way to return the nicknames from the Person nodes:
The query I'm using to generate the above is below:
MATCH (m:Mem)-[WITH]->(p:Person)
WHERE m.personID IS NOT NULL
UNWIND m.personID as personID
CALL {
WITH personID
MATCH (p:Person)
WHERE personID = p.personID
RETURN p.nickname
}
RETURN m.mem, m.date, personID, p.nickname
I think it's close to what I need but I just can't figure out the last bit where I can return the nicknames associated with the personIDs. This might be something that APOC is more capable of doing?
[UPDATED]
This should work for you:
MATCH (m:Mem)-[:WITH]->(p:Person)
WHERE p.personID IN m.personID
RETURN m.mem, m.date, p.personID, p.nickname
Also, notice that the relationship pattern must use:WITH instead of WITH if you want to query for the WITH relationship type.
Addendum
Of course, since you have the WITH relationships, you should consider deleting the redundant m.personID property. You should get the same results without m.personID this way:
MATCH (m:Mem)-[:WITH]->(p:Person)
RETURN m.mem, m.date, p.personID, p.nickname

How to get nodes specifically related to two nodes and not others in neo4j?

I have two types of labels User and Conversation
User have outgoing relation with Conversation
(u:User)-[:IS_PARTICIPANT]->(c:Conversation)
Multiple Users can be participant of the Conversation.
Now what I am trying to query is for conversation that is specifically between two users only.
for ex.
MATCH (p1:User {name: 'Tom'})-[:IS_PARTICIPANT]->(c:Conversation)<-[:IS_PARTICIPANT]-(p2:User {name:"Jerry"})
return c
Above query does return conversation between two users Tom and Jerry but it will also return this conversation even if there is other user Tweety in that specific conversation. Is there a way in cypher we can specifically get conversation where only specific users are participant of and not others.
Find the node, and find out how many others are connected to it:
MATCH (p1:User {name: 'Tom'})
-[:IS_PARTICIPANT]->(c:Conversation)<-[:IS_PARTICIPANT]
-(p2:User {name:"Jerry"})
MATCH (c)<-[:IS_PARTICIPANT]-(u:User)
WITH c,
COUNT(u) AS countUser WHERE countUser = 2
return c
If nodes are more then two:
WITH ["Tom", "Jerry", "Tweety"] as names
MATCH (p:User)-[:IS_PARTICIPANT]->(c:Conversation)
WHERE p.name IN names
WITH distinct c,
names
MATCH (c)<-[:IS_PARTICIPANT]-(u:User)
WHERE u.name in names
WITH distinct c,
names,
count(distinct u) as countUser
WHERE countUser = size(names)
RETURN c
There are a few tricks we can use here.
First, given an arbitrary-sized collections of names (where you know all the names correspond with :Users), this knowledge base entry can be helpful for determining when all of the given nodes have a relationship to the same node.
Second, if the :IS_PARTICIPANT relationship always only connects :User and :Conversation nodes, we can use size(()-[:IS_PARTICIPANT]->(c)) to efficiently get the number of incoming :IS_PARTICIPANT relationships to a conversation without having to pay the cost of actually expanding those relationships.
WITH ["Tom", "Jerry", "Tweety"] as names // should be parameterized instead
WITH names, size(names) as requiredCount
MATCH (u:User)
WHERE u.name in names
WITH u, requiredCount
MATCH (u)-[:IS_PARTICIPANT]->(c:Conversation)
WITH requiredCount, c, count(u) as matches
WHERE requiredCount = matches and size(()-[:IS_PARTICIPANT]->(c)) = requiredCount
RETURN c
Do you have something like this in mind?
MATCH (p1:User {name: 'Tom'})-[:IS_PARTICIPANT]->(c:Conversation)<-[:IS_PARTICIPANT]-(p2:User {name: 'Jerry'})
WHERE NOT (c:Conversation)<-[:IS_PARTICIPANT]-(:User {name: 'The Dog'})
RETURN c
Here, the User The Dog is not a participant of the conversation.

Neo4J Cypher query - Null relationship

I am fairly new to Neo4J and have a problem in a Cypher query.
I write cypher queries through Neo4J java and my Neo4J database is designed as follows:
I have a user node with attributes such as id, name, age, email, gender and a node city. Every user is related to a city node(with attributes id, name) by a relationship (lives). However there can be a case when a user is not associated with city.
Now my scenario of the query is such that I want to fetch all details of user and the city he lives in, in a single query which is.
match p, c, p-[:lives]->c where p.type = 'com.Person' and c.type='com.City' and p.id = 12345 return p.name, p.age, p.email, p.gender, c.name;
The query works well when user is related to a city, but it fails in case when a user is not associated with a city.
Could you please help me with a query that can handle both the scenarios.
Your MATCH and WHERE clauses are actually requiring that all matched p must be associated with a city. You have to use the OPTIONAL MATCH clause for optional matches.
By the way, the "p, c" in "MATCH p, c, p-[:lives]->c" is unnecessary and inefficient.
To get the results you want, try the following (c.name will be null if there is no associated city):
MATCH (p {type: "Person", id: 12345})
OPTIONAL MATCH (p)-[:lives]->(c {type: "City"})
RETURN p.name, p.age, p.email, p.gender, c.name;
Also, I would strongly suggest the use of the labels Person and City for your p and c nodes (instead of the type properties). That would make your queries much more efficient and also clearer. If you made this change to your nodes, the faster queries would look like:
MATCH (p:Person {id: 12345})
OPTIONAL MATCH (p)-[:lives]->(c:City)
RETURN p.name, p.age, p.email, p.gender, c.name;

Resources