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. If you want more details on how the data is loaded, see the section called “Importing CSV”.
LOAD CSV WITH HEADERS FROM "http://neo4j.com/docs/3.1.0-SNAPSHOT/csv/intro/movies.csv" AS line CREATE (m:Movie { id:line.id,title:line.title, released:toInt(line.year)}); LOAD CSV WITH HEADERS FROM "http://neo4j.com/docs/3.1.0-SNAPSHOT/csv/intro/persons.csv" AS line MERGE (a:Person { id:line.id }) ON CREATE SET a.name=line.name; LOAD CSV WITH HEADERS FROM "http://neo4j.com/docs/3.1.0-SNAPSHOT/csv/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 "http://neo4j.com/docs/3.1.0-SNAPSHOT/csv/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
movie | two_of_cast |
---|---|
4 rows | |
|
|
|
|
|
|
|
|
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))
extract(n IN nodes(path | coalesce(n.name, n.title)) |
---|---|
6 rows | |
|
|
|
|
|
|
|
|
|
|
|
|
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
sum_large_squares |
---|
1 row |
|
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")
movie | filter(actor IN cast WHERE actor.name STARTS WITH "M") |
---|---|
4 rows | |
|
|
|
|
|
|
|
|
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 list 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
movie | cast |
---|---|
3 rows | |
|
|
|
|
|
|