Neo4j get labels on an optional match - neo4j

I have the following example cypher:
MATCH (n)
OPTIONAL MATCH (n)-[:likes]->(p)
RETURN n, p, label(p)
This works great if optional match return a non null value. However if optional match is empty, this fails. Is there a way to return label(p) if p exists else return null?

First things, I think you probably want to narrow down what n matches with some criteria and an index, but to answer your question, coalesce is your friend.
MATCH (n)
OPTIONAL MATCH (n)-[:likes]->(p)
RETURN n
, coalesce(p, 'nobody')
, coalesce(labels(p),'nothing')

Related

Having multiple OPTIONAL MATCH in Cypher

I'm looking to form a query what should match on at least one of the OPTIONAL MATCH. In this form it would return a result even when none of the OPTIONAL MATCH, which is not what I desire.
MATCH (media:Media)-[rr:HAS]-(ad:Ad)
OPTIONAL MATCH (media)--(word:Word) WHERE word.value IN ['thing']
OPTIONAL MATCH (media) WHERE media.description CONTAINS 'something'
RETURN media, collect(DISTINCT word) as word, collect(DISTINCT ad) as ad
UNION would be a better fit in this case. The only caveat is that you return columns with the same alias from each part of the union.
MATCH (media:Media)-[rr:HAS]-(ad:Ad)
MATCH (media)--(word:Word) WHERE word.value IN ['thing']
RETURN media, collect(DISTINCT word) as things
UNION
MATCH (media:Media)-[rr:HAS]-(ad:Ad)
MATCH (media) WHERE media.description CONTAINS 'something'
RETURN media, collect(DISTINCT ad) as things
This would produce zero results if neither part matches, and at least one if either matches.

Match all nodes and return nodes + relationships

In the latest version of Cypher, I can use this query to get all nodes with relationships:
MATCH (n)-[r]-(m) RETURN n,r,m
However, I'm missing nodes without any relationships.
In trying to query the missing nodes, this attempt gives me the error: Variable 'r' not defined
MATCH (n) WHERE NOT (n)-[r]->() RETURN n
And, this attempt shows zero results:
MATCH (n)-[r]->() WHERE r is null RETURN n
I can see the stragglers with:
MATCH (n) RETURN n
But, then I'm missing the relationships.
How do I phrase my query to find all nodes and all relationships without duplicates?
You can try the OPTIONAL MATCH:
MATCH (n)
OPTIONAL MATCH (n)-[r]-(m)
RETURN n, r, m

OPTIONAL MATCH returns no path for disconnect nodes

I find weird that using OPTIONAL MATCH nodes that don’t have the expected relationship are not returned as a single node in path.
OPTIONAL MATCH path = (:Person) -[:LIKES]- (:Movie)
UNWIND nodes(p) as n
UNWIND rels(p) as e
WITH n
WHERE HEAD(LABELS(n)) = “Person”
return COUNT(DISTINCT n)
The number of people returned only includes those who liked a movie. By using OPTIONAL I would have expected all people to be returned.
Is there a workaround to this or am I doing some this wrong in the query?
A better way to go about this would be to match to all :People nodes first, then use the OPTIONAL MATCH to match to movies (or, if you want a collection of the movies they liked, use pattern comprehension).
If you do need to perform an UNWIND on an empty collection without wiping out the row, use a CASE around some condition to use a single-element list rather than the empty list.
MATCH (n:Person) // match all persons
OPTIONAL MATCH p = (n) -[:LIKES]- (m:Movie) // p and m are the optionals
UNWIND CASE WHEN p is null THEN [null] ELSE nodes(p) END as nodes // already have n, using a different variable
UNWIND CASE WHEN p is null THEN [null] ELSE rels(p) END as e // forcing a single element list means UNWIND won't wipe out the row
WITH n
WHERE HEAD(LABELS(n)) = “Person” // not really needed at all, and bad practice, you don't know the order of the labels on a node
return COUNT(DISTINCT n) // if this is really all you need, just keep the first match and the return of the query (without distinct), don't need anything else

Trouble using OPTIONAL MATCH with a MATCH and WHERE

I have a cypher query that is not behaving as expected and I'm trying to figure out why. I suspect I don't fully understand how OPTIONAL MATCH works.
The database has one (:'Person::Current') node and one (:'Trait::Current') node. It does not have a (:'PersonTrait::Current') node.
If I run this query, it correctly returns a count(t) of 1
MATCH (n:`Person::Current` {uuid: $person_id}), (t:`Trait::Current` {uuid: $trait_id})
WHERE NOT (
(n)-[:PERSON_TRAIT]->(:`PersonTrait::Current` {has: true})-[:PERSON_TRAIT]->(t) OR
(n)-[:PERSON_TRAIT]->(:`PersonTrait::Current` {has: false})-[:PERSON_TRAIT]->(t) OR
(t)-[:GIVES_TRAIT]->(:`GivesTrait::Current`)-[:GIVES_TRAIT]->(:`Trait::Current`)<-[:PERSON_TRAIT]-(:`PersonTrait::Current` {has: false})<-[:PERSON_TRAIT]-(n)
)
RETURN count(t) as res
When a (:'PersonTrait::Current') node is added to the database in the form
(:`Person::Current`)-[:PERSON_TRAIT]->(:`PersonTrait::Current` {has: true})-[:PERSON_TRAIT]->(:`Trait::Current`)
My query correctly returns a count(t) of 0.
However, if I try and DRY up the query by making use of OPTIONAL MATCH, like so
MATCH (n:`Person::Current` {uuid: $person_id}), (t:`Trait::Current` {uuid: $trait_id})
OPTIONAL MATCH (pt:`PersonTrait::Current`)
WHERE NOT (
((n)-[:PERSON_TRAIT]->(pt)-[:PERSON_TRAIT]->(t) AND exists(pt.has)) OR
(t)-[:GIVES_TRAIT]->(:`GivesTrait::Current`)-[:GIVES_TRAIT]->(:`Trait::Current`)<-[:PERSON_TRAIT]-(pt {has: false})<-[:PERSON_TRAIT]-(n)
)
RETURN count(t) as res
Then the query incorrectly returns a count(t) of 1 when a (:'PersonTrait::Current') node is added to the database in the form
(:`Person::Current`)-[:PERSON_TRAIT]->(:`PersonTrait::Current` {has: true})-[:PERSON_TRAIT]->(:`Trait::Current`)
Anyone know what's going wrong? The WHERE NOT clause should be filtering out (t) nodes if a (pt) node is present with the appropriate pattern.
THANKS!!!
I think the issue is understanding the WHERE clause, in that WHERE only applies to the previous MATCH, OPTIONAL MATCH, or WITH clause.
In this case, it's paired with the OPTIONAL MATCH, so rows won't be filtered out when the WHERE is false, it will behave the same as if the OPTIONAL MATCH failed, so newly introduced variables in the OPTIONAL MATCH would be set to null.
If you want the WHERE to filter out rows, pair it with a WITH clause instead:
MATCH (n:`Person::Current` {uuid: $person_id}), (t:`Trait::Current` {uuid: $trait_id})
OPTIONAL MATCH (pt:`PersonTrait::Current`)
WITH n, t, pt
WHERE NOT (
((n)-[:PERSON_TRAIT]->(pt)-[:PERSON_TRAIT]->(t) AND exists(pt.has)) OR
(t)-[:GIVES_TRAIT]->(:`GivesTrait::Current`)-[:GIVES_TRAIT]->(:`Trait::Current`)<-[:PERSON_TRAIT]-(pt {has: false})<-[:PERSON_TRAIT]-(n)
)
RETURN count(t) as res

Using Union results to filter a match using With

I wish to use the results of a UNION (n) as a filter for a subsequent match.
MATCH (n:Thing)-<<Insert valid match filters here>>
RETURN n
UNION
MATCH (n:Thing)-<<Insert a different set of match filters here>>
RETURN n;
n feeds into:
MATCH (n)-[:RELTYPE1]->(a:Artifact);
RETURN a;
I would expect to use a WITH statement, but I've struggled to figure out how structure the statement.
MATCH (n:Thing)-<<Insert valid match filters here>>
RETURN n
UNION
MATCH (n:Thing)-<<Insert a different set of match filters here>>
WITH n
MATCH (n)-[:RELTYPE1]->(a:Artifact);
RETURN a;
This was my original attempt, but the WITH is interpreted as the start of subquery of the UNION's second match (which makes sense).
I can see a few inelegant ways to make this work, but what is the proper approach?
I have been looking at your union example and it makes sense to me but I cannot see how I could make it work. But I am certainly not the guy with all of the answers. Is there a reason you couldn't do something like this though...
MATCH (n:Thing)
WHERE n.name = 'A'
WITH collect(n) as n1
MATCH (n:Thing)
WHERE n.name = 'B'
WITH n1 + collect(n) AS both
UNWIND both AS n
MATCH (n)-[:RELTYPE1]->(a:Artifact);
RETURN a;

Resources