From 197e230320f3345deeb5111c16132e2d78056bec Mon Sep 17 00:00:00 2001 From: Fabio Massimo Ercoli Date: Fri, 26 Jul 2024 12:38:43 +0200 Subject: [PATCH] HSEARCH-5133 Document metric aggregations --- .../reference/_search-dsl-aggregation.adoc | 111 ++++++++++++++++++ .../search/aggregation/AggregationDslIT.java | 90 ++++++++++++++ 2 files changed, 201 insertions(+) diff --git a/documentation/src/main/asciidoc/public/reference/_search-dsl-aggregation.adoc b/documentation/src/main/asciidoc/public/reference/_search-dsl-aggregation.adoc index 13e63fc886c..36916db7e73 100644 --- a/documentation/src/main/asciidoc/public/reference/_search-dsl-aggregation.adoc +++ b/documentation/src/main/asciidoc/public/reference/_search-dsl-aggregation.adoc @@ -324,6 +324,117 @@ See <> for more information. * For fields in nested objects, all nested objects are considered by default, but that can be <>. +[[search-dsl-aggregation-metric]] +== Metric aggregations + +include::../components/_incubating-warning.adoc[] + +Hibernate Search provides a set of most common metric aggregations such as `sum`, `min`, `max`, `count`, +`count distinct` and `avg`. These aggregations can be requested for fields of numerical or temporal type. + +[NOTE] +==== +At the moment, it is only possible to request metric aggregations at the root level; +that is, the aggregation will be applied to the entire hit set. +==== + +[IMPORTANT] +==== +The fields that are targeted by the aggregation function must be declared <>. +==== + +=== Sum metric aggregation + +The `sum` aggregation sums up the field values. + +.Sum the prices all of the science fiction books +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java[tags=sums] +---- +<1> Define the target field path to which you want to apply the aggregation function and the expected returned type. +==== + +[NOTE] +==== +You can always use the field type for the expected returned type. +Another option is to set `Double.class` for the expected returned type and you can always do it +even if the field is not double typed. +==== + +=== Min metric aggregation + +The `min` aggregation provides the minimum value among the field values. + +.Find the min release date among all the science fiction books +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java[tags=min] +---- +<1> Define the target field path to which you want to apply the aggregation function and the expected returned type. +==== + +=== Max metric aggregation + +The `max` aggregation provides the maximum value among the field values. + +.Find the max release date among all the science fiction books +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java[tags=max] +---- +<1> Define the target field path to which you want to apply the aggregation function and the expected returned type. +==== + +=== Count metric aggregation + +The `count` aggregation counts the field values. + +.Count anytime the price field has a value among all the science fiction books +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java[tags=count] +---- +<1> Define the target field path to which you want to apply the aggregation function. For this function a `Long.class` value is always returned. +==== + +=== Count distinct metric aggregation + +The `count distinct` aggregation counts the different field values. + +.Count anytime the price field has a different value among all the science fiction books +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java[tags=count-distinct] +---- +<1> Define the target field path to which you want to apply the aggregation function. For this function a `Long.class` value is always returned. +==== + +=== Avg metric aggregation + +Applies the SQL `avg` aggregation function on a given numeric or temporal field. + +.Compute the average of the prices all of the science fiction books +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java[tags=avg] +---- +<1> Define the target field path to which you want to apply the aggregation function and the expected returned type. +==== + +[NOTE] +==== +In the case of the `avg()` aggregation, the result may have some decimal values, +even if the field type is represented by one of the integer number types. +In this case, if you want to return decimals, provide `Double.class` for the expected returned type. +==== + [[search-dsl-aggregation-withparameters]] == `withParameters`: create aggregations using query parameters diff --git a/documentation/src/test/java/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java b/documentation/src/test/java/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java index 8279e3c38b8..fb1e2ef83a1 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java @@ -437,6 +437,96 @@ void withParameters() { } ); } + @Test + void sum() { + withinSearchSession( searchSession -> { + // tag::sums[] + AggregationKey sumPricesKey = AggregationKey.of( "sumPricesScienceFictionBooks" ); + SearchResult result = searchSession.search( Book.class ) + .where( f -> f.match().field( "genre" ).matching( Genre.SCIENCE_FICTION ) ) + .aggregation( sumPricesKey, f -> f.sum().field( "price", Double.class ) ) // <1> + .fetch( 20 ); + Double sumPrices = result.aggregation( sumPricesKey ); + assertThat( sumPrices ).isEqualTo( 60.97 ); + // end::sums[] + } ); + } + + @Test + void min() { + withinSearchSession( searchSession -> { + // tag::min[] + AggregationKey oldestReleaseKey = AggregationKey.of( "oldestRelease" ); + SearchResult result = searchSession.search( Book.class ) + .where( f -> f.match().field( "genre" ).matching( Genre.SCIENCE_FICTION ) ) + .aggregation( oldestReleaseKey, f -> f.min().field( "releaseDate", Date.class ) ) // <1> + .fetch( 20 ); + Date oldestRelease = result.aggregation( oldestReleaseKey ); + assertThat( oldestRelease ).isEqualTo( Date.valueOf( "1950-12-02" ) ); + // end::min[] + } ); + } + + @Test + void max() { + withinSearchSession( searchSession -> { + // tag::max[] + AggregationKey mostRecentReleaseKey = AggregationKey.of( "mostRecentRelease" ); + SearchResult result = searchSession.search( Book.class ) + .where( f -> f.match().field( "genre" ).matching( Genre.SCIENCE_FICTION ) ) + .aggregation( mostRecentReleaseKey, f -> f.max().field( "releaseDate", Date.class ) ) // <1> + .fetch( 20 ); + Date mostRecentRelease = result.aggregation( mostRecentReleaseKey ); + + // end::max[] + } ); + } + + @Test + void count() { + withinSearchSession( searchSession -> { + // tag::count[] + AggregationKey countPricesKey = AggregationKey.of( "countPrices" ); + SearchResult result = searchSession.search( Book.class ) + .where( f -> f.match().field( "genre" ).matching( Genre.SCIENCE_FICTION ) ) + .aggregation( countPricesKey, f -> f.count().field( "price" ) ) // <1> + .fetch( 20 ); + Long countPrices = result.aggregation( countPricesKey ); + assertThat( countPrices ).isEqualTo( 3L ); + // end::count[] + } ); + } + + @Test + void countDistinct() { + withinSearchSession( searchSession -> { + // tag::count-distinct[] + AggregationKey countDistinctPricesKey = AggregationKey.of( "countDistinctPrices" ); + SearchResult result = searchSession.search( Book.class ) + .where( f -> f.match().field( "genre" ).matching( Genre.SCIENCE_FICTION ) ) + .aggregation( countDistinctPricesKey, f -> f.countDistinct().field( "price" ) ) // <1> + .fetch( 20 ); + Long countDistinctPrices = result.aggregation( countDistinctPricesKey ); + assertThat( countDistinctPrices ).isEqualTo( 3L ); + // end::count-distinct[] + } ); + } + + @Test + void avg() { + withinSearchSession( searchSession -> { + // tag::avg[] + AggregationKey avgPricesKey = AggregationKey.of( "avgPrices" ); + SearchResult result = searchSession.search( Book.class ) + .where( f -> f.match().field( "genre" ).matching( Genre.SCIENCE_FICTION ) ) + .aggregation( avgPricesKey, f -> f.avg().field( "price", Double.class ) ) // <1> + .fetch( 20 ); + Double avgPrices = result.aggregation( avgPricesKey ); + assertThat( avgPrices ).isEqualTo( 20.323333333333334 ); + // end::avg[] + } ); + } + private void withinSearchSession(Consumer action) { with( entityManagerFactory ).runInTransaction( entityManager -> { SearchSession searchSession = Search.session( entityManager );