How to create all edges that using one statement in Cypher? - neo4j

How to create all edges that using one statement in Cypher?
For example: lets say I have one object like this
Employees {name: "abc, country: "NZ", zipcode: "123456"}
Employees {name: "def", country: "AUS", zipcode: "964573"}
and lets say I have the following Manager objects
Manager { name: "abc", depatment: "product"}
Manager {name: "abc", depatment: "sales"}
Manager {name: "abc", depatment: "marketing"}
and Finally the Address Objects
Address {zipcode: "964573", street: "Auckland St"}
Now I want to create all the edges where Employees.name = Manager.name and Employees.zipcode = Address.zipcode however if Employees.name != Manager.name but Employees.zipcode = Address.zipcode then I want all the edges to be created between Employees and Address similarly if Employees.zipcode != Address.zipcode but Employees.name = Manager.namethen I want all the edges to be created between Employees and Manager. And I want to achieve all of this in one single statement/query
Simply put if there are matching vertices between Employees, Manager and Address I want all the edges to be created between them but if there only a match between any two I want the edge to be created between those two vertices as well. And I am trying to all of this in a single query/statement?
Is this possible to write a query in one statement that can satisfy all the conditions above?
What I tried so far is this
Find the pairs first with MATCH clause and then CREATE a relationship between them.
MATCH (e:Employees),(m:Manager), (a:Address)
WHERE e.name=m.name or e.zipcode = a.zipcode
WITH e,m,a
CREATE (m)-[:REL_NAME]->(e), (e)-[:ADDR_REL]->(a)
This clearly won't work because of the Where clause because if e.name=m.name then e.zipcode = a.zipcode won't be checked and therefore no edge will be created between employees and address.

The following query avoids producing a cartesian product of all 3 node labels (and will perform better if you have indexes for :Manager(name) and :Address(zipcode)):
MATCH (e:Employees)
OPTIONAL MATCH (m:Manager)
WHERE e.name = m.name
WITH e, COLLECT(m) AS mList
FOREACH(x IN mList | CREATE (x)-[:REL_NAME]->(e))
WITH e
OPTIONAL MATCH (a:Address)
WHERE e.zipcode = a.zipcode
WITH e, COLLECT(a) AS aList
FOREACH(y IN aList | CREATE (e)-[:ADDR_REL]->(y))

Related

Conditional partial merge of pattern into graph

I'm trying to create a relationship that connects a person to a city -> state -> country without recreating the city/state/country nodes and relationships if they do already exist - so I'd end-up with only one USA node in my graph for example
I start with a person
CREATE (p:Person {name:'Omar', Id: 'a'})
RETURN p
then I'd like to turn this into an apoc.do.case statement with apoc
or turn it into one merge statement using unique the constraint that creates a new node if no node is found or otherwise matches an existing node
// first case where the city/state/country all exist
MATCH (locality:Locality{name:"San Diego"})-[:SITUATED_IN]->(adminArea:AdministrativeArea { name: 'California' })-[:SITUATED_IN]->(country:Country { name: 'USA' })
MERGE (p)-[:SITUATED_IN]->(locality)-[:SITUATED_IN]->(adminArea)-[:SITUATED_IN]->(country)
return p
// second case where only state/country exist
MATCH (adminArea:AdministrativeArea { name: 'California' })-[:SITUATED_IN]->(country:Country { name: 'USA' })
MERGE (p)-[:SITUATED_IN]->(locality:Locality{name:"San Diego"})-[:SITUATED_IN]->(adminArea)-[:SITUATED_IN]->(country)
return p
// third case where only country exists
MATCH (country:Country { name: 'USA' })
MERGE (p)-[:SITUATED_IN]->(locality:Locality{name:"San Diego"})-[:SITUATED_IN]->(adminArea:AdministrativeArea { name: 'California' })-[:SITUATED_IN]->(country)
return p
// last case where none of city/state/country exist, so I have to create all nodes + relations
MERGE (p)-[:SITUATED_IN]->(locality:Locality{name:"San Diego"})-[:SITUATED_IN]->(adminArea:AdministrativeArea { name: 'California' })-[:SITUATED_IN]->(country:Country { name: 'USA' })
return p
The key here is I only want to end-up with one (California)->(USA). I don't want those nodes & relationships to get duplicated
Your queries that use MATCH never specify which Person you want. Variable names like p only exist for the life of a query (and sometimes not even that long). So p is unbound in your MATCH queries, and can result in your MERGE clauses creating empty nodes. You need to add MATCH (p:Person {Id: 'a'}) to the start of those queries (assuming all people have unique Id values).
It should NOT be the responsibility of every single query to ensure that all needed localities exist and are connected correctly -- that is way too much complexity and overhead for every query. Instead, you should create the appropriate localities and inter-locality relationships separately -- before you need them. If fact, it should be the responsibility of each query that creates a locality to create all the relationships associated with it.
A MERGE will only not create the specified pattern if every single thing in the pattern already exists, so to avoid duplicates a MERGE pattern should have at most 1 thing that might not already exist. So, a MERGE pattern should have at most 1 relationship, and if it has a relationship then the 2 end nodes should already be bound (by MATCH clauses, for example).
Once the Locality nodes and the inter-locality relationships exist, you can add a person like this:
MATCH (locality:Locality {name: "San Diego"})
MERGE (p:Person {Id: 'a'}) // create person if needed, specifying a unique identifier
ON CREATE SET p.name = 'Omar'; // set other properties as needed
MERGE (p)-[:SITUATED_IN]->(locality) // create relationship if necessary
The above considerations should help you design the code for creating the Locality nodes and the inter-locality relationships.
Finally, the solution I used is much simpler, it's a series of merges.
match (person:Person {Id: 'Omar'}) // that should be present in the graph
merge (country:Country {name: 'USA'})
merge (state:State {name: 'California'})-[:SITUATED_IN]->(country)
merge (city:City {name: 'Los Angeles'})-[:SITUATED_IN]->(state)
merge (person)-[:SITUATED_IN]->(city)
return person;

How to create edges based on the equality check on vertex attributes in Cypher?

How to create edges based on the equality check on vertex attributes in Cypher?
For example: lets say I have one object like this
Employees {name: "abc, country: "NZ"}
and lets say I have the following objects
Manager { name: "abc", depatment: "product"}
Manager {name: "abc", depatment: "sales"}
Manager {name: "abc", depatment: "marketing"}
Now I want to create all the edges where Employees.name = Manager.name
How do I write the Cypher query to create all 4 vertices and 3 edges?
Find the pairs first with MATCH clause and then CREATE a relationship between them.
MATCH (e:Employees),(m:Manager)
WHERE e.name=m.name
WITH e,m
CREATE (m)-[:REL_NAME]->(e)

Creating Nodes and Relationships, after Matching Nodes using ID

I have 3 types of nodes - Challenge, Entry and User, and 2 relationships between these nodes: (Entry)-[POSTED_BY]->(User) and (Entry)-[PART_OF]->(Challenge).
Here is what I'm trying to accomplish:
- I have an existing Challenge, and I can identify it by its ID (internal neo4j id)
- I also have an existing User, and I can identify it by its id (not the same as neo4j internal id)
- Given the existing Challenge and User, I need to create a new Entry node, and link it with the Challenge node with a PART_OF relationship.
- In addition, I need to link the new Entry with the User node by the POSTED_BY relationship
- I want to achieve the above in a single Cypher query statement, if possible
This is what I'm trying:
MATCH (c:Challenge)
WHERE (id(c) = 240),
MATCH (u:User {id: '70cf6846-b38a-413c-bab8-7c707d4f46a8'})
CREATE (e:Entry {name: "My Entry"})-[:PART_OF]->(c), (u)<-[r:POSTED_BY]-(e)
RETURN e;
However, this is failing as it seems I cannot match two nodes with the above syntax. However, if I match the Challenge with a non-internal property, say name, it seems to work:
MATCH (c:Challenge {name: "Challenge Name"),
MATCH (u:User {id: '70cf6846-b38a-413c-bab8-7c707d4f46a8'})
CREATE (e:Entry {name: "My Entry"})-[:PART_OF]->(c), (u)<-[r:POSTED_BY]-(e)
RETURN e;
However, as I mentioned above, I want to match the neo4j internal Node ID for the Challenge, and I'm not sure if there's a way to match that other than by using the WHERE id(c) = 232 clause.
Your syntax is almost correct, but you don't need the comma between the WHERE and the next MATCH.
MATCH (c:Challenge)
WHERE id(c) = 240
MATCH (u:User {id: '70cf6846-b38a-413c-bab8-7c707d4f46a8'})
CREATE (e:Entry {name: "My Entry"})-[:PART_OF]->(c), (u)<-[r:POSTED_BY]-(e)
RETURN e;

Neo4j Passing distinct nodes through WITH in Cypher

I have the following query, where there are 3 MATCHES, connected with WITH, searching through 3 paths.
MATCH (:File {name: 'A'})-[:FILE_OF]->(:Fun {name: 'B'})-->(ent:CFGEntry)-[:Flows*]->()-->(expr:CallExpr {name: 'C'})-->()-[:IS_PARENT]->(Callee {name: 'd'})
WITH expr, ent
MATCH (expr)-->(:Arg {chNum: '1'})-->(id:Id)
WITH id, ent
MATCH (entry)-[:Flows*]->(:IdDecl)-[:Def]->(sym:Sym)
WHERE id.name = sym.name
RETURN id.name
The query returns two distinct id and one distinct entry, and 7 distinct sym.
The problem is that since in the second MATCH I pass "WITH id, entry", and two distinct id were found, two instances of entry is passed to the third match instead of 1, and the run time of the third match unnecessarily gets doubled at least.
I am wondering if anyone know how I should write this query to just make use of one single instance of entry.
Your best bet will be to aggregate id, but then you'll need to adjust your logic in the third part of your query accordingly:
MATCH (:File {name: 'A'})-[:FILE_OF]->(:Fun {name: 'B'})-->(ent:CFGEntry)-[:Flows*]->()-->(expr:CallExpr {name: 'C'})-->()-[:IS_PARENT]->(Callee {name: 'd'})
WITH expr, ent
MATCH (expr)-->(:Arg {chNum: '1'})-->(id:Id)
WITH collect(id.name) as names, ent
MATCH (entry)-[:Flows*]->(:IdDecl)-[:Def]->(sym:Sym)
WHERE sym.name in names
RETURN sym.name

Cypher: How to use FOREACH on an array of objects to create multiple nodes and multiple relationships

I am having trouble with the fetch described in the title of this question. My attempt is the following string:
FOREACH (_tabobj IN {_tabarray} |
MATCH (a:Superlabel),(b)
WHERE a.id = {_parentid} AND b.id = _tabobj.id
MERGE (a)-[r:INCLUDES]->(b {
name : _tabobj.name
})
I am trying to only create the relationship if it is not already in the database and nothing if the relationship is already in the database. Also trying to only create the b node if it is not already in the database and nothing if the b node is already in the database.
I am very grateful for help you can offer
How about something like this:
MATCH (a:Superlabel {id: {_parentid}})
WITH a, tabarray IN {_tabarray}
UNWIND tabarray AS tabobj
MATCH (b {id: tabobj.id)
MERGE (a)-[r:INCLUDES]->(b {name: tabobj.name})
I generally use FOREACH as a last resort ;)
And a simpler solution:
MATCH (a:Superlabel {id: {_parentid}}), (b)
WHERE b.id IN EXTRACT(tabobj IN {_tabarray} | tabobj.id)
MERGE (a)-[r:INCLUDES]->(b {name: tabobj.name})
An important consideration for both of these queries: When matching the b node on the id property the database index (if you have one) won't be used because indexes are for a label / property pair. Specifying a label for b, if applicable, could help performance.

Resources