RETURN and Aggregation
Transform and aggregate query results.
RETURN Clause
Returning Nodes
Returning Properties
// Single property
MATCH (p:Person)
RETURN p.name
// Multiple properties
MATCH (p:Person)
RETURN p.name, p.age, p.email
Aliases
// Using AS keyword
MATCH (p:Person)
RETURN p.name AS personName, p.age AS years
// Implicit alias (without AS - not recommended)
MATCH (p:Person)
RETURN p.name personName
Distinct Results
Expressions
// Computed values
MATCH (p:Person)
RETURN p.name, p.age + 1 AS nextYearAge
// String concatenation
MATCH (p:Person)
RETURN p.firstName + ' ' + p.lastName AS fullName
Arithmetic uses + - * / % ^ with standard precedence; see
Numeric Functions.
Standalone RETURN
A query can consist of just a RETURN (no MATCH), which is handy for
evaluating expressions:
Aggregation Functions
COUNT
// Count all matches
MATCH (p:Person)
RETURN COUNT(p) AS personCount
// Count non-NULL values
MATCH (p:Person)
RETURN COUNT(p.email) AS withEmail
// Count all rows (including NULLs)
MATCH (p:Person)
RETURN COUNT(*) AS total
// Count distinct
MATCH (p:Person)-[:WORKS_AT]->(c:Company)
RETURN COUNT(DISTINCT c.name) AS companyCount
SUM
// Total of all ages
MATCH (p:Person)
RETURN SUM(p.age) AS totalAge
// Total salary by department
MATCH (e:Employee)
RETURN e.department, SUM(e.salary) AS totalSalary
AVG
// Average age
MATCH (p:Person)
RETURN AVG(p.age) AS averageAge
// Average by group
MATCH (p:Person)
RETURN p.city, AVG(p.age) AS avgAge
MIN / MAX
// Minimum and maximum
MATCH (p:Person)
RETURN MIN(p.age) AS youngest, MAX(p.age) AS oldest
// By city
MATCH (p:Person)
RETURN p.city, MIN(p.age), MAX(p.age)
COLLECT
// Collect into list
MATCH (p:Person)
RETURN COLLECT(p.name) AS allNames
// Collect with filter
MATCH (p:Person)
WHERE p.city = 'NYC'
RETURN COLLECT(p.name) AS nyNames
// Nested collect
MATCH (p:Person)-[:WORKS_AT]->(c:Company)
RETURN c.name, COLLECT(p.name) AS employees
Standard Deviation
// Sample standard deviation
MATCH (p:Person)
RETURN stdDev(p.age) AS ageStdDev
// Population standard deviation
MATCH (p:Person)
RETURN stdDevP(p.age) AS agePopulationStdDev
Percentiles
// Continuous percentile (interpolated)
MATCH (p:Person)
RETURN percentileCont(p.age, 0.5) AS medianAge
// Discrete percentile (actual value)
MATCH (p:Person)
RETURN percentileDisc(p.age, 0.9) AS p90Age
Grouping Results
Implicit Grouping
// Group by company
MATCH (p:Person)-[:WORKS_AT]->(c:Company)
RETURN c.name, AVG(p.age) AS avgAge
// Multiple group keys
MATCH (p:Person)
RETURN p.city, p.department, COUNT(*) AS count
WITH for Pre-aggregation
// Filter after aggregation
MATCH (p:Person)
WITH p.city AS city, COUNT(*) AS cityCount
WHERE cityCount > 10
RETURN city, cityCount
WITH Scope
A WITH clause projects, then filters — so its own WHERE sees the aliases
that clause defines:
WITH accepts the same expressions RETURN does: arithmetic, comparisons,
function calls, patterns.
The rows the predicate sees also keep the bindings that came into the clause,
so filtering on a variable the WITH did not carry forward works:
MATCH (p:Person)-[r:KNOWS]->(other)
WITH p
WHERE r.weight > 5 // `r` is not projected, but still visible
RETURN p.name
Standard Cypher drops those from scope and would reject the second query. This is deliberately more permissive.
ORDER BY, SKIP and LIMIT attach to whichever clause they follow. Written
after a WITH they sort what that clause projects; written after the trailing
RETURN they sort its output, and can name either its columns or the names the
WITH carried:
MATCH (p:Person)
WITH p, p.age AS age
ORDER BY age DESC LIMIT 10 -- the WITH's: top ten by age
RETURN p.name AS name
ORDER BY name -- the RETURN's: those ten, alphabetical
Returning Everything
* stands for every variable currently bound:
MATCH (p:Person)-[r:KNOWS]->(other)
RETURN * -- p, r and other
MATCH (p:Person)-[r:KNOWS]->(other)
WITH * -- carry all three forward
WHERE r.weight > 5
RETURN p.name
It combines with named items, which is the usual way to add something without listing what you already have:
* means variables, not the columns an earlier stage produced: after
WITH p, p.age AS age, a following WITH * carries p and age, and nothing
named p.name.
* with an aggregate groups by everything in scope
WITH *, count(m) AS friends groups by every bound variable, m included —
so it produces one row per (n, m) pair, not one row per n. When you want
a count per n, name the grouping key: WITH n, count(m) AS friends.
Advanced Return Patterns
Conditional Values
// Using CASE
MATCH (p:Person)
RETURN p.name,
CASE
WHEN p.age < 18 THEN 'minor'
WHEN p.age < 65 THEN 'adult'
ELSE 'senior'
END AS category
Complex Expressions
// Computed columns
MATCH (p:Person)
RETURN
p.name,
p.age,
p.age * 12 AS ageInMonths,
date().year - p.birthYear AS calculatedAge
Returning Maps
// Create map from properties
MATCH (p:Person)
RETURN {
name: p.name,
age: p.age,
city: p.city
} AS personMap
Returning Paths
// Return full path
MATCH p = (a:Person)-[:KNOWS*1..3]->(b:Person)
RETURN p, length(p) AS hops
// Path nodes and relationships
MATCH p = (a:Person)-[:KNOWS]->(b:Person)
RETURN nodes(p) AS pathNodes, relationships(p) AS pathRels
Path Functions
nodes()
Returns a list of all nodes in a path.
MATCH p = (a:Person {name: 'Alice'})-[:KNOWS*1..3]->(b:Person)
RETURN nodes(p) as pathNodes
// [(:Person {name: 'Alice'}), (:Person {...}), ...]
// Count nodes in path
MATCH p = shortestPath((a)-[:KNOWS*]-(b))
RETURN size(nodes(p)) as nodeCount
relationships()
Returns a list of all relationships in a path.
MATCH p = (a:Person {name: 'Alice'})-[:KNOWS*1..3]->(b:Person)
RETURN relationships(p) as pathRels
// [(:KNOWS {...}), (:KNOWS {...}), ...]
// Get relationship types
MATCH p = (a)-[*1..3]-(b)
RETURN [r IN relationships(p) | type(r)] as relTypes
length()
Returns the number of relationships in a path. length() also accepts a string
or list and returns its length.
Graph and Scalar Functions
These functions inspect nodes and relationships:
| Function | Returns |
|---|---|
labels(n) |
List of a node's labels |
type(r) |
A relationship's type |
properties(x) |
Property map of a node/relationship (or the map itself) |
keys(x) |
List of property names |
id(x) |
Integer identity of a node/relationship |
elementId(x) |
Identity as a string |
startNode(r) / endNode(r) |
The source / target node of a relationship |
MATCH (n:Person {name: 'Alice'})
RETURN labels(n) AS labels, properties(n) AS props, id(n) AS id
MATCH ()-[r]->()
RETURN type(r) AS rel_type, startNode(r).name AS from, endNode(r).name AS to
labels() can also be used as a filter, e.g. WHERE 'Admin' IN labels(n).
Common Use Cases
Dashboard Metrics
// User statistics
MATCH (u:User)
RETURN
COUNT(*) AS totalUsers,
COUNT(u.verifiedEmail) AS verifiedUsers,
AVG(u.loginCount) AS avgLogins,
MAX(u.lastLogin) AS mostRecentLogin
Reports
// Monthly signups
MATCH (u:User)
WITH date.truncate('month', u.createdAt) AS month, COUNT(*) AS signups
RETURN month, signups
ORDER BY month