Skip to content

Commit 6758bc7

Browse files
committed
HSEARCH-3661 Document range/terms .values(..)
1 parent 2a6150c commit 6758bc7

File tree

5 files changed

+164
-40
lines changed

5 files changed

+164
-40
lines changed

documentation/src/main/asciidoc/public/reference/_search-dsl-aggregation.adoc

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,23 @@ When ordering entries by ascending count in a `terms` aggregation,
220220
link:{elasticsearchDocUrl}/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-order[hit counts are approximate].
221221
====
222222

223+
[[search-dsl-aggregation-terms-value]]
224+
=== Aggregated value
225+
226+
By default, the aggregated value represents the number of documents that fall into the group of a particular term.
227+
With the `.value(..)` step in aggregation definition, it is now possible to set the aggregated value to something other than the document count.
228+
The `.value(..)` accepts any other aggregation, which will be applied to the documents within the aggregated group.
229+
230+
.Total price of books per category
231+
====
232+
[source, JAVA, indent=0, subs="+callouts"]
233+
----
234+
include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java[tags=terms-sum]
235+
----
236+
<1> Define the path and type of the field whose values should be considered as terms for the aggregation.
237+
<2> Define what the aggregated value should represent, e.g. the sum of all book prices within the genre.
238+
====
239+
223240
[[search-dsl-aggregation-terms-other]]
224241
=== Other options
225242

@@ -318,6 +335,23 @@ include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/Aggre
318335

319336
See <<search-dsl-argument-type>> for more information.
320337

338+
[[search-dsl-aggregation-range-value]]
339+
=== Aggregated value
340+
341+
By default, the aggregated value represents the number of documents that fall into particular, defined range .
342+
With the `.value(..)` step in aggregation definition, it is now possible to set the aggregated value to something other than the document count.
343+
The `.value(..)` accepts any other aggregation, which will be applied to the documents within the aggregated group.
344+
345+
.Total price of books per category
346+
====
347+
[source, JAVA, indent=0, subs="+callouts"]
348+
----
349+
include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java[tags=range-avg]
350+
----
351+
<1> Define the path and type of the field whose value ranges for the aggregation.
352+
<2> Define what the aggregated value should represent, e.g. the average rating of all books within the price range.
353+
====
354+
321355
[[search-dsl-aggregation-range-other]]
322356
=== Other options
323357

@@ -329,14 +363,8 @@ but that can be <<search-dsl-aggregation-common-filter,controlled explicitly wit
329363

330364
include::../components/_incubating-warning.adoc[]
331365

332-
Hibernate Search provides a set of most common metric aggregations such as `sum`, `min`, `max`, `count`,
333-
`count distinct` and `avg`. These aggregations can be requested for fields of numerical or temporal type.
334-
335-
[NOTE]
336-
====
337-
At the moment, it is only possible to request metric aggregations at the root level;
338-
that is, the aggregation will be applied to the entire hit set.
339-
====
366+
Hibernate Search provides a set of most common metric aggregations such as `sum`, `min`, `max`, `count documents`, `count values`,
367+
`count values distinct` and `avg`. These aggregations can be requested for fields of numerical or temporal types.
340368

341369
[IMPORTANT]
342370
====
@@ -390,9 +418,28 @@ include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/Aggre
390418
<1> Define the target field path to which you want to apply the aggregation function and the expected returned type.
391419
====
392420

421+
=== Count documents metric aggregation
422+
423+
The `count documents` aggregation counts the number of documents.
424+
While it is usually discouraged to use this aggregation at the root level,
425+
as the result would be equivalent to the count returned by the search results in `SearchResultTotal`,
426+
this aggregation can still be useful in defining aggregation values in other, more complex aggregations like
427+
<<search-dsl-aggregation-range-value,`range`>> or <<search-dsl-aggregation-terms-value,`terms`>>.
428+
429+
.Count the number of the science fiction books
430+
====
431+
[source, JAVA, indent=0, subs="+callouts"]
432+
----
433+
include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java[tags=count-documents]
434+
----
435+
<1> Apply the document count aggregation. For this function a `Long.class` value is always returned.
436+
====
437+
393438
=== Count values metric aggregation
394439

395440
The `count values` aggregation counts the number of non-empty field values.
441+
This aggregation mostly make sense when the aggregated field is multivalued.
442+
For single-valued fields this aggregation would result in the number of documents where the aggregated field is present.
396443

397444
.Count the number of the science fiction books with prices
398445
====
@@ -407,7 +454,7 @@ include::{sourcedir}/org/hibernate/search/documentation/search/aggregation/Aggre
407454

408455
The `count distinct values` aggregation counts the number of unique field values.
409456

410-
.Count anytime the price field has a different value among all the science fiction books
457+
.Count the number of all different price value among all the science fiction books
411458
====
412459
[source, JAVA, indent=0, subs="+callouts"]
413460
----

documentation/src/test/java/org/hibernate/search/documentation/search/aggregation/AggregationDslIT.java

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -272,23 +272,21 @@ void terms() {
272272
void terms_value() {
273273
withinSearchSession( searchSession -> {
274274
// tag::terms-sum[]
275-
AggregationKey<Map<Double, Double>> sumByPriceKey = AggregationKey.of( "sumByPrice" );
275+
AggregationKey<Map<Genre, Double>> sumByCategoryKey = AggregationKey.of( "sumByCategory" );
276276
SearchResult<Book> result = searchSession.search( Book.class )
277277
.where( f -> f.matchAll() )
278278
.aggregation(
279-
sumByPriceKey, f -> f.terms()
280-
.field( "price", Double.class ) // <1>
281-
.value( f.sum().field( "price", Double.class ) )
279+
sumByCategoryKey, f -> f.terms()
280+
.field( "genre", Genre.class ) // <1>
281+
.value( f.sum().field( "price", Double.class ) ) // <2>
282282
)
283283
.fetch( 20 );
284-
Map<Double, Double> sumByPrice = result.aggregation( sumByPriceKey );
284+
Map<Genre, Double> sumByPrice = result.aggregation( sumByCategoryKey );
285285
// end::terms-sum[]
286286
assertThat( sumByPrice )
287287
.containsExactly(
288-
entry( 7.99, 7.99 ),
289-
entry( 15.99, 15.99 ),
290-
entry( 19.99, 19.99 ),
291-
entry( 24.99, 24.99 )
288+
entry( Genre.SCIENCE_FICTION, 60.97 ),
289+
entry( Genre.CRIME_FICTION, 7.99 )
292290
);
293291
} );
294292

@@ -339,26 +337,26 @@ void terms_value() {
339337
@Test
340338
void range_value() {
341339
withinSearchSession( searchSession -> {
342-
// tag::range-sum[]
343-
AggregationKey<Map<Range<Double>, Double>> countsByPriceKey = AggregationKey.of( "countsByPrice" );
340+
// tag::range-avg[]
341+
AggregationKey<Map<Range<Double>, Double>> avgRatingByPriceKey = AggregationKey.of( "avgRatingByPrice" );
344342
SearchResult<Book> result = searchSession.search( Book.class )
345343
.where( f -> f.matchAll() )
346344
.aggregation(
347-
countsByPriceKey, f -> f.range()
345+
avgRatingByPriceKey, f -> f.range()
348346
.field( "price", Double.class ) // <1>
349-
.range( 0.0, 10.0 ) // <2>
347+
.range( 0.0, 10.0 )
350348
.range( 10.0, 20.0 )
351-
.range( 20.0, null ) // <3>
352-
.value( f.sum().field( "price", Double.class ) )
349+
.range( 20.0, null )
350+
.value( f.avg().field( "ratings", Double.class, ValueModel.RAW ) ) // <2>
353351
)
354352
.fetch( 20 );
355-
Map<Range<Double>, Double> countsByPrice = result.aggregation( countsByPriceKey );
356-
// end::range-sum[]
353+
Map<Range<Double>, Double> countsByPrice = result.aggregation( avgRatingByPriceKey );
354+
// end::range-avg[]
357355
assertThat( countsByPrice )
358356
.containsExactly(
359-
entry( Range.canonical( 0.0, 10.0 ), 7.99 ),
360-
entry( Range.canonical( 10.0, 20.0 ), 35.98 ),
361-
entry( Range.canonical( 20.0, null ), 24.99 )
357+
entry( Range.canonical( 0.0, 10.0 ), 4.0 ),
358+
entry( Range.canonical( 10.0, 20.0 ), 3.6 ),
359+
entry( Range.canonical( 20.0, null ), 3.2 )
362360
);
363361
} );
364362

@@ -584,8 +582,8 @@ void sum() {
584582
.aggregation( sumPricesKey, f -> f.sum().field( "price", Double.class ) ) // <1>
585583
.fetch( 20 );
586584
Double sumPrices = result.aggregation( sumPricesKey );
587-
assertThat( sumPrices ).isEqualTo( 60.97 );
588585
// end::sums[]
586+
assertThat( sumPrices ).isEqualTo( 60.97 );
589587
} );
590588
}
591589

@@ -599,8 +597,8 @@ void min() {
599597
.aggregation( oldestReleaseKey, f -> f.min().field( "releaseDate", Date.class ) ) // <1>
600598
.fetch( 20 );
601599
Date oldestRelease = result.aggregation( oldestReleaseKey );
602-
assertThat( oldestRelease ).isEqualTo( Date.valueOf( "1950-12-02" ) );
603600
// end::min[]
601+
assertThat( oldestRelease ).isEqualTo( Date.valueOf( "1950-12-02" ) );
604602
} );
605603
}
606604

@@ -614,28 +612,43 @@ void max() {
614612
.aggregation( mostRecentReleaseKey, f -> f.max().field( "releaseDate", Date.class ) ) // <1>
615613
.fetch( 20 );
616614
Date mostRecentRelease = result.aggregation( mostRecentReleaseKey );
617-
618615
// end::max[]
616+
assertThat( mostRecentRelease ).isEqualTo( Date.valueOf( "1983-01-01" ) );
619617
} );
620618
}
621619

622620
@Test
623-
void count() {
621+
void countDocuments() {
624622
withinSearchSession( searchSession -> {
625-
// tag::count[]
626-
AggregationKey<Long> countPricesKey = AggregationKey.of( "countPrices" );
623+
// tag::count-documents[]
624+
AggregationKey<Long> countBooksKey = AggregationKey.of( "countBooks" );
627625
SearchResult<Book> result = searchSession.search( Book.class )
628626
.where( f -> f.match().field( "genre" ).matching( Genre.SCIENCE_FICTION ) )
629-
.aggregation( countPricesKey, f -> f.countValues().field( "price" ) ) // <1>
627+
.aggregation( countBooksKey, f -> f.countDocuments() ) // <1>
630628
.fetch( 20 );
631-
Long countPrices = result.aggregation( countPricesKey );
629+
Long countPrices = result.aggregation( countBooksKey );
630+
// end::count-documents[]
632631
assertThat( countPrices ).isEqualTo( 3L );
632+
} );
633+
}
634+
635+
@Test
636+
void countValues() {
637+
withinSearchSession( searchSession -> {
638+
// tag::count[]
639+
AggregationKey<Long> countRatingsKey = AggregationKey.of( "countRatings" );
640+
SearchResult<Book> result = searchSession.search( Book.class )
641+
.where( f -> f.match().field( "genre" ).matching( Genre.SCIENCE_FICTION ) )
642+
.aggregation( countRatingsKey, f -> f.countValues().field( "ratings" ) ) // <1>
643+
.fetch( 20 );
644+
Long countPrices = result.aggregation( countRatingsKey );
633645
// end::count[]
646+
assertThat( countPrices ).isEqualTo( 15L );
634647
} );
635648
}
636649

637650
@Test
638-
void countDistinct() {
651+
void countDistinctValues() {
639652
withinSearchSession( searchSession -> {
640653
// tag::count-distinct[]
641654
AggregationKey<Long> countDistinctPricesKey = AggregationKey.of( "countDistinctPrices" );
@@ -644,8 +657,8 @@ void countDistinct() {
644657
.aggregation( countDistinctPricesKey, f -> f.countDistinctValues().field( "price" ) ) // <1>
645658
.fetch( 20 );
646659
Long countDistinctPrices = result.aggregation( countDistinctPricesKey );
647-
assertThat( countDistinctPrices ).isEqualTo( 3L );
648660
// end::count-distinct[]
661+
assertThat( countDistinctPrices ).isEqualTo( 3L );
649662
} );
650663
}
651664

@@ -679,6 +692,7 @@ private void initData() {
679692
book1.setPrice( 24.99 );
680693
book1.setGenre( Genre.SCIENCE_FICTION );
681694
book1.setReleaseDate( Date.valueOf( "1950-12-02" ) );
695+
book1.setRatings( List.of( 5, 5, 4, 2, 0 ) );
682696
addEdition( book1, "Mass Market Paperback, 1st Edition", 9.99 );
683697
addEdition( book1, "Kindle", 9.99 );
684698

@@ -688,6 +702,7 @@ private void initData() {
688702
book2.setPrice( 19.99 );
689703
book2.setGenre( Genre.SCIENCE_FICTION );
690704
book2.setReleaseDate( Date.valueOf( "1953-10-01" ) );
705+
book2.setRatings( List.of( 5, 5, 3, 3, 5 ) );
691706
addEdition( book2, "Mass Market Paperback, 12th Edition", 4.99 );
692707
addEdition( book2, "Kindle", 19.99 );
693708

@@ -697,6 +712,7 @@ private void initData() {
697712
book3.setPrice( 15.99 );
698713
book3.setGenre( Genre.SCIENCE_FICTION );
699714
book3.setReleaseDate( Date.valueOf( "1983-01-01" ) );
715+
book3.setRatings( List.of( 3, 3, 3, 3, 3 ) );
700716
addEdition( book3, "Mass Market Paperback, 59th Edition", 3.99 );
701717
addEdition( book3, "Kindle", 5.99 );
702718

@@ -706,6 +722,7 @@ private void initData() {
706722
book4.setPrice( 7.99 );
707723
book4.setGenre( Genre.CRIME_FICTION );
708724
book4.setReleaseDate( Date.valueOf( "2008-02-05" ) );
725+
book4.setRatings( List.of( 4, 4, 4, 4, 4 ) );
709726
addEdition( book4, "Mass Market Paperback, 2nd Edition", 10.99 );
710727
addEdition( book4, "Kindle", 12.99 );
711728

documentation/src/test/java/org/hibernate/search/documentation/search/aggregation/Book.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.List;
1010

1111
import jakarta.persistence.CascadeType;
12+
import jakarta.persistence.ElementCollection;
1213
import jakarta.persistence.Entity;
1314
import jakarta.persistence.Id;
1415
import jakarta.persistence.OneToMany;
@@ -42,6 +43,10 @@ public class Book {
4243
@GenericField(aggregable = Aggregable.YES)
4344
private Date releaseDate;
4445

46+
@GenericField(aggregable = Aggregable.YES)
47+
@ElementCollection
48+
private List<Integer> ratings;
49+
4550
@OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
4651
@OrderColumn
4752
@IndexedEmbedded(structure = ObjectStructure.NESTED)
@@ -97,4 +102,12 @@ public List<BookEdition> getEditions() {
97102
public void setEditions(List<BookEdition> editions) {
98103
this.editions = editions;
99104
}
105+
106+
public List<Integer> getRatings() {
107+
return ratings;
108+
}
109+
110+
public void setRatings(List<Integer> ratings) {
111+
this.ratings = ratings;
112+
}
100113
}

engine/src/main/java/org/hibernate/search/engine/search/aggregation/dsl/RangeAggregationRangeValueStep.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import org.hibernate.search.engine.search.aggregation.SearchAggregation;
1111
import org.hibernate.search.engine.search.predicate.dsl.TypedSearchPredicateFactory;
12+
import org.hibernate.search.util.common.annotation.Incubating;
1213
import org.hibernate.search.util.common.data.Range;
1314

1415
/**
@@ -18,13 +19,35 @@
1819
* @param <PDF> The type of factory used to create predicates in {@link RangeAggregationOptionsStep#filter(Function)}.
1920
* @param <F> The type of the targeted field.
2021
*/
22+
@Incubating
2123
public interface RangeAggregationRangeValueStep<
2224
SR,
2325
PDF extends TypedSearchPredicateFactory<SR>,
2426
F> {
25-
27+
/**
28+
* Specify which aggregation to apply to the documents within the range.
29+
* <p>
30+
* This allows to "group" the documents by "ranges" and then apply one of the aggregations from {@link SearchAggregationFactory}
31+
* to the documents in that group.
32+
*
33+
* @param aggregation The aggregation to apply to the documents within each range.
34+
* @return The next step in range aggregation definition.
35+
* @param <T> The type of the aggregated results within a range.
36+
*/
37+
@Incubating
2638
<T> RangeAggregationOptionsStep<SR, ?, PDF, F, Map<Range<F>, T>> value(SearchAggregation<T> aggregation);
2739

40+
/**
41+
* Specify which aggregation to apply to the documents within the range.
42+
* <p>
43+
* This allows to "group" the documents by "ranges" and then apply one of the aggregations from {@link SearchAggregationFactory}
44+
* to the documents in that group.
45+
*
46+
* @param aggregation The aggregation to apply to the documents within each range.
47+
* @return The next step in range aggregation definition.
48+
* @param <T> The type of the aggregated results within a range.
49+
*/
50+
@Incubating
2851
default <T> RangeAggregationOptionsStep<SR, ?, PDF, F, Map<Range<F>, T>> value(AggregationFinalStep<T> aggregation) {
2952
return value( aggregation.toAggregation() );
3053
}

0 commit comments

Comments
 (0)