Negation of Relationship

match  (m:Movie)
where m.title contains "Matrix"
match (c:Person)-[:ACTED_IN]-(d)
where not (c)-[:ACTED_IN]->(m) 
return distinct c.name
order by c.name asc

Trying to find all people who didnt act in a Matrix movie. Is there an easier way?

Actually your query finds people who didn't act in one specific matrix movie but they could have acted in others.

match  (m:Movie) where m.title contains "Matrix" with collect(m) as movies
match (c:Person) where size[(c)-[:ACTED_IN]->(d) WHERE d IN movies] = 0 
return c.name
order by c.name asc

or

match  (m:Movie) where m.title contains "Matrix" with collect(m) as movies
match (c:Person)-[:ACTED_IN]->(d)
with c, collect(d) as actorMovies where none(m in movies where m in actorMovies)
return c.name
order by c.name asc

Hmm...But the first two statements collect all the Movies with a title that contains "Matrix" in it - if you return right after that, that's the result you get.

Then, if you run this:
match (m:Movie)
where m.title contains "Matrix"
match (c:Person)-[:ACTED_IN]-> (m)
return c.name, m.title

You get a list of all actors/actresses of all Matrix movies because of the first two statements. So, how is my resulting query only specific to one Matrix movie?

Also, both your queries return an error :confused:


(few minutes later)
Modified your second query and seems to work -

match  (m:Movie) where m.title contains "Matrix" with collect(m) as movies
match (c:Person)-[:ACTED_IN]->(d)
with d, c, movies, collect(d) as actorMovies where none(m in movies where m in actorMovies)
return c.name
order by c.name asc

But it returns the wrong results. Keanu Reeves, for example, shows up in that result.

remove the d you don't want to aggregate by the movie. but only by the person

match  (m:Movie) where m.title contains "Matrix" with collect(m) as movies
match (c:Person)-[:ACTED_IN]->(d)
with c, movies, collect(d) as actorMovies where none(m in movies where m in actorMovies)
return c.name
order by c.name asc

Can you explain why the query I have doesnt work? That was really my question. Why every other part seems to return what I am looking for but the last few statements do not. See explanation below:

Hmm...But the first two statements collect all the Movies with a title that contains "Matrix" in it - if you return right after that, that's the result you get.

Then, if you run this:
match (m:Movie)
where m.title contains "Matrix"
match (c:Person)-[:ACTED_IN]-> (m)
return c.name, m.title

You get a list of all actors/actresses of all Matrix movies because of the first two statements. So, how is my resulting query only specific to one Matrix movie?

Because the match is executed per row.

Imagine an actor only played in one matrix movie.

so you get 3 rows of matrix movies

and for each of them the person is checked against one
with the negation filtering it out, per movie

so the person is filtered out once and kept twice

so in the end you have the person still returned.

The thing to keep in mind is to only treat a variable as a list of values if it's a collection (either you've used collect() to generate it or a pattern comprehension or from a procedure or function that returns lists).

If it's just a variable to a node match (even if potential several nodes match), that variable will only represent a single possible value per row, it can't be treated as a list of all matching variables.

Just as I start to think I understand Cypher, then I encounter a new thing...

Is there a way to make this Query simpler? Or is there something missing in the Cypher language that would allow this to be more easily expressed?

I find it a bit difficult to puzzle through.

I wasn't able to come up with the query myself. If there was a simpler variant, I think I could understand this better.

(Sort like with a foreign language, it's easy to understand something said to you than trying to produce a complex sentence.)

With Neo4j 4.x we now have existential subqueries, that might be a more readable alternative:

MATCH (c:Person)
WHERE NOT EXISTS {
  MATCH (c)-[:ACTED_IN]->(m:Movie)
  WHERE m.title CONTAINS "Matrix"
}
RETURN c.name 
ORDER BY c.name ASC

That said, this won't be as efficient as other queries that begin with matching to and collecting Matrix movies (provided there's an index on :Movie(title) ).

Here's another approach, still collecting Matrix movies, but using a list predicate over a pattern comprehension to make sure that none of the movies the person acted in are matrix movies.

MATCH (m:Movie) 
WHERE m.title CONTAINS "Matrix" 
WITH collect(m) as movies
MATCH (c:Person)
WHERE none(m in [(c)-[:ACTED_IN]->(m) | m] WHERE m IN movies)
RETURN c.name 
ORDER BY c.name ASC

EDIT fixed up the ordering clause

2 Likes