Collections
Collections
Lists, UNWIND, and collection functions
What You'll Learn
- List Creation - Create and manipulate lists
- UNWIND - Expand lists to rows
- List Functions - Filter, map, reduce
- List Comprehensions - Compact list operations
# Cell 1 — ParametersUSERNAME = "_FILL_ME_IN_" # Set your email before running# Cell 2 — Connectfrom graph_olap import GraphOLAPClientclient = GraphOLAPClient(username=USERNAME)
# Cell 3 — Provisionfrom notebook_setup import provisionpersonas, conn = provision(USERNAME)analyst = personas["analyst"]admin = personas["admin"]ops = personas["ops"]client = analyst
print(f"Connected | {conn.query_scalar('MATCH (n) RETURN count(n)')} nodes")Cypher has first-class support for lists (ordered collections) and maps (key-value pairs).
You can build lists from literal values, or aggregate query results into lists with COLLECT().
List literals are written with square brackets:
RETURN [1, 2, 3] AS numbersRETURN ['alice', 'bob'] AS namesCOLLECT() aggregates values from multiple rows into a single list — it is the list equivalent of COUNT() or SUM():
MATCH (p:Person) RETURN COLLECT(p.name) AS all_namesMaps are key-value structures written with curly braces:
RETURN {name: 'Alice', age: 30} AS person_mapEvery node’s properties are internally stored as a map. You can extract them with properties(n).
# --- List literals ---result = conn.query("RETURN [1, 2, 3] AS numbers, ['a', 'b', 'c'] AS letters")result.show()# --- COLLECT() aggregates values into a list ---df = conn.query_df("MATCH (n) RETURN labels(n)[0] AS label, COLLECT(n.name) AS names LIMIT 5")df# --- Map literals and node properties ---result = conn.query("RETURN {name: 'Alice', role: 'Engineer'} AS map_literal")result.show()# --- Extract property maps from nodes ---# properties(n) returns all properties as a map; keys(n) returns the property namesdf = conn.query_df("MATCH (n) RETURN keys(n) AS property_keys, properties(n) AS props LIMIT 3")dfUNWIND is the inverse of COLLECT() — it takes a list and expands each element into its own row.
This is essential when you need to process list elements individually, or when you want to pass a
list of values into a query as parameters.
Basic pattern:
UNWIND [1, 2, 3] AS numRETURN numThis produces three rows, one for each element.
Common use case — feed a list of lookup values into a MATCH:
UNWIND ['Alice', 'Bob'] AS nameMATCH (p:Person {name: name})RETURN pRound-trip — COLLECT then UNWIND lets you aggregate, filter, and re-expand:
MATCH (p:Person)-[:WORKS_AT]->(c:Company)WITH c, COLLECT(p.name) AS employeesWHERE size(employees) > 2UNWIND employees AS empRETURN c.name, emp# --- UNWIND expands a list into individual rows ---df = conn.query_df("UNWIND [10, 20, 30] AS value RETURN value, value * 2 AS doubled")df# --- UNWIND with MATCH: look up nodes from a list ---df = conn.query_df(""" UNWIND ['Person', 'Company'] AS lbl MATCH (n) WHERE lbl IN labels(n) RETURN lbl, count(n) AS count""")df# --- Round-trip: COLLECT then UNWIND ---df = conn.query_df(""" MATCH (n) WITH labels(n)[0] AS label, COLLECT(n.name) AS names WHERE size(names) >= 2 UNWIND names AS name RETURN label, name LIMIT 10""")dfCypher provides a rich set of built-in functions for working with lists:
| Function | Description | Example |
|---|---|---|
size(list) | Number of elements | size([1,2,3]) => 3 |
head(list) | First element | head([1,2,3]) => 1 |
tail(list) | All elements except the first | tail([1,2,3]) => [2,3] |
last(list) | Last element | last([1,2,3]) => 3 |
range(start, end) | Generate integer sequence | range(0, 4) => [0,1,2,3,4] |
reverse(list) | Reverse order | reverse([1,2,3]) => [3,2,1] |
list[idx] | Index access (0-based) | [10,20,30][1] => 20 |
list[-1] | Negative index (from end) | [10,20,30][-1] => 30 |
list[start..end] | Slice (exclusive end) | [10,20,30,40][1..3] => [20,30] |
You can also check membership with IN:
RETURN 2 IN [1, 2, 3] AS found# --- Basic list functions ---df = conn.query_df(""" WITH [10, 20, 30, 40, 50] AS nums RETURN size(nums) AS length, head(nums) AS first, last(nums) AS last_elem, tail(nums) AS all_but_first, reverse(nums) AS reversed""")df# --- Indexing, slicing, and range() ---df = conn.query_df(""" WITH [10, 20, 30, 40, 50] AS nums RETURN nums[0] AS first_by_index, nums[-1] AS last_by_index, nums[1..3] AS slice_1_to_3, range(0, 4) AS generated_range""")df# --- Membership test with IN ---df = conn.query_df(""" WITH ['Alice', 'Bob', 'Carol'] AS team RETURN 'Alice' IN team AS alice_present, 'Dave' IN team AS dave_present""")dfList comprehensions let you filter and transform lists in a single expression, similar to
Python’s [x for x in list if condition].
The Cypher syntax is:
[variable IN list WHERE predicate | expression]- The
WHEREclause is optional (omit it to transform all elements). - The
| expressionpart is optional (omit it to just filter). - You can use both together to filter AND transform.
Examples:
// Filter: keep only values greater than 2RETURN [x IN [1,2,3,4,5] WHERE x > 2] AS filtered// => [3, 4, 5]
// Transform: double every valueRETURN [x IN [1,2,3] | x * 2] AS doubled// => [2, 4, 6]
// Filter + transform: square values greater than 2RETURN [x IN [1,2,3,4,5] WHERE x > 2 | x * x] AS filtered_and_squared// => [9, 16, 25]List comprehensions are especially powerful when combined with COLLECT() results from a query.
# --- Filter: keep only even numbers ---result = conn.query("RETURN [x IN range(1, 10) WHERE x % 2 = 0] AS evens")result.show()# --- Transform: square each value ---result = conn.query("RETURN [x IN [1, 2, 3, 4, 5] | x * x] AS squares")result.show()# --- Filter + transform on real data ---# Collect node names, then keep only those starting with a specific letterdf = conn.query_df(""" MATCH (n) WITH COLLECT(DISTINCT n.name) AS all_names RETURN size(all_names) AS total, [name IN all_names WHERE name STARTS WITH 'A'] AS a_names""")dfKey Takeaways
- Lists created with [1,2,3] or COLLECT()
- UNWIND expands lists to individual rows
- List functions: head(), tail(), size(), range()
- Comprehensions: [x IN list WHERE cond | expr]