I have a JSON in the next form:
{ "conditions": [ { "id": "123", "type": "a", entities: ["529", "454"] },
{ "id": "124", "type": "b", entities: ["530", "455"] }
]
}
I want to create relation ship between the Condition node with node A Entities or node B entities based on type attribute which can be A/B i Assuming that these entities already exists in neo4j.
I am trying something like the below cypher but that doesn't work.
WITH {json} as data
UNWIND data.conditions as condition
MATCH (c:Condition { conditionId: condition.id})
CASE c.type
WHEN 'a' THEN FOREACH (sid IN condition.entities |
MERGE (s:NodeA {nr_serverId:sid}) MERGE (s)-[:ATTACHED_TO]->(c)
)
WHEN 'b' THEN FOREACH (aid IN condition.entities |
MERGE (a:NodeB {nr_appId: aid}) MERGE (a)-[:ATTACHED_TO]->(c)
)
END;
Can anyone please help me with the correct way of doing this? Thank you.
Since at the moment there is no classical conditional statements in cypher, you can use the famous trick with foreach and case:
WITH {json} as data
UNWIND data.conditions as condition
MATCH (c:Condition { conditionId: condition.id})
FOREACH (ift in CASE WHEN c.type = 'a' THEN [1] ELSE [] END |
FOREACH (sid IN condition.entities |
MERGE (s:NodeA {nr_serverId:sid}) MERGE (s)-[:ATTACHED_TO]->(c)
)
)
FOREACH (ift in CASE WHEN c.type = 'b' THEN [1] ELSE [] END |
FOREACH (aid IN condition.entities |
MERGE (a:NodeB {nr_appId: aid}) MERGE (a)-[:ATTACHED_TO]->(c)
)
)
APOC Procedures just updated with support for conditional cypher execution. You'll need version 3.1.3.7 or greater (if using Neo4j 3.1.x), or version 3.2.0.3 or greater (if using Neo4j 3.2.x).
Here's an example of using these new procedures to execute your query:
WITH {json} as data
UNWIND data.conditions as condition
MATCH (c:Condition { conditionId: condition.id})
CALL apoc.do.case([
c.type = 'a',
"FOREACH (sid IN condition.entities |
MERGE (s:NodeA {nr_serverId:sid}) MERGE (s)-[:ATTACHED_TO]->(c))",
c.type = 'b',
"FOREACH (aid IN condition.entities |
MERGE (a:NodeB {nr_appId: aid}) MERGE (a)-[:ATTACHED_TO]->(c))"
], '', {condition:condition, c:c}) YIELD value
Related
I have a nested FOREACH loop that needs to match a tag to multiple labels.
OPTIONAL MATCH (a: Article {URL: event.URL})
FOREACH(ignoreme in case when a is null then [1] else [] end |
CREATE (a: Article {URL: event.URL})
//other statements ..... //
FOREACH (relation in CASE WHEN event.article.nlp_relations is not null then event.article.nlp_relations else [] end |
match (a)-[:HAS_NLP_TAG]->(t_from) where (t_from:Tag or t_from:Entity) and t_from.value = relation.from.value
match (a)-[:HAS_NLP_TAG]->(t_to) where (t_to:Tag or t_to:Entity) and t_to.value = relation.to.value
call apoc.create.relationship(t_from,relation.type , {}, t_to)
)
)
This does not work because you cannot use a match inside a foreach. I can say that there will always be a node to match as it will be created earlier in the same query. so it will never be null, but I do not know how to express this in this current form. Can anybody help
It looks like you can just reformulate your second FOREACH as a normal MATCH-CREATE combination, something like (I didn't try to run it):
OPTIONAL MATCH (a: Article {URL: event.URL})
FOREACH(ignoreme in case when a is null then [1] else [] end |
CREATE (a: Article {URL: event.URL}
)
//other statements ..... //
unwind Case When event.article.nlp_relations is null then [] else event.article.nlp_relations end as relation
match (a)-[:HAS_NLP_TAG]->(t_from) where (t_from:Tag or t_from:Entity) and t_from.value = relation.from.value
match (a)-[:HAS_NLP_TAG]->(t_to) where (t_to:Tag or t_to:Entity) and t_to.value = relation.to.value
call apoc.create.relationship(t_from,relation.type , {}, t_to)
)
You might still need to insert an appropriate WITH-Statement before the MATCH-statements.
Is there a way to retrieve relations as child elements of a tree?
This is the basic data i have:
CREATE (:Customer {id:1, name:'Customer 1'})<-[:CREATED_BY]-(c:Category {id:1, name:'Category 1'})
WITH c as category, range(2, 7) as subCatIds
FOREACH (s IN subCatIds | CREATE (category)<-[:SUBCATEGORY_OF]-(:Category {id:s, name:'SubCategory '+s}))
WITH category, range(1, 5) as attTypeIds
FOREACH (a IN attTypeIds | CREATE (category)-[:HAS_ATTRIBUTE {name:'Attribute '+a, required: (a%2=0)}]->(:AttributeType {id:a, name:'AttributeType '+a}))
WITH category
MATCH p = (:AttributeType)<-[:HAS_ATTRIBUTE]-(category)<-[:SUBCATEGORY_OF]-(:Category)
RETURN p
So this query returns correctly the tree structure:
MATCH (:Customer {id:1})<-[:CREATED_BY]-(c:Category {id:1}),
p = (c)<-[:SUBCATEGORY_OF*0..1]-(:Category)
WITH COLLECT(p) as category
CALL apoc.convert.toTree(category) yield value
RETURN value
How do i add the relationships [:HAS_ATTRIBUTE] as child nodes to this query?
I've tried already:
MATCH (:Customer {id:1})<-[:CREATED_BY]-(c:Category {id:1}),
SubCatsP = (c)<-[:SUBCATEGORY_OF*0..1]-(:Category),
AttP = (c)-[:HAS_ATTRIBUTE]->(att:AttributeType)
WITH COLLECT(SubCatsP) as category, RELATIONSHIPS(AttP) as attributes
CALL apoc.convert.toTree(category) yield value
RETURN value, attributes
But this returns 5 records (1 for each relationship [:HAS_ATTRIBUTE]) with the Category-Subcategories tree repeated.
I expect the result to be:
{
id: 1,
name: 'Category 1',
SUBCATEGORY_OF:[
{id:2, name: 'Subcategory 2'}, ...
]
HAS_ATTRIBUTE:[
{name: 'Attribute 1', required: false, att: {name:'AttributeType 5',id:5}}, ...
<OR>
{name: 'Attribute 1', required: false, att.name:'AttributeType 5',att.id:5}, ...
]
}
Is this even possible or do you consider a better approach to perform 2 separate queries?
You can try combining the paths into a single list:
MATCH (:Customer {id:1})<-[:CREATED_BY]-(c:Category {id:1}),
SubCatsP = (c)<-[:SUBCATEGORY_OF*0..1]-(:Category),
AttP = (c)-[:HAS_ATTRIBUTE]->(att:AttributeType)
WITH COLLECT(SubCatsP) + COLLECT(AttP) as category
CALL apoc.convert.toTree(category) yield value
RETURN value
I have a use case where I am trying to optimize my Neo4j db calls and code by using the RETURN CASE WHEN THEN clauses in Cypher to run different queries depending on the WHEN result. This is my example:
MATCH (n {email: 'abc123#abc.com'})
RETURN
CASE WHEN n.category='Owner' THEN MATCH '(n)-[r:OWNS]->(m)'
WHEN n.category='Dealer' THEN MATCH (n)-[r:SUPPLY_PARTS_FOR]->(m)
WHEN n.category='Mechanic' THEN MATCH (n)-[r:SERVICE]-(m) END
AS result;
I am not sure this is legal but this is what I want to achieve. I am getting syntax errors like Invalid input '>'. How can I achieve this in the best manner?
EDIT for possible APOC solution:
This was my plan before discovering the limitation of FOREACH...
MATCH (user:Person {email:{paramEmail}})
FOREACH (_ IN case when 'Owner' = {paramCategory} then [1] else [] end|
SET user:Owner, user += queryObj
WITH user, {paramVehicles} AS coll
UNWIND coll AS vehicle
MATCH(v:Vehicles {name:vehicle})
CREATE UNIQUE (user)-[r:OWNS {since: timestamp()}]->(v)
SET r += paramVehicleProps
)
FOREACH (_ IN case when 'Mechanic' = {Category} then [1] else [] end|
SET user:Owner, user += queryObj
WITH user, {paramVehicles} AS coll
….
)
FOREACH (_ IN case when 'Dealer' = {paramCategory} then [1] else [] end|
SET user:Owner, user += queryObj
WITH user, {paramVehicles} AS coll
…...
)
RETURN user,
CASE {paramCategory}
WHEN 'Owner' THEN [(n)-[r:OWNS]->(m) | m and r]
WHEN 'Dealer' THEN [(n)-[r:SUPPLY_PARTS_FOR]->(m) | m]
WHEN 'Mechanic' THEN [(n)-[r:SERVICE]-(m) | m]
END AS result`,{
paramQueryObj: queryObj,
paramVehicles: makeVehicleArray,
paramVehicleProps: vehiclePropsArray,
paramSalesAgent: dealerSalesAgentObjarray,
paramWarehouseAgent: dealerWarehouseAgentObjarray
}).....
does anyone know to convert this using apoc.do.when()? note I need 'm' and 'r' in the first THEN.
You should still use a label in your first match, otherwise you get a full database scan and not a index lookup by email!!
for your query you can use pattern comprehensions:
MATCH (n:Person {email: 'abc123#abc.com'})
RETURN
CASE n.category
WHEN 'Owner' THEN [(n)-[r:OWNS]->(m) | m]
WHEN 'Dealer' THEN [(n)-[r:SUPPLY_PARTS_FOR]->(m) | m]
WHEN 'Mechanic' THEN [(n)-[r:SERVICE]-(m) | m] END
AS result;
It is also possible to use apoc.do.case: https://neo4j.com/labs/apoc/4.4/overview/apoc.do/apoc.do.case/
CALL apoc.do.case([
false,
'CREATE (a:Node{name:"A"}) RETURN a AS node',
true,
'CREATE (b:Node{name:"B"}) RETURN b AS node'
],
'CREATE (c:Node{name:"C"}) RETURN c AS node',{})
YIELD value
RETURN value.node AS node;
I'm using Neo4J to retrieve a person and their skills. This is my Cypher query:
MATCH (p:Person {id: "1"})
OPTIONAL MATCH (p) -[exp:HAS_EXPERIENCE]->(s:Skill)
WITH collect(distinct {id: s.id, name: s.name}) as skills, p
RETURN p.id as id, skills
This is the result:
{
"id": "1",
"skills": [
{
"name": null,
"id": null,
}
]
}
As you can see, the list of skills contains a 'default' item. However, in this particular case the person has no skills.
Why does the result contain an array item? How to I adjust the query so that an empty array is returned?
Using Neo4J 3.1.1.
This should work:
MATCH (p:Person {id: "1"})
OPTIONAL MATCH (p)-[exp:HAS_EXPERIENCE]->(s:Skill)
RETURN p.id AS id,
CASE WHEN s IS NULL THEN [] ELSE COLLECT(distinct {id: s.id, name: s.name}) END as skills;
s would only be NULL if the OPTIONAL MATCH does not match anything.
You may wish to consider the following:
MATCH (p:Person {id: "1"})
OPTIONAL MATCH (p) -[exp:HAS_EXPERIENCE]->(s:Skill)
RETURN p {id: p.id, skills: COLLECT(distinct s {.*})}
Assuming that you wish to get all fields from the Skill. Otherwise
... collect(distinct s {id:s.id, name:s.name})
I am creating multiple relationships in one query. If a match is not found for the first relationship the second one is not created. If both matches exist then both relationships work.
Example:
Assume that 'MATCH1ID' below does not exist but 'MATCH2ID' does. Why does this happen?
DOES NOT WORK
MERGE (NewRecord:OBJECTNAME { Id: 'ABZDEFG' })
SET NewRecord.name='NEWNAME'
WITH NewRecord
MATCH (a)
WHERE a.Id = 'MATCH1ID'
CREATE (a)-[ar:Relationship1]->(NewRecord)
WITH NewRecord MATCH (b)
WHERE b.Id = 'MATCH2ID'
CREATE (b)-[br:Relationship2]->(NewRecord)
(If I switch the order of the node that does exist to be first in the order it works)
WORKS
MERGE (NewRecord:OBJECTNAME { Id: 'ABZDEFG' })
SET NewRecord.name='NEWNAME'
WITH NewRecord
MATCH (b)
WHERE b.Id = 'MATCH2ID'
CREATE (b)-[br:Relationship2]->(NewRecord)
WITH NewRecord
MATCH (a)
WHERE a.Id = 'MATCH1ID'
CREATE (a)-[ar:Relationship1]->(NewRecord)
You can try using a OPTIONAL MATCH instead of a simple MATCH:
MERGE (NewRecord:OBJECTNAME { Id: 'ABZDEFG' })
SET NewRecord.name='NEWNAME'
WITH NewRecord
OPTIONAL MATCH (a)
WHERE a.Id = 'MATCH1ID'
FOREACH(x IN (CASE WHEN a IS NULL THEN [] ELSE [1] END) |
CREATE (a)-[ar:Relationship1]->(NewRecord)
)
WITH NewRecord
OPTIONAL MATCH (b)
WHERE b.Id = 'MATCH2ID'
FOREACH(x IN (CASE WHEN b IS NULL THEN [] ELSE [1] END) |
CREATE (b)-[br:Relationship2]->(NewRecord)
)
Also, this query uses a trick with FORACH and CASE WHEN to handle conditions. In this case, the CREATE statement is executed only when a or b variables are not null.
See Neo4j: LOAD CSV - Handling Conditionals.