Search code examples
neo4jcypher

Cypher: search for nodes that satisfy all constraints


I'm trying to write a Cypher query that will return all node sets that satisfy every specified condition. For example, if we have (Actor) - [ACTING] - (Movie), (Director) - [DIRECTING] - (Movie) and (Composer) - [COMPOSING_MUSIC] - (Movie); and query like: Actor1 Movie1 Director1 Actor2 Movie1 Director1 Actor2 Movie2 Director2 Actor1 Movie3 Director3 Composer1

What are the sets of nodes (Actor), (Movie), (Director), (Composer) that satisfy All constraints from above?

I tried Matching each edge

match (a1:Actor)-[:ACTING]-(m1:Movie),
      (a2:Actor)-[:ACTING]-(m1:Movie),
      ...
      (d1:Director)-[:DIRECTING]-(m1:Movie),
      (d2:Director)-[:DIRECTING]-(m2:Movie),
      ...
where a1<>a2 and d1<>d2 ...

However, this will give me a cross matching with every match statement. So my result set will becom larger (instead of smaller) with each new constraint. Is there a canonic way of solving these typo of problems? The next edge(constraint) should be only within filtered (previous) results?

Thank you


Solution

  • You did not specify the directionality of your relationships, so in my answer I am going to assume that all your relationships are directed towards Movie nodes. For example: (:Actor)-[:ACTING]->(:Movie).

    The following queries avoid creating a cartesian product, as the outer MATCH just creates one row for each movie, and each COLLECT just adds one value (a list) to each row (instead of multiplying the number of rows).

    Neo4j 5.6+ solution

    This query uses the COLLECT subquery clause (available in neo4j 5.6+) to return a list of the actors, directors, and composers for each movie:

    MATCH (m:Movie)
    RETURN m,
      COLLECT{ MATCH (m)<-[:ACTING]-(a) RETURN a } AS actors,
      COLLECT{ MATCH (m)<-[:DIRECTING]-(d) RETURN d } AS directors,
      COLLECT{ MATCH (m)<-[:COMPOSING_MUSIC]-(c) RETURN c } AS composers
    

    More general solution

    This query uses the CALL subquery clause, which has existed for a long time, to accomplish the same thing:

    MATCH (m:Movie)
    CALL{ WITH m MATCH (m)<-[:ACTING]-(a) RETURN COLLECT(a) AS actors }
    CALL{ WITH m MATCH (m)<-[:DIRECTING]-(d) RETURN COLLECT(d) AS directors }
    CALL{ WITH m MATCH (m)<-[:COMPOSING_MUSIC]-(c) RETURN COLLECT(c) AS composers }
    RETURN m, actors, directors, composers