From 9e0aa86be940643cf91a13330ec7fea42b72b5c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Thu, 8 May 2025 11:16:49 +0200 Subject: [PATCH 01/10] trial and errors --- modules/ROOT/pages/clauses/order-by.adoc | 91 +++++++++++++----------- 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/modules/ROOT/pages/clauses/order-by.adoc b/modules/ROOT/pages/clauses/order-by.adoc index 913580b34..9eeea7470 100644 --- a/modules/ROOT/pages/clauses/order-by.adoc +++ b/modules/ROOT/pages/clauses/order-by.adoc @@ -1,14 +1,15 @@ -:description: `ORDER BY` is a sub-clause following `RETURN` or `WITH`, and it specifies that the output should be sorted and how. +:description: Information about Cypher's `ORDER BY` subclause. [[query-order]] = ORDER BY -`ORDER BY` specifies how the output of a clause should be sorted. -It be used as a sub-clause following `RETURN` or `WITH`. +`ORDER BY` is a subclause which specifies the order in which the output of a xref:clauses/return.adoc[`RETURN`] or xref:clauses/with.adoc[`WITH`] clause. 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` relies on comparisons to sort the output, see xref:values-and-types/ordering-equality-comparison.adoc[Ordering and comparison of values]. -You can sort on many different values, e.g. node/relationship properties, the node/relationship ids, or on most expressions. +`ORDER BY` by default sorts results in an ascending order, though it can be modified to return results in a descending order. + +`ORDER BY` relies on comparisons to sort the output (see xref:values-and-types/ordering-equality-comparison.adoc[] for more details). +You can sort on different values, such as node or relationship properties, IDs, or the result of expressions. [NOTE] ==== @@ -20,41 +21,47 @@ Unless `ORDER BY` is used, Neo4j does not guarantee the row order of a query res The following graph is used for the examples below: -image::graph_order_by_clause.svg[width="600", role="middle"] +image::list_expressions_graph.svg[width="600", role="middle"] To recreate it, run the following query against an empty Neo4j database: [source, cypher, role=test-setup] ---- -CREATE - (andy: Person {name: 'Andy', age: 34, length: 170}), - (bernard: Person {name: 'Bernard', age: 36}), - (charlotte: Person {name: 'Charlotte', age: 32, length: 185}), - (andy)-[:KNOWS]->(bernard), - (bernard)-[:KNOWS]->(charlotte) +CREATE (alice:Person {name:'Alice', age: 65, role: 'Project manager', skills: ['Java', 'Python']}), + (cecil:Person {name: 'Cecil', age: 25, role: 'Software developer', skills: ['Java', 'Python']}), + (cecilia:Person {name: 'Cecilia', age: 31, role: 'Software developer', skills: ['JavaScript', 'TypeScript']}), + (charlie:Person {name: 'Charlie', age: 61, role: 'Security engineer', skills: ['C++', 'Python']}), + (daniel:Person {name: 'Daniel', age: 39, role: 'Director', skills: ['Ruby', 'Go']}), + (eskil:Person {name: 'Eskil', age: 39, role: 'CEO', skills: ['Java', 'C++', 'Python']}), + + (cecil)-[:WORKS_FOR]->(alice), + (cecilia)-[:WORKS_FOR]->(alice), + (charlie)-[:WORKS_FOR]->(daniel), + (alice)-[:WORKS_FOR]->(daniel), + (daniel)-[:WORKS_FOR]->(eskil) ---- -[[order-nodes-by-property]] -== Order nodes by property +[[order-by-property]] +== Order by property values -`ORDER BY` is used to sort the output. +`ORDER BY` can be used to sort property values. -.Query +.Order by node property // tag::clauses_order_by[] [source, cypher] ---- -MATCH (n) -RETURN n.name, n.age -ORDER BY n.name +MATCH (p:Person) +RETURN p.name, p.age +ORDER BY p.name ---- // end::clauses_order_by[] -The nodes are returned, sorted by their name. +The nodes are returned, sorted by their name in an ascending order. .Result [role="queryresult",options="header,footer",cols="2* +| "Bernard" | 36 | null | "Andy" | 34 | 170 | "Charlotte" | 32 | 185 3+d|Rows: 3 From e57961a9eb672b4f4960758651e6f05e74103d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Thu, 8 May 2025 14:33:00 +0200 Subject: [PATCH 02/10] new dataset --- modules/ROOT/pages/clauses/order-by.adoc | 166 +++++++++++++++-------- 1 file changed, 110 insertions(+), 56 deletions(-) diff --git a/modules/ROOT/pages/clauses/order-by.adoc b/modules/ROOT/pages/clauses/order-by.adoc index 9eeea7470..1b975092a 100644 --- a/modules/ROOT/pages/clauses/order-by.adoc +++ b/modules/ROOT/pages/clauses/order-by.adoc @@ -27,74 +27,94 @@ To recreate it, run the following query against an empty Neo4j database: [source, cypher, role=test-setup] ---- -CREATE (alice:Person {name:'Alice', age: 65, role: 'Project manager', skills: ['Java', 'Python']}), - (cecil:Person {name: 'Cecil', age: 25, role: 'Software developer', skills: ['Java', 'Python']}), - (cecilia:Person {name: 'Cecilia', age: 31, role: 'Software developer', skills: ['JavaScript', 'TypeScript']}), - (charlie:Person {name: 'Charlie', age: 61, role: 'Security engineer', skills: ['C++', 'Python']}), - (daniel:Person {name: 'Daniel', age: 39, role: 'Director', skills: ['Ruby', 'Go']}), - (eskil:Person {name: 'Eskil', age: 39, role: 'CEO', skills: ['Java', 'C++', 'Python']}), - - (cecil)-[:WORKS_FOR]->(alice), - (cecilia)-[:WORKS_FOR]->(alice), - (charlie)-[:WORKS_FOR]->(daniel), - (alice)-[:WORKS_FOR]->(daniel), - (daniel)-[:WORKS_FOR]->(eskil) +CREATE + (o1:Order {id: 'ORD-001', createdAt: datetime('2024-05-01T10:00:00'), total: 200, status: 'shipped'}), + (o2:Order {id: 'ORD-002', createdAt: datetime('2024-05-02T14:30:00'), total: 300, status: 'pending'}), + (o3:Order {id: 'ORD-003', createdAt: datetime('2024-05-03T09:15:00'), total: 200, status: 'pending'}), + (o4:Order {id: 'ORD-004', createdAt: datetime('2024-05-04T12:45:00'), total: 150}), + (o5:Order {id: 'ORD-005', createdAt: datetime('2024-05-02T11:20:00'), total: 300, status: 'shipped'}), + + (i1:Item {name: 'Phone'}), + (i2:Item {name: 'Laptop'}), + (i3:Item {name: 'Headphones'}), + (i4:Item {name: 'Charger'}), + (i5:Item {name: 'Keyboard'}), + + (o1)-[:CONTAINS]->(i1), + (o1)-[:CONTAINS]->(i4), + (o2)-[:CONTAINS]->(i2), + (o3)-[:CONTAINS]->(i3), + (o4)-[:CONTAINS]->(i5), + (o5)-[:CONTAINS]->(i2), + (o5)-[:CONTAINS]->(i3) ---- + [[order-by-property]] == Order by property values -`ORDER BY` can be used to sort property values. +`ORDER BY` can be used to sort the result by property values. .Order by node property // tag::clauses_order_by[] [source, cypher] ---- -MATCH (p:Person) -RETURN p.name, p.age -ORDER BY p.name +MATCH (o:Order) +RETURN o.id AS order, + o.total AS total + ORDER BY total ---- // end::clauses_order_by[] -The nodes are returned, sorted by their name in an ascending order. +The nodes are returned, sorted by the value of the `total` properties in an ascending order. .Result [role="queryresult",options="header,footer",cols="2*(:Item) } AS itemCount + ORDER BY itemCount +---- + + .Result -[role="queryresult",options="header,footer",cols="3* Date: Fri, 9 May 2025 16:14:03 +0200 Subject: [PATCH 03/10] image left --- .../ROOT/pages/clauses/listing-functions.adoc | 2 +- modules/ROOT/pages/clauses/order-by.adoc | 318 +++++++++++------- 2 files changed, 205 insertions(+), 115 deletions(-) diff --git a/modules/ROOT/pages/clauses/listing-functions.adoc b/modules/ROOT/pages/clauses/listing-functions.adoc index e8e391eb9..1cffdf59c 100644 --- a/modules/ROOT/pages/clauses/listing-functions.adoc +++ b/modules/ROOT/pages/clauses/listing-functions.adoc @@ -139,7 +139,7 @@ If all columns are required, use `SHOW FUNCTIONS YIELD *`. .Query -[source, cypher, role=test-result-skip] +[source, cypher, sult-skip] ---- SHOW FUNCTIONS ---- diff --git a/modules/ROOT/pages/clauses/order-by.adoc b/modules/ROOT/pages/clauses/order-by.adoc index 1b975092a..fbd9fbfe8 100644 --- a/modules/ROOT/pages/clauses/order-by.adoc +++ b/modules/ROOT/pages/clauses/order-by.adoc @@ -6,12 +6,12 @@ `ORDER BY` is a subclause which specifies the order in which the output of a xref:clauses/return.adoc[`RETURN`] or xref:clauses/with.adoc[`WITH`] clause. 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` by default sorts results in an ascending order, though it can be modified to return results in a descending order. +`ORDER BY` by default sorts results in an ascending order, though it can be modified to return results in a xref:clauses/order-by.adoc#ascending-descending-order[descending order]. `ORDER BY` relies on comparisons to sort the output (see xref:values-and-types/ordering-equality-comparison.adoc[] for more details). You can sort on different values, such as node or relationship properties, IDs, or the result of expressions. -[NOTE] +[IMPORTANT] ==== Unless `ORDER BY` is used, Neo4j does not guarantee the row order of a query result. ==== @@ -27,26 +27,27 @@ To recreate it, run the following query against an empty Neo4j database: [source, cypher, role=test-setup] ---- -CREATE - (o1:Order {id: 'ORD-001', createdAt: datetime('2024-05-01T10:00:00'), total: 200, status: 'shipped'}), - (o2:Order {id: 'ORD-002', createdAt: datetime('2024-05-02T14:30:00'), total: 300, status: 'pending'}), - (o3:Order {id: 'ORD-003', createdAt: datetime('2024-05-03T09:15:00'), total: 200, status: 'pending'}), - (o4:Order {id: 'ORD-004', createdAt: datetime('2024-05-04T12:45:00'), total: 150}), - (o5:Order {id: 'ORD-005', createdAt: datetime('2024-05-02T11:20:00'), total: 300, status: 'shipped'}), - - (i1:Item {name: 'Phone'}), - (i2:Item {name: 'Laptop'}), - (i3:Item {name: 'Headphones'}), - (i4:Item {name: 'Charger'}), - (i5:Item {name: 'Keyboard'}), - - (o1)-[:CONTAINS]->(i1), - (o1)-[:CONTAINS]->(i4), - (o2)-[:CONTAINS]->(i2), - (o3)-[:CONTAINS]->(i3), - (o4)-[:CONTAINS]->(i5), - (o5)-[:CONTAINS]->(i2), - (o5)-[:CONTAINS]->(i3) +CREATE (o1:Order {id: 'ORD-001', orderDate: datetime('2024-05-01T10:00:00'), total: 750, 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: 750, status: 'pending'}), + (o4:Order {id: 'ORD-004', orderDate: datetime('2024-05-04T12:45:00'), total: 500}), + (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) ---- @@ -73,16 +74,15 @@ The nodes are returned, sorted by the value of the `total` properties in an asce |=== | order | total -| "ORD-004" | 150 -| "ORD-003" | 200 -| "ORD-001" | 200 -| "ORD-002" | 300 -| "ORD-005" | 300 +| "ORD-004" | 500 +| "ORD-001" | 750 +| "ORD-003" | 750 +| "ORD-005" | 800 +| "ORD-002" | 1000 -5+d|Rows: 2 +2+d|Rows: 5 |=== - [[order-by-multiple-properties]] == Order by multiple property values @@ -96,25 +96,25 @@ Cypher sorts by the first property, and if values are equal, it moves to the nex MATCH (o:Order) RETURN o.id AS order, o.total AS total, - o.createdAt AS createdAt - ORDER BY total, createdAt + o.orderDate AS orderDate + ORDER BY total, orderDate ---- // end::clauses_order_by_multiple[] -This returns the nodes, sorted first by their `total` property, and then, for equal values, by their `createdAt` property. +This returns the nodes, sorted first by their `total` property, and then, for equal values, by their `orderDate` property. .Result [role="queryresult",options="header,footer",cols="3*(:Item) } AS itemCount - ORDER BY itemCount + ORDER BY itemCount ---- - - .Result [role="queryresult",options="header,footer",cols="2*(i:Item) +RETURN o.id AS order, + o.total, + collect(i.name) AS items ---- -The nodes are returned sorted by the length property, with a node without that property last. - .Result [role="queryresult",options="header,footer",cols="3* | "Bernard" | 36 -3+d|Rows: 3 +| order | total | items + +| "ORD-005" | 800 | ["Phone", "Headphones", "Charger"] + +3+d|Rows: 1 +|=== + +[[null]] +== Ordering `null` + +When sorting, `null` values appear last in ascending order and first in descending order. + +.Sort on null property +[source, cypher] +---- +MATCH (o:Order) +RETURN o.id AS order, + o.status AS status + ORDER BY status DESC +---- + +.Result +[role="queryresult",options="header,footer",cols="2*(i:Item) +WITH o, i + ORDER BY i.price DESC +RETURN o.id AS orderId, + collect(i.name || "($" || toString(i.price) || ")") AS orderedListOfItems ---- -The list of names built from the `collect` aggregating function contains the names in order of the `age` property. .Result -[role="queryresult",options="header,footer",cols="1* Date: Mon, 12 May 2025 13:54:08 +0200 Subject: [PATCH 04/10] image and more example --- modules/ROOT/images/graph_order_by_clause.svg | 2 +- modules/ROOT/pages/clauses/order-by.adoc | 112 +++++++++--------- 2 files changed, 59 insertions(+), 55 deletions(-) diff --git a/modules/ROOT/images/graph_order_by_clause.svg b/modules/ROOT/images/graph_order_by_clause.svg index a42046fcc..a571834d7 100644 --- a/modules/ROOT/images/graph_order_by_clause.svg +++ b/modules/ROOT/images/graph_order_by_clause.svg @@ -1 +1 @@ -KNOWSKNOWSPersonname:'Charlotte'age:32length:185Personname:'Bernard'age:36Personname:'Andy'age:34length:170 \ No newline at end of file +CONTAINSOrderid:STRINGordeDate:DATEtotal:INTEGERstatus:STRINGItemname:STRINGprice:INTEGER \ No newline at end of file diff --git a/modules/ROOT/pages/clauses/order-by.adoc b/modules/ROOT/pages/clauses/order-by.adoc index fbd9fbfe8..a73ac2575 100644 --- a/modules/ROOT/pages/clauses/order-by.adoc +++ b/modules/ROOT/pages/clauses/order-by.adoc @@ -6,31 +6,30 @@ `ORDER BY` is a subclause which specifies the order in which the output of a xref:clauses/return.adoc[`RETURN`] or xref:clauses/with.adoc[`WITH`] clause. 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` by default sorts results in an ascending order, though it can be modified to return results in a xref:clauses/order-by.adoc#ascending-descending-order[descending order]. +`ORDER BY` defaults to sorting results in an ascending order, though it can be modified to sort results in a xref:clauses/order-by.adoc#ascending-descending-order[descending order]. `ORDER BY` relies on comparisons to sort the output (see xref:values-and-types/ordering-equality-comparison.adoc[] for more details). You can sort on different values, such as node or relationship properties, IDs, or the result of expressions. [IMPORTANT] -==== Unless `ORDER BY` is used, Neo4j does not guarantee the row order of a query result. -==== + [[example-graph]] == Example graph -The following graph is used for the examples below: +A graph with the following schema is used for the examples below: -image::list_expressions_graph.svg[width="600", role="middle"] +image::graph_order_by_clause.svg[width="400", role="middle"] To recreate it, run the following query against an empty Neo4j database: [source, cypher, role=test-setup] ---- -CREATE (o1:Order {id: 'ORD-001', orderDate: datetime('2024-05-01T10:00:00'), total: 750, status: 'shipped'}), +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: 750, status: 'pending'}), - (o4:Order {id: 'ORD-004', orderDate: datetime('2024-05-04T12:45:00'), total: 500}), + (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}), @@ -74,11 +73,11 @@ The nodes are returned, sorted by the value of the `total` properties in an asce |=== | order | total -| "ORD-004" | 500 -| "ORD-001" | 750 -| "ORD-003" | 750 +| "ORD-004" | 200 +| "ORD-001" | 550 +| "ORD-003" | 550 | "ORD-005" | 800 -| "ORD-002" | 1000 +| "ORD-002" | 1000 2+d|Rows: 5 |=== @@ -108,9 +107,9 @@ This returns the nodes, sorted first by their `total` property, and then, for eq |=== | order | total | orderDate -| "ORD-004" | 500 | 2024-05-04T12:45Z -| "ORD-001" | 750 | 2024-05-01T10:00Z -| "ORD-003" | 750 | 2024-05-03T09:15Z +| "ORD-004" | 200 | 2024-05-04T12:45Z +| "ORD-001" | 550 | 2024-05-01T10:00Z +| "ORD-003" | 550 | 2024-05-03T09:15Z | "ORD-005" | 800 | 2024-05-05T15:00Z | "ORD-002" | 1000 | 2024-05-02T14:30Z @@ -132,8 +131,6 @@ RETURN o.id AS order, ORDER BY elementId ---- -The nodes are returned, sorted by their internal IDs. - .Result [role="queryresult",options="header,footer",cols="2*(i:Item) +WITH o.id AS order, + i.name AS item + ORDER BY o.orderDate +RETURN order, item ---- -== Ordering aggregated or DISTINCT results +* If the `RETURN` or `WITH` performs an aggregation or uses `DISTINCT` only the projected variables from either operation are available to `ORDER BY`. +This is because these operations alter the number of rows produced by the clause and any variables not explicitly projected are discarded. -In terms of scope of variables, `ORDER BY` follows special rules, depending on if the projecting `RETURN` or `WITH` clause is either aggregating or `DISTINCT`. -If it is an aggregating or `DISTINCT` projection, only the variables available in the projection are available. -If the projection does not alter the output cardinality (which aggregation and `DISTINCT` do), variables available from before the projecting clause are also available. -When the projection clause shadows already existing variables, only the new variables are available. +.`ORDER BY` following a `WITH` clause projecting an aggregated value +[source, cypher, role=test-fail] +---- +MATCH (o:Order)-[:CONTAINS]->(i:Item) +WITH collect(o.id) AS orders, + i.name AS items + ORDER BY o.orderDate +RETURN orders, items +---- -It is also not allowed to use aggregating expressions in the `ORDER BY` subclause if they are not also listed in the projecting clause. -This rule is to make sure that `ORDER BY` does not change the results, only the order of them. +.Error message +[source, error] +---- +In a WITH/RETURN with DISTINCT or an aggregation, it is not possible to access variables declared before the WITH/RETURN: o +---- +[[indexes]] == 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. From d46331fcadcafec6132b807cfd221c66db1b4379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Mon, 12 May 2025 14:07:52 +0200 Subject: [PATCH 05/10] how did that get in there --- modules/ROOT/pages/clauses/listing-functions.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/clauses/listing-functions.adoc b/modules/ROOT/pages/clauses/listing-functions.adoc index 1cffdf59c..e8e391eb9 100644 --- a/modules/ROOT/pages/clauses/listing-functions.adoc +++ b/modules/ROOT/pages/clauses/listing-functions.adoc @@ -139,7 +139,7 @@ If all columns are required, use `SHOW FUNCTIONS YIELD *`. .Query -[source, cypher, sult-skip] +[source, cypher, role=test-result-skip] ---- SHOW FUNCTIONS ---- From 35f2931ca74b905620a66e6bd0302ce094831841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Mon, 19 May 2025 13:21:23 +0200 Subject: [PATCH 06/10] post review corrections --- modules/ROOT/pages/clauses/order-by.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ROOT/pages/clauses/order-by.adoc b/modules/ROOT/pages/clauses/order-by.adoc index a73ac2575..117a75239 100644 --- a/modules/ROOT/pages/clauses/order-by.adoc +++ b/modules/ROOT/pages/clauses/order-by.adoc @@ -3,7 +3,7 @@ [[query-order]] = ORDER BY -`ORDER BY` is a subclause which specifies the order in which the output of a xref:clauses/return.adoc[`RETURN`] or xref:clauses/with.adoc[`WITH`] clause. +`ORDER BY` is a subclause that determines how the results of a xref:clauses/return.adoc[`RETURN`] or xref:clauses/with.adoc[`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 xref:clauses/order-by.adoc#ascending-descending-order[descending order]. @@ -355,7 +355,7 @@ RETURN o.id AS orderId, [[aggregation-distinct]] == Ordering aggregated or DISTINCT results -The variables available to `ORDER BY` depends on whether or not the preceding `RETURN` or `WITH` clause performs an aggregation to combine results or uses `DISTINCT` to remove duplicates. +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` or `WITH` is not aggregating values or using `DISTINCT`, then `ORDER BY` can reference any variables referenced in the preceding `RETURN` or `WITH` clause. From cba286342e5cd1516268222529500bf8050d1e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 20 May 2025 15:23:12 +0200 Subject: [PATCH 07/10] update --- modules/ROOT/pages/clauses/order-by.adoc | 66 +++++++++++++++++++----- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/modules/ROOT/pages/clauses/order-by.adoc b/modules/ROOT/pages/clauses/order-by.adoc index 117a75239..7b606deed 100644 --- a/modules/ROOT/pages/clauses/order-by.adoc +++ b/modules/ROOT/pages/clauses/order-by.adoc @@ -1,7 +1,6 @@ -:description: Information about Cypher's `ORDER BY` subclause. - -[[query-order]] = ORDER BY +:description: Information about Cypher's `ORDER BY` subclause. +:table-caption!: `ORDER BY` is a subclause that determines how the results of a xref:clauses/return.adoc[`RETURN`] or xref:clauses/with.adoc[`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`. @@ -51,7 +50,10 @@ CREATE (o1:Order {id: 'ORD-001', orderDate: datetime('2024-05-01T10:00:00'), tot [[order-by-property]] -== Order by property values +== Basic examples + +.Order by property values +===== `ORDER BY` can be used to sort the result by property values. @@ -82,8 +84,10 @@ The nodes are returned, sorted by the value of the `total` properties in an asce 2+d|Rows: 5 |=== -[[order-by-multiple-properties]] -== Order by multiple property values +===== + +.Order by multiple property values +===== 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. @@ -116,9 +120,10 @@ This returns the nodes, sorted first by their `total` property, and then, for eq 3+d|Rows: 5 |=== +===== -[[order-by-id]] -== Order by ID +.Order by ID +===== `ORDER BY` can be used to sort nodes or relationships by their ID (retrieved by either the xref:functions/scalar.adoc#functions-elementid[`elementId()`] or xref:functions/scalar.adoc#functions-id[`id()`] functions). @@ -150,8 +155,10 @@ 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-expression]] -== Order by expressions +===== + +.Order by expressions +===== `ORDER BY` can be used to sort according to the results of an xref:expressions/index.adoc[expression]. The below query calculates a 10% discount on each order's `total` property value, and then orders the results by the discounted total. @@ -179,7 +186,7 @@ RETURN o.id AS order, 2+d|Rows: 5 |=== -This next example xref:subqueries/count.adoc[counts] the number of items contained in each order and then orders the results by the item count. +This next query xref:subqueries/count.adoc[counts] the number of items contained in each order and then orders the results by the item count. .Order by an expression result [source, cypher] @@ -204,6 +211,36 @@ RETURN o.id AS order, 2+d|Rows: 5 |=== +===== + +[[order-by-values-not-in-result]] +== 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. + +.Order by values not in the returned results +[source, cypher] +---- +MATCH (o:Order) +RETURN o.id AS order + ORDER BY COUNT { (o)-[:CONTAINS]->(:Item) } +---- + +.Result +[role="queryresult",options="header,footer",cols="1*(i:Item) RETURN o.id AS order, o.total, @@ -321,7 +359,7 @@ RETURN o.id AS order, == 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. -The ordering guarantee can be useful to exploit by operations which depend on the order in which they consume values. +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 xref:functions/aggregating.adoc#functions-collect[`collect()`] aggregating function. The xref:clauses/merge.adoc[`MERGE`] and xref:clauses/set.adoc[`SET`] clauses also have ordering dependencies which can be controlled this way. From 0a0c1fc46f5c9ac3ac627db230acfa29f7860f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Tue, 20 May 2025 15:28:13 +0200 Subject: [PATCH 08/10] wrong anchor --- modules/ROOT/pages/clauses/order-by.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/clauses/order-by.adoc b/modules/ROOT/pages/clauses/order-by.adoc index 7b606deed..5f76c79a6 100644 --- a/modules/ROOT/pages/clauses/order-by.adoc +++ b/modules/ROOT/pages/clauses/order-by.adoc @@ -49,7 +49,7 @@ CREATE (o1:Order {id: 'ORD-001', orderDate: datetime('2024-05-01T10:00:00'), tot ---- -[[order-by-property]] +[[basic-examples]] == Basic examples .Order by property values From 4c10e55b9a909158fb1d4132a9847b7585fd6afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20Pryce-=C3=85klundh?= <112686610+JPryce-Aklundh@users.noreply.github.com> Date: Wed, 21 May 2025 09:57:24 +0200 Subject: [PATCH 09/10] add example of desc and asc combined ordering --- modules/ROOT/pages/clauses/order-by.adoc | 52 ++++++++++++++++++------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/modules/ROOT/pages/clauses/order-by.adoc b/modules/ROOT/pages/clauses/order-by.adoc index 5f76c79a6..3d14fc6a7 100644 --- a/modules/ROOT/pages/clauses/order-by.adoc +++ b/modules/ROOT/pages/clauses/order-by.adoc @@ -100,7 +100,8 @@ MATCH (o:Order) RETURN o.id AS order, o.total AS total, o.orderDate AS orderDate - ORDER BY total, orderDate + ORDER BY total, + orderDate ---- // end::clauses_order_by_multiple[] @@ -296,6 +297,34 @@ RETURN o.id AS order, 2+d|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. + +.Combine ascending and descending result ordering +[source, cypher] +---- +MATCH (o:Order) +RETURN o.id AS order, + o.total AS total, + o.orderDate AS orderDate + ORDER BY total DESC, + orderDate ASC +---- + +.Result +[role="queryresult",options="header,footer",cols="3*(i:Item) WITH o, i ORDER BY i.price DESC -RETURN o.id AS orderId, - collect(i.name || "($" || toString(i.price) || ")") AS orderedListOfItems +RETURN o.id AS order, + collect(i.name || " ($" || toString(i.price) || ")") AS orderedListOfItems ---- - .Result [role="queryresult",options="header,footer",cols="2* Date: Wed, 21 May 2025 11:40:11 +0200 Subject: [PATCH 10/10] Update modules/ROOT/pages/clauses/order-by.adoc --- modules/ROOT/pages/clauses/order-by.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ROOT/pages/clauses/order-by.adoc b/modules/ROOT/pages/clauses/order-by.adoc index 3d14fc6a7..06287833d 100644 --- a/modules/ROOT/pages/clauses/order-by.adoc +++ b/modules/ROOT/pages/clauses/order-by.adoc @@ -330,7 +330,7 @@ RETURN o.id AS order, == 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 xref:clauses/limit.adoc[`LIMIT`] subclause 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. +In the example below, it is combined with the xref:clauses/limit.adoc[`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 xref:clauses/order-by.adoc#order-standalone-clause[standalone clauses] and not as subclauses in this example. .Find the items contained in the most recently placed order