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.