From 649a39c096b8afe3f970e16252029d6c4b74941c Mon Sep 17 00:00:00 2001
From: Matthias Schur <107557548+MattSchur@users.noreply.github.com>
Date: Thu, 2 Oct 2025 13:08:29 +0200
Subject: [PATCH 1/7] Java: Aggregation API
---
java/working-with-cql/query-api.md | 60 ++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/java/working-with-cql/query-api.md b/java/working-with-cql/query-api.md
index a4a1a410e..a4e0b0a32 100644
--- a/java/working-with-cql/query-api.md
+++ b/java/working-with-cql/query-api.md
@@ -621,6 +621,66 @@ Select.from("bookshop.Books")
b.get("title").startsWith("Wuth")));
```
+
+### Aggregating { #aggregating }
+
+Use the aggregation methods `min`, `max`, `sum`, and `count` to calculate minimums, maximums, totals, and counts directly in your CQL queries. You can use these aggregation methods in *columns* to include the aggregated values in the result set, or in the *where* clause to filter the result set based on aggregated values.
+
+::: tip
+Use [infix filters](/cds/cql#with-infix-filters) to aggregate only a subset of a collection.
+:::
+
+#### min
+
+Find the minimum value of an element in a collection.
+
+```java
+Select.from(ORDERS).columns(
+ o -> o.id(),
+ o -> o.items()
+ .filter(i -> i.amount().gt(0)) // optional filter
+ .min(i -> i.amount()).as("minAmount")
+);
+```
+
+This query selects each order’s id and the minimum item amount greater than 0 as "minAmount".
+
+#### max
+
+Find the maximum value of an element in a collection.
+
+```java
+Select.from(ORDERS)
+ .where(o -> o.items().max(i -> i.amount()).gt(100));
+```
+
+This query selects all orders where the maximum item amount is greater than 100.
+
+#### sum
+
+Calculate the total of a numeric element across related entities.
+
+```java
+Select.from(ORDERS).columns(
+ o -> o.id(),
+ o -> o.items().sum(i -> i.amount()).as("totalAmount")
+);
+```
+
+This query selects each order’s id and the sum of its item amounts as "totalAmount".
+
+#### count
+
+Count non-null values of an element in a collection.
+
+```java
+Select.from(ORDERS)
+ .where(o -> o.items().count(i -> i.discount()).gt(0));
+```
+
+This query selects all orders where at least one item has a discount.
+
+
### Grouping
The Query Builder API offers a way to group the results into summarized rows (in most cases these are aggregate functions) and apply certain criteria on it.
From 8fc8085f69b9a1065bf9208b4ef833758c548e84 Mon Sep 17 00:00:00 2001
From: Matthias Schur <107557548+MattSchur@users.noreply.github.com>
Date: Mon, 6 Oct 2025 12:19:29 +0200
Subject: [PATCH 2/7] Update java/working-with-cql/query-api.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Adrian Görler
---
java/working-with-cql/query-api.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/working-with-cql/query-api.md b/java/working-with-cql/query-api.md
index a4e0b0a32..793d6fcca 100644
--- a/java/working-with-cql/query-api.md
+++ b/java/working-with-cql/query-api.md
@@ -624,7 +624,7 @@ Select.from("bookshop.Books")
### Aggregating { #aggregating }
-Use the aggregation methods `min`, `max`, `sum`, and `count` to calculate minimums, maximums, totals, and counts directly in your CQL queries. You can use these aggregation methods in *columns* to include the aggregated values in the result set, or in the *where* clause to filter the result set based on aggregated values.
+Use the aggregation methods `min`, `max`, `sum`, and `count` to calculate minimums, maximums, totals, and counts of values of associated entities directly in your CQL queries. You can use these aggregation methods in *columns* to include the aggregated values in the result set, or in the *where* clause to filter the result set based on aggregated values.
::: tip
Use [infix filters](/cds/cql#with-infix-filters) to aggregate only a subset of a collection.
From d0e1e5a388638e981f36fe45051bc770122e68df Mon Sep 17 00:00:00 2001
From: Matthias Schur <107557548+MattSchur@users.noreply.github.com>
Date: Mon, 6 Oct 2025 14:48:12 +0200
Subject: [PATCH 3/7] Aggregating over Associations
---
java/working-with-cql/query-api.md | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/java/working-with-cql/query-api.md b/java/working-with-cql/query-api.md
index 793d6fcca..5b1786625 100644
--- a/java/working-with-cql/query-api.md
+++ b/java/working-with-cql/query-api.md
@@ -622,7 +622,9 @@ Select.from("bookshop.Books")
```
-### Aggregating { #aggregating }
+### Aggregating { #aggregating }
+
+#### Aggregating over Associations { #aggregating-associations }
Use the aggregation methods `min`, `max`, `sum`, and `count` to calculate minimums, maximums, totals, and counts of values of associated entities directly in your CQL queries. You can use these aggregation methods in *columns* to include the aggregated values in the result set, or in the *where* clause to filter the result set based on aggregated values.
@@ -681,7 +683,7 @@ Select.from(ORDERS)
This query selects all orders where at least one item has a discount.
-### Grouping
+#### Grouping
The Query Builder API offers a way to group the results into summarized rows (in most cases these are aggregate functions) and apply certain criteria on it.
@@ -695,7 +697,7 @@ Let's assume the following dataset for our examples:
|103 |Hugo |
|104 |Smith |
-#### Group By
+##### Group By
The `groupBy` clause groups by one or more elements and usually involves aggregate [functions](query-api#scalar-functions), such as `count`, `countDistinct`, `sum`, `max`, `avg`, and so on. It returns one row for each group.
@@ -718,7 +720,7 @@ If we execute the query on our dataset, we get the following result:
|Hugo |1 |
-#### Having
+##### Having
To filter the [grouped](#group-by) result, `having` is used. Both, `having` and `where`, filter the result before `group by` is applied and can be used in the same query.
From f1eb0fb80893c0adb03fa1ace5aaed58a2a4a349 Mon Sep 17 00:00:00 2001
From: Matthias Schur <107557548+MattSchur@users.noreply.github.com>
Date: Mon, 6 Oct 2025 15:02:00 +0200
Subject: [PATCH 4/7] Aggregation Functions
---
java/working-with-cql/query-api.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/java/working-with-cql/query-api.md b/java/working-with-cql/query-api.md
index 5b1786625..dc8732e64 100644
--- a/java/working-with-cql/query-api.md
+++ b/java/working-with-cql/query-api.md
@@ -624,6 +624,10 @@ Select.from("bookshop.Books")
### Aggregating { #aggregating }
+#### Aggregation Functions { #aggregation-functions }
+
+Use [aggregation functions](/guides/databases#aggregate-functions) to calculate minimums, maximums, totals, averages, and counts of values. You can use them in *columns* of `Select` statements to include the aggregated values in the result set, or in the *where* clause to filter based on aggregated values.
+
#### Aggregating over Associations { #aggregating-associations }
Use the aggregation methods `min`, `max`, `sum`, and `count` to calculate minimums, maximums, totals, and counts of values of associated entities directly in your CQL queries. You can use these aggregation methods in *columns* to include the aggregated values in the result set, or in the *where* clause to filter the result set based on aggregated values.
From f348cc42c0aaa9ce2b2ac0a7564228bb2e03db2c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrian=20G=C3=B6rler?=
Date: Tue, 7 Oct 2025 09:49:16 +0200
Subject: [PATCH 5/7] Apply suggestions from code review
Co-authored-by: Matthias Schur <107557548+MattSchur@users.noreply.github.com>
---
java/working-with-cql/query-api.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/java/working-with-cql/query-api.md b/java/working-with-cql/query-api.md
index dc8732e64..c21a14afa 100644
--- a/java/working-with-cql/query-api.md
+++ b/java/working-with-cql/query-api.md
@@ -633,7 +633,7 @@ Use [aggregation functions](/guides/databases#aggregate-functions) to calculate
Use the aggregation methods `min`, `max`, `sum`, and `count` to calculate minimums, maximums, totals, and counts of values of associated entities directly in your CQL queries. You can use these aggregation methods in *columns* to include the aggregated values in the result set, or in the *where* clause to filter the result set based on aggregated values.
::: tip
-Use [infix filters](/cds/cql#with-infix-filters) to aggregate only a subset of a collection.
+Use [infix filters](/cds/cql#with-infix-filters) to aggregate only a subset of a (to-many) association.
:::
#### min
@@ -669,11 +669,11 @@ Calculate the total of a numeric element across related entities.
```java
Select.from(ORDERS).columns(
o -> o.id(),
- o -> o.items().sum(i -> i.amount()).as("totalAmount")
+ o -> o.items().sum(i -> i.amount().times(i.price())).as("orderTotal")
);
```
-This query selects each order’s id and the sum of its item amounts as "totalAmount".
+This query selects each order's id and the total order amount (sum of amount times price) as "orderTotal".
#### count
From e9fa077da054988c363e84965d7cf5595a6e23fb Mon Sep 17 00:00:00 2001
From: Matthias Schur <107557548+MattSchur@users.noreply.github.com>
Date: Tue, 7 Oct 2025 12:22:22 +0200
Subject: [PATCH 6/7] headers
---
java/working-with-cql/query-api.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/java/working-with-cql/query-api.md b/java/working-with-cql/query-api.md
index c21a14afa..23353e9c5 100644
--- a/java/working-with-cql/query-api.md
+++ b/java/working-with-cql/query-api.md
@@ -636,7 +636,7 @@ Use the aggregation methods `min`, `max`, `sum`, and `count` to calculate minimu
Use [infix filters](/cds/cql#with-infix-filters) to aggregate only a subset of a (to-many) association.
:::
-#### min
+##### min
Find the minimum value of an element in a collection.
@@ -651,7 +651,7 @@ Select.from(ORDERS).columns(
This query selects each order’s id and the minimum item amount greater than 0 as "minAmount".
-#### max
+##### max
Find the maximum value of an element in a collection.
@@ -662,7 +662,7 @@ Select.from(ORDERS)
This query selects all orders where the maximum item amount is greater than 100.
-#### sum
+##### sum
Calculate the total of a numeric element across related entities.
@@ -675,7 +675,7 @@ Select.from(ORDERS).columns(
This query selects each order's id and the total order amount (sum of amount times price) as "orderTotal".
-#### count
+##### count
Count non-null values of an element in a collection.
From 102bd7d5dff4f6153a9ff7097d57d98a71453569 Mon Sep 17 00:00:00 2001
From: Matthias Schur <107557548+MattSchur@users.noreply.github.com>
Date: Tue, 7 Oct 2025 15:10:03 +0200
Subject: [PATCH 7/7] Update query-api.md
---
java/working-with-cql/query-api.md | 126 +++++++++++++++--------------
1 file changed, 67 insertions(+), 59 deletions(-)
diff --git a/java/working-with-cql/query-api.md b/java/working-with-cql/query-api.md
index 23353e9c5..d37f04d31 100644
--- a/java/working-with-cql/query-api.md
+++ b/java/working-with-cql/query-api.md
@@ -622,11 +622,76 @@ Select.from("bookshop.Books")
```
-### Aggregating { #aggregating }
+### Aggregating Data { #aggregating }
+
+You can aggregate data in two ways:
+
+- **Aggregate the current entity:** Use [aggregate functions](#aggregation-functions) like `sum` in the columns clause of your `Select` statement, usually together with [groupBy](#group-by), to summarize or group data.
+
+- **Aggregate associated entities:** Use dedicated aggregation methods to calculate values over to-many associations directly in your queries. See [Aggregating over Associations](#aggregating-associations).
+
#### Aggregation Functions { #aggregation-functions }
-Use [aggregation functions](/guides/databases#aggregate-functions) to calculate minimums, maximums, totals, averages, and counts of values. You can use them in *columns* of `Select` statements to include the aggregated values in the result set, or in the *where* clause to filter based on aggregated values.
+Use [aggregation functions](/guides/databases#aggregate-functions) to calculate minimums, maximums, totals, averages, and counts of values. You can use them in *columns* of `Select` statements to include the aggregated values in the result set, or in the [having](#having) clause to filter based on aggregated values.
+
+
+#### Grouping { #grouping }
+
+The Query Builder API offers a way to group the results into summarized rows (in most cases these are aggregate functions) and apply certain criteria on it.
+
+Let's assume the following dataset for our examples:
+
+|ID |NAME |
+|----|------|
+|100 |Smith |
+|101 |Miller|
+|102 |Smith |
+|103 |Hugo |
+|104 |Smith |
+
+##### Group By { #group-by }
+
+The `groupBy` clause groups by one or more elements and usually involves aggregate [functions](query-api#scalar-functions), such as `count`, `countDistinct`, `sum`, `max`, `avg`, and so on. It returns one row for each group.
+
+In the following example, we select the authors' name and, using the aggregate function `count`, determine how many authors with the same name exist in `bookshop.Authors`.
+
+```java
+import com.sap.cds.ql.CQL;
+
+Select.from("bookshop.Authors")
+ .columns(c -> c.get("name"), c -> CQL.count(c.get("name")).as("count"))
+ .groupBy(g -> g.get("name"));
+```
+
+If we execute the query on our dataset, we get the following result:
+
+|name |count|
+|------|-----|
+|Smith |3 |
+|Miller|1 |
+|Hugo |1 |
+
+
+##### Having { #having }
+
+To filter the [grouped](#group-by) result, `having` is used. Both, `having` and `where`, filter the result before `group by` is applied and can be used in the same query.
+
+The following example selects authors where count is higher than 2:
+
+```java
+Select.from("bookshop.Authors")
+ .columns(c -> c.get("name"), c -> func("count", c.get("name")).as("count"))
+ .groupBy(c -> c.get("name"))
+ .having(c -> func("count", c.get("name")).gt(2));
+```
+
+If we execute the query on our dataset, we get the following result:
+
+|name |count|
+|------|-----|
+|Smith |3 |
+
#### Aggregating over Associations { #aggregating-associations }
@@ -687,63 +752,6 @@ Select.from(ORDERS)
This query selects all orders where at least one item has a discount.
-#### Grouping
-
-The Query Builder API offers a way to group the results into summarized rows (in most cases these are aggregate functions) and apply certain criteria on it.
-
-Let's assume the following dataset for our examples:
-
-|ID |NAME |
-|----|------|
-|100 |Smith |
-|101 |Miller|
-|102 |Smith |
-|103 |Hugo |
-|104 |Smith |
-
-##### Group By
-
-The `groupBy` clause groups by one or more elements and usually involves aggregate [functions](query-api#scalar-functions), such as `count`, `countDistinct`, `sum`, `max`, `avg`, and so on. It returns one row for each group.
-
-In the following example, we select the authors' name and, using the aggregate function `count`, determine how many authors with the same name exist in `bookshop.Authors`.
-
-```java
-import com.sap.cds.ql.CQL;
-
-Select.from("bookshop.Authors")
- .columns(c -> c.get("name"), c -> CQL.count(c.get("name")).as("count"))
- .groupBy(g -> g.get("name"));
-```
-
-If we execute the query on our dataset, we get the following result:
-
-|name |count|
-|------|-----|
-|Smith |3 |
-|Miller|1 |
-|Hugo |1 |
-
-
-##### Having
-
-To filter the [grouped](#group-by) result, `having` is used. Both, `having` and `where`, filter the result before `group by` is applied and can be used in the same query.
-
-The following example selects authors where count is higher than 2:
-
-```java
-Select.from("bookshop.Authors")
- .columns(c -> c.get("name"), c -> func("count", c.get("name")).as("count"))
- .groupBy(c -> c.get("name"))
- .having(c -> func("count", c.get("name")).gt(2));
-```
-
-If we execute the query on our dataset, we get the following result:
-
-|name |count|
-|------|-----|
-|Smith |3 |
-
-
### Ordering and Pagination
The Query Builder API allows to specify the sort order of query results. The _sort specification_ governs, according to which elements the result is sorted, and which sort order (ascending or descending) is applied.