Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"query/matching",
"query/paths",
"query/aggregation",
"query/composition",
"query/subqueries",
"query/functions",
"query/writing",
Expand Down
59 changes: 14 additions & 45 deletions query/aggregation.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Results, Aggregation and Lists"
sidebarTitle: "Results and Aggregation"
description: "Shape results with WITH, DISTINCT, ORDER BY, SKIP, LIMIT and UNION, chain queries with NEXT and WHEN, then aggregate, unwind and transform lists."
description: "Shape results with WITH, DISTINCT, ORDER BY, SKIP, LIMIT and UNION, then aggregate, unwind and transform lists."
---

These examples use the [sample graph](/query/matching) from the matching page.
Expand Down Expand Up @@ -45,7 +45,7 @@ RETURN n.name, n.age
ORDER BY n.age DESC SKIP 1 LIMIT 2
```

- You can sort by several keys, by aliases and by expressions. Ascending is the default.
- You can sort by several keys, by aliases and by expressions. Ascending is the default, and `ASCENDING` / `DESCENDING` are synonyms of `ASC` / `DESC`.
- `SKIP` and `LIMIT` combine for pagination: `SKIP 10 LIMIT 10` returns the 11th to 20th rows.
- `SKIP` and `LIMIT` take constant expressions.
- `RETURN DISTINCT ... ORDER BY` can only sort by returned columns.
Expand Down Expand Up @@ -97,48 +97,7 @@ RETURN c.name AS name

`UNION` removes duplicates and `UNION ALL` keeps them. All branches must return the same column names, and each column must keep one value type across the branches.

## NEXT

`NEXT` chains queries in one request: the columns returned by the query before it become the incoming rows of the query after it.

```cypher
MATCH (n:Person)
RETURN n.name AS nm, n.age AS age
NEXT
RETURN nm
ORDER BY nm LIMIT 2

MATCH (n:Person)
RETURN n.city AS city, count(*) AS people
NEXT
RETURN max(people) AS biggestCity
```

- Every expression in a `RETURN` followed by `NEXT` needs an alias (`AS`).
- A later query sees what an earlier one wrote: `CREATE (x:Tag {v: 1}) RETURN x NEXT MATCH (t:Tag) RETURN count(t)`.

## Conditional queries with WHEN

`WHEN <condition> THEN <query> [WHEN ... THEN ...] [ELSE <query>]` runs the first branch whose condition holds. Branches return the same columns, and they can also write.

```cypher
WHEN true THEN RETURN 1 AS x ELSE RETURN 2 AS x

MATCH (n:Person)
RETURN n.name AS nm, n.age AS age
NEXT
WHEN age > 30 THEN RETURN nm, 'senior' AS band
ELSE RETURN nm, 'junior' AS band

MATCH (n:Person)
CALL (n) {
WHEN n.age > 30 THEN RETURN 'senior' AS band
ELSE RETURN 'junior' AS band
}
RETURN n.name, band
```

A `WHEN` can start a query, follow `NEXT`, or form the body of a [`CALL` subquery](/query/subqueries#call-subqueries), where it runs once per row. Several `WHEN` queries combine with `UNION`: `{ WHEN c1 THEN RETURN 1 AS x } UNION { WHEN c2 THEN RETURN 2 AS x }`.
To run several queries in one request, or choose which query runs, see [NEXT and WHEN](/query/composition).

## Aggregation

Expand Down Expand Up @@ -176,6 +135,16 @@ ORDER BY c DESC

MATCH (n:Person)
RETURN stDev(n.age) AS sd, percentileCont(n.age, 0.5) AS median, percentileDisc(n.age, 0.9) AS p90

MATCH (n:Person)
RETURN stDev(n.age) AS sample, stDevP(n.age) AS population

MATCH (n:Person)
WITH n, 0.25 AS q
RETURN percentileCont(n.age, q) AS firstQuartile

UNWIND [[1, 2], [1, 3], [0, 9]] AS x
RETURN min(x) AS lo, max(x) AS hi
```

| Aggregate | Notes |
Expand Down Expand Up @@ -246,7 +215,7 @@ RETURN size([1, 2]), head([1, 2]), last([1, 2]), tail([1, 2]), reverse([1, 2, 3]
MATCH (n:Person {name: 'Alice'})
RETURN n.tags[0], size(n.tags)

RETURN [1, 2] + [3], [1, 2] + 3, 'a' || 'b'
RETURN [1, 2] + [3], [1, 2] + 3, 0 + [1, 2], 'a' || 'b'
```

- Indexing and slicing work on literals, list properties and function results (`labels(n)[0]`). Negative indexes count from the end, and an out-of-range index returns `null`.
Expand Down
25 changes: 23 additions & 2 deletions query/cheatsheet.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ RETURN [n IN nodes(p) | n.name], length(p), reduce(t = 0, r IN relationships(p)

## WITH, ORDER BY, SKIP, LIMIT, UNION, NEXT, WHEN

[Results and aggregation](/query/aggregation)
[Results and aggregation](/query/aggregation), [NEXT and WHEN](/query/composition)

```cypher
MATCH (n:Person)
Expand Down Expand Up @@ -221,9 +221,20 @@ RETURN n.name AS nm, n.age AS age
NEXT
WHEN age > 30 THEN RETURN nm, 'senior' AS band
ELSE RETURN nm, 'junior' AS band

MATCH (n:Person)
WHERE n.age > 30
RETURN n AS p
NEXT
MATCH (p)-[:KNOWS]->(f)
RETURN p.name, f.name

WHEN COUNT { MATCH (n:Person) } > 3 THEN RETURN 'big' AS size
ELSE RETURN 'small' AS size
```

- Expressions in `WITH`, and in a `RETURN` followed by `NEXT`, must be aliased.
- Expressions in `WITH`, and in a `RETURN` followed by `NEXT`, must be aliased. Only the returned columns carry over a `NEXT`.
- `WHEN` branches return the same columns. Without `ELSE`, a conditional with no matching branch returns no rows.
- Nulls sort last ascending and first descending.

## Aggregation
Expand Down Expand Up @@ -293,6 +304,16 @@ RETURN p.name, COUNT { (p)-[:KNOWS]->() } AS outDegree
MATCH (p:Person)
RETURN p.name, COLLECT { MATCH (p)-[:KNOWS]->(f) RETURN f.name } AS friends

MATCH (c:Company)
RETURN c.name, COLLECT { MATCH (p:Person)-[:WORKS_AT]->(c) RETURN p.name ORDER BY p.age DESC LIMIT 1 } AS oldest

MATCH (p:Person)
CALL (*) {
MATCH (p)-[:KNOWS]->(f)
RETURN count(f) AS deg
}
RETURN p.name, deg

MATCH (p:Person)
CALL (p) {
MATCH (p)-[:KNOWS]->(f)
Expand Down
218 changes: 218 additions & 0 deletions query/composition.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
---
title: "Chaining and Conditional Queries"
sidebarTitle: "NEXT and WHEN"
description: "Chain queries in one request with NEXT, and choose which query runs with WHEN ... THEN ... ELSE."
---

These examples use the [sample graph](/query/matching) from the matching page.

## NEXT

`NEXT` runs the query before it, then hands the columns it returns to the query after it, as that query's incoming rows. Use it to run several steps in one request, or to start a new stage from a smaller set of rows.

```cypher
MATCH (n:Person)
RETURN n.name AS nm, n.age AS age
NEXT
RETURN nm
ORDER BY nm LIMIT 2

MATCH (n:Person)
RETURN n.city AS city, count(*) AS people
NEXT
RETURN max(people) AS biggestCity
```

### Passing nodes on

A returned node is still a node in the next step, so you can match from it:

```cypher
MATCH (n:Person)
WHERE n.age > 30
RETURN n AS p
NEXT
MATCH (p)-[:KNOWS]->(f)
RETURN p.name, f.name
```

### Longer chains

A chain can have as many steps as you need, and each step is a full query:

```cypher
MATCH (n:Person)
RETURN n.name AS nm, n.age AS age
NEXT
WITH nm, age
WHERE age > 28
RETURN nm, age
NEXT
RETURN count(*) AS over28
```

### After a UNION

A step can be a `UNION`, and the next step reads the combined rows:

```cypher
MATCH (p:Person)
RETURN p.name AS name
UNION
MATCH (c:Company)
RETURN c.name AS name
NEXT
RETURN count(*) AS total
```

A whole chain in braces can also be one side of a `UNION`:

```cypher
{ MATCH (p:Person) RETURN p.age AS v NEXT RETURN max(v) AS v }
UNION
{ MATCH (c:Company) RETURN c.founded AS v NEXT RETURN max(v) AS v }
```

### Inside subqueries

`NEXT` works inside [`CALL`, `EXISTS` and `COUNT`](/query/subqueries) subqueries, where the chain runs once for each outer row:

```cypher
MATCH (c:Company)
CALL (c) {
MATCH (p:Person)-[:WORKS_AT]->(c)
RETURN p.age AS age
NEXT
RETURN avg(age) AS avgAge
}
RETURN c.name, avgAge

MATCH (p:Person)
RETURN p.name, COUNT {
MATCH (p)-[:KNOWS]->(f)
RETURN f AS friend
NEXT
MATCH (friend)-[:KNOWS]->(fof)
RETURN fof
} AS friendsOfFriends
```

### Writes

Inside a [change](/query/writing), a later step sees what an earlier step wrote:

```cypher
CREATE (t:Tag {name: 'new'})
RETURN t
NEXT
MATCH (x:Tag)
RETURN count(x) AS tags
```

### Rules

- **Aliases.** Every expression in a `RETURN` followed by `NEXT` needs an alias. Without one: `Expression in a RETURN followed by NEXT must be aliased (use AS)`.
- **Scope.** Only the returned columns carry over. `MATCH (n:Person) RETURN n.name AS nm NEXT RETURN n.age` reports `Variable 'n' not found`, because the first step did not return `n`.
- `NEXT` is not yet available inside a `COLLECT { }` body.

## WHEN

`WHEN <condition> THEN <query>` runs a query when the condition holds. Add more `WHEN ... THEN ...` branches and an `ELSE <query>` as needed: the first branch whose condition holds is the one that runs.

```cypher
WHEN true THEN RETURN 1 AS x ELSE RETURN 2 AS x
```

### Conditions on the data

Conditions can be subqueries over the graph:

```cypher
WHEN EXISTS { MATCH (:Company {name: 'Acme'}) } THEN RETURN 'has Acme' AS result
ELSE RETURN 'no Acme' AS result

WHEN COUNT { MATCH (n:Person) } > 3 THEN RETURN 'big' AS size
ELSE RETURN 'small' AS size
```

### For each row, after NEXT

After `NEXT`, the conditions are checked for each incoming row, and each row takes the first branch that matches:

```cypher
MATCH (n:Person)
RETURN n.name AS nm, n.age AS age
NEXT
WHEN age > 35 THEN RETURN nm, 'over 35' AS band
WHEN age > 29 THEN RETURN nm, '30 to 35' AS band
ELSE RETURN nm, 'under 30' AS band
```

### For each row, in a CALL subquery

As the body of a [`CALL` subquery](/query/subqueries#call-subqueries), a conditional runs once per outer row, and a branch can match more data:

```cypher
MATCH (n:Person)
CALL (n) {
WHEN n.city = 'London' THEN
MATCH (n)-[:WORKS_AT]->(c)
RETURN c.name AS employer
ELSE RETURN 'elsewhere' AS employer
}
RETURN n.name, employer
```

### Without ELSE

When no branch matches and there is no `ELSE`, the conditional returns no rows, so `WHEN false THEN RETURN 1 AS x` returns nothing. In a `CALL`, rows that match no branch are dropped. Use `OPTIONAL CALL` to keep them with `null` values:

```cypher
MATCH (n:Person)
OPTIONAL CALL (n) {
WHEN n.age >= 40 THEN RETURN 'senior' AS band
}
RETURN n.name, band
```

### Several steps in a branch

Put a `NEXT` chain in braces:

```cypher
WHEN true THEN {
MATCH (n:Person)
RETURN n.city AS city, count(*) AS people
NEXT
RETURN max(people) AS biggest
}
ELSE RETURN 0 AS biggest
```

### Combining with UNION

Conditionals in braces combine with `UNION`. Each one names its columns with `AS`:

```cypher
{ WHEN true THEN RETURN 'a' AS x }
UNION
{ WHEN false THEN RETURN 'b' AS x ELSE RETURN 'c' AS x }
```

### Writes

Branches can write, inside a change. See [Conditional writes](/query/writing#conditional-writes):

```cypher
MATCH (n:Person)
CALL (n) {
WHEN n.age > 30 THEN CREATE (:Senior {name: n.name})
}
RETURN count(*)
```

### Rules

- **Same columns.** All branches return the same column names. Otherwise: `All branches of a conditional query must return the same column names`.
- **Where it goes.** A `WHEN` starts a query, follows `NEXT`, forms a `CALL` body, or is a braced side of a `UNION`. To branch on the results of a `MATCH`, return them and continue after `NEXT`, as above.
- **Not yet available:** `UNION` inside a branch, and `WHEN` inside an `EXISTS`, `COUNT` or `COLLECT` body.
Loading
Loading