neo4j optional match and null - neo4j

Perhaps this approach is wrong but I've built a cypher query using optional matches and collect. If there is data everything is fine, if not, collect returns null for the properties specified. It looks like this is expected as per the docs.
Ideally I'd like collect to return an empty array or null when there no match is made. I'm using the following...
MATCH (p) WHERE id(p) = 11
OPTIONAL MATCH (p) -[:car]- (c)
OPTIONAL MATCH (p) -[:driver]- (u)
RETURN {
_id: id(p), name: p.name, type: p.type,
cars: collect({_id: id(c), name: c.name}),
drivers: collect({_id: id(u), name: u.email})
} AS place

Try like this
MATCH (p) WHERE id(p) = 11
OPTIONAL MATCH (p) -[:car]- (c)
OPTIONAL MATCH (p) -[:driver]- (u)
RETURN {
_id: id(p), name: p.name, type: p.type,
cars: CASE WHEN c IS NOT NULL THEN collect({_id: id(c), name: c.name}) ELSE NULL END,
drivers: CASE WHEN u IS NOT NULL THEN collect({_id: id(u), name: u.email}) ELSE NULL END
} AS place
This will check whether data is present to collect or not if present then it will return else null value will be returned

Related

Neo4J Cypher: create relationship only if destination node exists

I am trying to load a csv file that and as part of import try to create a relationship if the foreign key fields are resolved.
OPTIONAL MATCH (d1:Destination1 { id: line[2], name: line[3] })
OPTIONAL MATCH (d2:Destination2 { id: line[4], name: line[5] })
MERGE (n:Source {id: line[0])-[:MY_RELATIONSHIP]->(d1)
MERGE (n:Source {id: line[0])-[:MY_RELATIONSHIP]->(d2)
this query fails when either of d1 or d2 are not resolved. Is there a way to have a IF THEN option to create a link only if the destination exists. I see even WHERE clause is not allowed in the MERGE statement.
UPDATE 1
Looks like I over simplified my query, to give better context, here is what I am planning to achieve:
LOAD CSV FROM 'file:///MyData.csv' AS line
MATCH (n:BOM { Material: line[0]})
WITH n, line
MATCH (s:Supplier { name:line[2]})
MATCH (t:Type { name: line[7]})
MATCH (g1:Grade { number: line[6]})
MATCH (g2:Grade { number: line[5]})
MERGE (n)-[:HAS_SUPPLIER]->(s)
MERGE (n)-[:MATERIAL_TYPE]->(t)
MERGE (n)-[:HAS_GRADE{priority:1}]->(g1)
MERGE (n)-[:HAS_GRADE{priority:2}]->(g2)
I notice that if the query fails to find either g1 or g2 it fully fails to update other relationships. I had to resort to Optional Match but then the Merge fails
If the only thing you are doing is creating a relationship between existing nodes, then simply change the OPTIONAL MATCH to MATCH clause
MATCH (d1:Destination1 { id: line[2], name: line[3] })
MATCH (d2:Destination2 { id: line[4], name: line[5] })
....
You can think of it as this, if a MATCH clause fails to retrieve any data, then the query execution stops for that particular row. In a sense it is doing the same as:
OPTIONAL MATCH (d1:Destination1 { id: line[2], name: line[3] })
OPTIONAL MATCH (d2:Destination2 { id: line[4], name: line[5] })
WITH d1, d2
WHERE d1 IS NOT NULL and d2 IS NOT NULL
....
If you need to do other things as well, you can resort to FOREACH tricks or subqueries.
LOAD CSV FROM 'file:///MyData.csv' AS line
MATCH (n:BOM { Material: line[0]})
WITH n, line
MATCH (s:Supplier { name:line[2]})
MATCH (t:Type { name: line[7]})
OPTIONAL MATCH (g1:Grade { number: line[6]})
OPTIONAL MATCH (g2:Grade { number: line[5]})
MERGE (n)-[:HAS_SUPPLIER]->(s)
MERGE (n)-[:MATERIAL_TYPE]->(t)
FOREACH (_ in CASE WHEN g2 is not null and g1 is not null THEN [1] ELSE [] END |
MERGE (n)-[:HAS_GRADE{priority:1}]->(g1)
MERGE (n)-[:HAS_GRADE{priority:2}]->(g2))
FOREACH trick has been around since forewer (https://data-xtractor.com/blog/databases/neo4j-cypher-hacks/#4_Conditional_Execution_with_8230FOREACH)
Maybe a more clean version would be to use a subquery. I think the following should work:
LOAD CSV FROM 'file:///MyData.csv' AS line
MATCH (n:BOM { Material: line[0]})
WITH n, line
MATCH (s:Supplier { name:line[2]})
MATCH (t:Type { name: line[7]})
OPTIONAL MATCH (g1:Grade { number: line[6]})
OPTIONAL MATCH (g2:Grade { number: line[5]})
MERGE (n)-[:HAS_SUPPLIER]->(s)
MERGE (n)-[:MATERIAL_TYPE]->(t)
WITH n, g1, g2
CALL {
WITH n, g1, g2
WITH *
WHERE g1 IS NOT NULL and g2 IS NOT NULL
MERGE (n)-[:HAS_GRADE{priority:1}]->(g1)
MERGE (n)-[:HAS_GRADE{priority:2}]->(g2)}
You can also obviously create two FOREACH subqueries or two subqueries if you want to link to grade1 if it exists but grade 2 does not exists.
LOAD CSV FROM 'file:///MyData.csv' AS line
MATCH (n:BOM { Material: line[0]})
WITH n, line
MATCH (s:Supplier { name:line[2]})
MATCH (t:Type { name: line[7]})
OPTIONAL MATCH (g1:Grade { number: line[6]})
OPTIONAL MATCH (g2:Grade { number: line[5]})
MERGE (n)-[:HAS_SUPPLIER]->(s)
MERGE (n)-[:MATERIAL_TYPE]->(t)
FOREACH (_ in CASE WHEN g1 is not null THEN [1] ELSE [] END |
MERGE (n)-[:HAS_GRADE{priority:1}]->(g1))
FOREACH (_ in CASE WHEN g2 is not null THEN [1] ELSE [] END |
MERGE (n)-[:HAS_GRADE{priority:2}]->(g2))

Handling parameterised order by filters within neo4j cypher

I am working on the cypher below but I am not sure how to implement parameterised sorting. I want to sort with the parameters $field and $sort where $field could be: 'species.name', 'species.description', 'species.scientificName', 'monthCount', 'eats' or 'eatenBy' and $sort is only 'asc' or 'desc'. If either of those values is hardcoded then the cypher runs but when passed as a parameter it fails. Any help would be greatly appreciated! :)
MATCH (s:Species)
WHERE toLower(s.name) CONTAINS toLower($search)
WITH s
OPTIONAL MATCH (s)-[e:EATS]->(eatsSpecies:Species)
OPTIONAL MATCH (s)<-[:EATEN_BY]-(eatenBySpecies:Species)
OPTIONAL MATCH (s)<-[:IS_ABOUT]-(image:Image)
OPTIONAL MATCH (s)-[:FALLS_UNDER]->(primary:Primary)
OPTIONAL MATCH (s)-[:MEASURED_BY]->(month:Month)
WITH s, eatsSpecies, eatenBySpecies, image, primary, month
WITH s,
count(DISTINCT eatsSpecies.name) AS eats,
count(DISTINCT eatenBySpecies.name) AS eatenBy,
primary,
image,
count(distinct month) as monthCount
WITH {
name: s.name,
scientificName: s.scientificName,
description: s.description,
primary: case when exists(primary.GUID) then true else false end,
active: case when exists(s.active) then s.active else true end,
months: monthCount,
guid: s.GUID,
eats: eats,
eatenBy: eatenBy,
image: case when exists(image.url) then true else false end
} AS species order by $field $sort
SKIP $skip
LIMIT $limit
RETURN collect(species)
Something like this could work, by adding a field key to the object and inverting the collection before the SKIP and LIMIT
The suggestion below is pure Cypher. Using apoc, you could also create your dynamic queries.
MATCH (s:Species)
WHERE toLower(s.name) CONTAINS toLower($search)
WITH s
OPTIONAL MATCH (s)-[e:EATS]->(eatsSpecies:Species)
OPTIONAL MATCH (s)<-[:EATEN_BY]-(eatenBySpecies:Species)
OPTIONAL MATCH (s)<-[:IS_ABOUT]-(image:Image)
OPTIONAL MATCH (s)-[:FALLS_UNDER]->(primary:Primary)
OPTIONAL MATCH (s)-[:MEASURED_BY]->(month:Month)
WITH s, eatsSpecies, eatenBySpecies, image, primary, month
WITH s,
// add a 'sortField'
s[$field] AS field,
count(DISTINCT eatsSpecies.name) AS eats,
count(DISTINCT eatenBySpecies.name) AS eatenBy,
primary,
image,
count(distinct month) as monthCount
WITH {
name: s.name,
scientificName: s.scientificName,
description: s.description,
primary: case when exists(primary.GUID) then true else false end,
active: case when exists(s.active) then s.active else true end,
months: monthCount,
guid: s.GUID,
eats: eats,
eatenBy: eatenBy,
image: case when exists(image.url) then true else false end,
field: field
} AS species ORDER BY species.field
WITH COLLECT(species) AS sortedSpecies
RETURN CASE $sort
WHEN "asc" THEN sortedSpecies[$skip .. $limit]
ELSE REDUCE(array=[], i IN RANGE(1,size(sortedSpecies)) |
array
+sortedSpecies[size(sortedSpecies)-i]
)[$skip .. $limit]
END AS sortedSpecies

Cypher: using OPTIONAL MATCH in combination with collect returns default item when there is none

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})

Consolidating & Collecting into one Record

I'm trying to write a cypher query where all related nodes are collected and returned under a key in a map:
{
name: 'chart',
CONTAINTED: [
{
name: 'id'
},
{
name: 'bloodpressure'
},
...
],
FOREIGNKEY: [
{
name: 'clientid'
}
]
}
I've attempted to do this with the following cypher query but it isn't quite right. Using the method below two records are returned rather than just one.
MATCH path=(table:tabPHI {name: 'client'})-[r]-(c)
WITH table as tab, type(r) as rel, collect(c) as col
CALL apoc.map.setKey(tab, rel, col) YIELD value as foo
return foo
MATCH (table:tabPHI {name: 'client'})-[r]-(c)
WITH table as tab, type(r) as rel, collect(c) as col
WITH tab, COLLECT(rel) AS keys, COLLECT(col) AS values
WITH keys(tab) as main_keys, [key IN keys(tab)|tab[key]] AS main_values, keys, values
CALL apoc.map.fromLists(main_keys + keys, main_values + values) YIELD value as foo
return foo
You're creating a single map for each relationship type in your query. You have to COLLECT(rel) at some point to get a single result for each tab.

How to report named properties in JSON format from a cypher query?

I have graph in which nodes are linked to other nodes containing properties. For instance:
(:Person {Name: 'A'}) -[:OWNS]-> (:Object {Name: 'Car', Value: 2})
(:Person {Name: 'A'}) -[:OWNS]-> (:Object {Name: 'Computer', Value: 1})
Is it possible (how) to report tagged data from a cypher query. Expected format would preferably be:
{Person: 'A', owns: {Car: 2, Computer: 1}}
My current query is:
match (p:Person) -[:OWNS]-> (o:Object)
return {Person: p.Name, owns: collect({name: o.Name, value: o.Value})
this returns
{Person: 'A', owns: [{name: 'Car', value: 2}, {name: 'Computer', value: 1}]}
Unfortunately, I can't write something like:
return {Person: p.Name, owns: collect({o.Name: o.Value})
Assuming that a person can have any number of cars and computers (including 0):
OPTIONAL MATCH (p:Person)-[:OWNS]->(car:Object { Name: 'Car' })
OPTIONAL MATCH (p)-[:OWNS]->(comp:Object { Name: 'Computer' })
RETURN { Person: p.Name, owns: { Cars: COLLECT(car.Value), Computers: COLLECT(comp.Value)}} AS result;
Here is a sample result for 2 cars and 0 computers for person A:
{Person:"A", owns:{"Cars":[2,3],"Computers":[]}}
1: Fetch all nodes: - START n=node (*) RETURN n; or match (n) return n
2: Displays the nodes and the relationships: - MATCH (n) MATCH (n)-[r]-() RETURN n,r Or START n=node(*) MATCH (n)-[r]->(m) RETURN n,r,m
3: Match nodes and relationships: - MATCH (a:Policy)-[:APPLIES_TO]-(Cluster) WHERE a.name = "pol-1nils" RETURN a, Cluster
4: Get all object of particular nodes: - MATCH (list:Policy) return list
5: bound to the entities between two nodes: - match (a:WorkLoad)-[b:APPLIES_TO]->(c:Policy) where c.name = "shamshad" return a,b,c;

Resources