GraphGists

Cypher can create and consume more complex data structures out of the box. As already mentioned you can create literal lists ([1,2,3]) and maps ({name: value}) within a statement.

There are a number of functions that work with lists. They range from simple ones like size(list) that returns the size of a list to reduce, which runs an expression against the elements and accumulates the results.

Let’s first load a bit of data into the graph.

LOAD CSV WITH HEADERS FROM "https://raw.githubusercontent.com/neo4j/neo4j/2.3/manual/cypher/cypher-docs/src/docs/graphgists/intro/movies.csv" AS line
CREATE (m:Movie {id:line.id,title:line.title, released:toInt(line.year)});
LOAD CSV WITH HEADERS FROM "https://raw.githubusercontent.com/neo4j/neo4j/2.3/manual/cypher/cypher-docs/src/docs/graphgists/intro/persons.csv" AS line
MERGE (a:Person {id:line.id}) ON CREATE SET a.name=line.name;
LOAD CSV WITH HEADERS FROM "https://raw.githubusercontent.com/neo4j/neo4j/2.3/manual/cypher/cypher-docs/src/docs/graphgists/intro/roles.csv" AS line
MATCH (m:Movie {id:line.movieId})
MATCH (a:Person {id:line.personId})
CREATE (a)-[:ACTED_IN {roles:[line.role]}]->(m);
LOAD CSV WITH HEADERS FROM "https://raw.githubusercontent.com/neo4j/neo4j/2.3/manual/cypher/cypher-docs/src/docs/graphgists/intro/movie_actor_roles.csv" AS line FIELDTERMINATOR ";"
MERGE (m:Movie {title:line.title}) ON CREATE SET m.released = toInt(line.released)
MERGE (a:Person {name:line.actor}) ON CREATE SET a.born = toInt(line.born)
MERGE (a)-[:ACTED_IN {roles:split(line.characters,",") }]->(m)

Now, let’s try out data structures.

To begin with, collect the names of the actors per movie, and return two of them:

MATCH (movie:Movie)<-[:ACTED_IN]-(actor:Person)
RETURN movie.title as movie, collect(actor.name)[0..2] as two_of_cast

You can also access individual elements or slices of a list quickly with list[1] or list[5..-5]. Other functions to access parts of a list are head(list), tail(list) and last(list).

List Predicates

When using lists and arrays in comparisons you can use predicates like value IN list or any(x IN list WHERE x = value). There are list predicates to satisfy conditions for all, any, none and single elements.

MATCH path = (:Person)-->(:Movie)<--(:Person)
WHERE any(n in nodes(path) WHERE n.name = 'Michael Douglas')
RETURN extract(n IN nodes(path)| coalesce(n.name, n.title))

List Processing

Oftentimes you want to process lists to filter, aggregate (reduce) or transform (extract) their values. Those transformations can be done within Cypher or in the calling code. This kind of list-processing can reduce the amount of data handled and returned, so it might make sense to do it within the Cypher statement.

A simple, non-graph example would be:

WITH range(1,10) as numbers
WITH extract(n in numbers | n*n) as squares
WITH filter(n in squares WHERE n > 25) as large_squares
RETURN reduce( a = 0, n in large_squares | a + n ) as sum_large_squares

In a graph-query you can filter or aggregate collected values instead or work on array properties.

MATCH (m:Movie)<-[r:ACTED_IN]-(a:Person)
WITH m.title as movie, collect({name: a.name, roles: r.roles}) as cast
RETURN movie, filter(actor IN cast WHERE actor.name STARTS WITH "M")

Unwind Lists

Sometimes you have collected information into a list, but want to use each element individually as a row. For instance, you might want to further match patterns in the graph. Or you passed in a collection of values but now want to create or match a node or relationship for each element. Then you can use the UNWIND clause to unroll a list into a sequence of rows again.

For instance, a query to find the top 3 co-actors and then follow their movies and again list the cast for each of those movies:

MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie)<-[:ACTED_IN]-(colleague:Person)
WHERE actor.name < colleague.name
WITH actor, colleague, count(*) AS frequency, collect(movie) AS movies
ORDER BY frequency DESC
LIMIT 3
UNWIND movies AS m
MATCH (m)<-[:ACTED_IN]-(a)
RETURN m.title AS movie, collect(a.name) AS cast