ORDER BY
ORDER BY is a subclause that determines how the results of a RETURN or WITH clause are ordered.
As of Neo4j 5.24, it can also be used as a standalone clause, either on its own or in combination with SKIP/OFFSET or LIMIT.
ORDER BY defaults to sorting results in an ascending order, though it can be modified to sort results in a descending order.
ORDER BY relies on comparisons to sort the output (see Equality, ordering, and comparison of value types for more details).
You can sort on different values, such as node or relationship properties, IDs, or the result of expressions.
Unless ORDER BY is used, Neo4j does not guarantee the row order of a query result.
|
Example graph
A graph with the following schema is used for the examples below:
To recreate it, run the following query against an empty Neo4j database:
CREATE (o1:Order {id: 'ORD-001', orderDate: datetime('2024-05-01T10:00:00'), total: 550, status: 'shipped'}),
(o2:Order {id: 'ORD-002', orderDate: datetime('2024-05-02T14:30:00'), total: 1000, status: 'pending'}),
(o3:Order {id: 'ORD-003', orderDate: datetime('2024-05-03T09:15:00'), total: 550, status: 'pending'}),
(o4:Order {id: 'ORD-004', orderDate: datetime('2024-05-04T12:45:00'), total: 200}),
(o5:Order {id: 'ORD-005', orderDate: datetime('2024-05-05T15:00:00'), total: 800, status: 'shipped'}),
(i1:Item {name: 'Phone', price: 500}),
(i2:Item {name: 'Laptop', price: 1000}),
(i3:Item {name: 'Headphones', price: 250}),
(i4:Item {name: 'Charger', price: 50}),
(i5:Item {name: 'Keyboard', price: 200}),
(o1)-[:CONTAINS]->(i1),
(o1)-[:CONTAINS]->(i4),
(o2)-[:CONTAINS]->(i2),
(o3)-[:CONTAINS]->(i1),
(o3)-[:CONTAINS]->(i4),
(o4)-[:CONTAINS]->(i5),
(o5)-[:CONTAINS]->(i1),
(o5)-[:CONTAINS]->(i3),
(o5)-[:CONTAINS]->(i4)
Basic examples
ORDER BY can be used to sort the result by property values.
MATCH (o:Order)
RETURN o.id AS order,
o.total AS total
ORDER BY total
The nodes are returned, sorted by the value of the total properties in an ascending order.
| order | total |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|
Order by multiple property values by listing two or more properties in the ORDER BY subclause.
Cypher® sorts by the first property, and if values are equal, it moves to the next property, and so on.
MATCH (o:Order)
RETURN o.id AS order,
o.total AS total,
o.orderDate AS orderDate
ORDER BY total,
orderDate
This returns the nodes, sorted first by their total property, and then, for equal values, by their orderDate property.
| order | total | orderDate |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
||
ORDER BY can be used to sort nodes or relationships by their ID (retrieved by either the elementId() or id() functions).
MATCH (o:Order)
RETURN o.id AS order,
elementId(o) AS elementId
ORDER BY elementId
| order | elementId |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|
| Neo4j reuses its internal IDs when nodes and relationships are deleted. Applications relying on internal Neo4j IDs are, as a result, brittle and can be inaccurate. It is recommended to use application-generated IDs instead. |
ORDER BY can be used to sort according to the results of an expression.
The below query calculates a 10% discount on each order’s total property value, and then orders the results by the discounted total.
MATCH (o:Order)
RETURN o.id AS order,
o.total * 0.9 AS discountedTotal
ORDER BY discountedTotal
| order | discountedTotal |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|
This next query counts the number of items contained in each order and then orders the results by the item count.
MATCH (o:Order)
RETURN o.id AS order,
COUNT { (o)-[:CONTAINS]->(:Item) } AS itemCount
ORDER BY itemCount
| order | itemCount |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|
Order by values not in the result
ORDER BY can sort by values that are not included in the result set.
That is, the sort key does not need to be part of the preceding RETURN or WITH clause.
For example, the query below sorts orders based on how many items they contain, even though that count is not returned.
MATCH (o:Order)
RETURN o.id AS order
ORDER BY COUNT { (o)-[:CONTAINS]->(:Item) }
| order |
|---|
|
|
|
|
|
Rows: 5 |
Ascending and descending order
ORDER BY sorts results in an ascending order by default.
To explicitly sort results in an ascending order, append ASC[ENDING].
MATCH (o:Order)
RETURN o.id AS order,
o.total AS total
ORDER BY total ASC
| order | total |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|
To sort results in a descending order, append DESC[ENDING].
MATCH (o:Order)
RETURN o.id AS order,
o.total AS total
ORDER BY total DESC
| order | total |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|
ORDER BY can combine ascending and descending ordering.
In the example below, results are sorted first by total values in descending order and then by orderDate values in ascending order.
MATCH (o:Order)
RETURN o.id AS order,
o.total AS total,
o.orderDate AS orderDate
ORDER BY total DESC,
orderDate ASC
| order | total | orderDate |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
||
ORDER BY and pattern matching
ORDER BY can be used to sort results before continuing with additional pattern matching.
In the example below, it is combined with the LIMIT to first sort Order nodes by their orderDate property values, limit the result to the most recent Order, and then match any connected Item nodes.
Also note that ORDER BY and LIMIT are used as standalone clauses and not as subclauses in this example.
MATCH (o:Order)
ORDER BY o.orderDate DESC
LIMIT 1
MATCH (o)-[:CONTAINS]->(i:Item)
RETURN o.id AS order,
o.total,
collect(i.name) AS items
| order | total | items |
|---|---|---|
|
|
|
Rows: 1 |
||
Null values
When sorting, null values appear last in ascending order and first in descending order.
MATCH (o:Order)
RETURN o.id AS order,
o.status AS status
ORDER BY status DESC
| order | status |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|
ORDER BY and the WITH clause
When ORDER BY is present on a WITH clause, the immediately following clause will receive records in the specified order.
This guaranteed order is useful for operations that rely on the sequence in which values are processed.
For example, appending ORDER BY to a WITH clause can be used to control the order of items in the list produced by the collect() aggregating function.
The MERGE and SET clauses also have ordering dependencies which can be controlled this way.
The below example uses WITH and ORDER BY to sort Item nodes by their price property, then the collect() in the subsequent RETURN clause builds an ordered list per order based on that sort.
WITH, ORDER BY, and collect()MATCH (o:Order)-[:CONTAINS]->(i:Item)
WITH o, i
ORDER BY i.price DESC
RETURN o.id AS order,
collect(i.name || " ($" || toString(i.price) || ")") AS orderedListOfItems
| order | orderedListOfItems |
|---|---|
|
|
|
|
|
|
|
|
|
|
Rows: 5 |
|
Ordering aggregated or DISTINCT results
The variables available to ORDER BY depend on whether or not the preceding RETURN or WITH clause performs an aggregation to combine results or uses DISTINCT to remove duplicates.
-
If the
RETURNorWITHis not aggregating values or usingDISTINCT, thenORDER BYcan reference any variables referenced in the precedingRETURNorWITHclause.
ORDER BY following a WITH clause excluding aggregation or DISTINCTMATCH (o:Order)-[:CONTAINS]->(i:Item)
WITH o.id AS order,
i.name AS item
ORDER BY o.orderDate
RETURN order, item
-
If the
RETURNorWITHperforms an aggregation or usesDISTINCTonly the projected variables from either operation are available toORDER BY. This is because these operations alter the number of rows produced by the clause and any variables not explicitly projected are discarded.
ORDER BY following a WITH clause projecting an aggregated valueMATCH (o:Order)-[:CONTAINS]->(i:Item)
WITH collect(o.id) AS orders,
i.name AS items
ORDER BY o.orderDate
RETURN orders, items
In a WITH/RETURN with DISTINCT or an aggregation, it is not possible to access variables declared before the WITH/RETURN: o
ORDER BY and indexes
The performance of Cypher queries using ORDER BY on node properties can be influenced by the existence and use of an index for finding the nodes.
If the index can provide the nodes in the order requested in the query, Cypher can avoid the use of an expensive Sort operation.
Read more about this capability in Range index-backed ORDER BY.
Using ORDER BY as a standalone clauseIntroduced in 5.24
ORDER BY can be used as a standalone clause, or in conjunction with SKIP/OFFSET or LIMIT.
ORDER BYMATCH (i:Item)
ORDER BY i.price
RETURN collect(i.name || " ($" || toString(i.price) || ")") AS orderedPriceList
| orderedPriceList |
|---|
|
Rows: 1 |
ORDER BY used in conjunction with SKIP and LIMITMATCH (i:Item)
ORDER BY i.price DESC
SKIP 1
LIMIT 1
RETURN i.name AS secondMostExpensiveItem,
i.price AS price
| secondMostExpensiveItem | price |
|---|---|
|
|
Rows: 1 |
|