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
RETURN
orWITH
is not aggregating values or usingDISTINCT
, thenORDER BY
can reference any variables referenced in the precedingRETURN
orWITH
clause.
ORDER BY
following a WITH
clause excluding aggregation or DISTINCT
MATCH (o:Order)-[:CONTAINS]->(i:Item)
WITH o.id AS order,
i.name AS item
ORDER BY o.orderDate
RETURN order, item
-
If the
RETURN
orWITH
performs an aggregation or usesDISTINCT
only 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 clause
ORDER BY
can be used as a standalone clause, or in conjunction with SKIP
/OFFSET
or LIMIT
.
ORDER BY
MATCH (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 LIMIT
MATCH (i:Item)
ORDER BY i.price DESC
SKIP 1
LIMIT 1
RETURN i.name AS secondMostExpensiveItem,
i.price AS price
secondMostExpensiveItem | price |
---|---|
|
|
Rows: 1 |