Browse Source

[DOCS] Support for nested functions in ES|QL STATS...BY (#104788)

* Document nested expressions for stats

* More docs

* Apply suggestions from review

- count-distinct.asciidoc
  - Content restructured, moving the section about approximate counts to end of doc.

- count.asciidoc
  - Clarified that omitting the `expression` parameter in `COUNT` is equivalent to `COUNT(*)`, which counts the number of rows.

- percentile.asciidoc
  - Moved the note about `PERCENTILE` being approximate and non-deterministic to end of doc.

- stats.asciidoc
  - Clarified the `STATS` command
  -  Added a note indicating that individual `null` values are skipped during aggregation

* Comment out mentioning a buggy behavior

* Update sum with inline function example, update test file

* Fix typo

* Delete line

* Simplify wording

* Fix conflict fix typo

---------

Co-authored-by: Liam Thompson <leemthompo@gmail.com>
Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com>
Abdon Pijpelink 1 year ago
parent
commit
980bc500b0

+ 17 - 2
docs/reference/esql/functions/avg.asciidoc

@@ -10,7 +10,9 @@ AVG(expression)
 ----
 
 `expression`::
-Numeric expression. If `null`, the function returns `null`.
+Numeric expression.
+//If `null`, the function returns `null`.
+// TODO: Remove comment when https://github.com/elastic/elasticsearch/issues/104900 is fixed.
 
 *Description*
 
@@ -20,7 +22,7 @@ The average of a numeric expression.
 
 The result is always a `double` no matter the input type.
 
-*Example*
+*Examples*
 
 [source.merge.styled,esql]
 ----
@@ -30,3 +32,16 @@ include::{esql-specs}/stats.csv-spec[tag=avg]
 |===
 include::{esql-specs}/stats.csv-spec[tag=avg-result]
 |===
+
+The expression can use inline functions. For example, to calculate the average
+over a multivalued column, first use `MV_AVG` to average the multiple values per
+row, and use the result with the `AVG` function:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats.csv-spec[tag=docsStatsAvgNestedExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats.csv-spec[tag=docsStatsAvgNestedExpression-result]
+|===

+ 38 - 26
docs/reference/esql/functions/count-distinct.asciidoc

@@ -6,13 +6,13 @@
 
 [source,esql]
 ----
-COUNT_DISTINCT(column[, precision_threshold])
+COUNT_DISTINCT(expression[, precision_threshold])
 ----
 
 *Parameters*
 
-`column`::
-Column for which to count the number of distinct values.
+`expression`::
+Expression that outputs the values on which to perform a distinct count.
 
 `precision_threshold`::
 Precision threshold. Refer to <<esql-agg-count-distinct-approximate>>. The
@@ -23,29 +23,6 @@ same effect as a threshold of 40000. The default value is 3000.
 
 Returns the approximate number of distinct values.
 
-[discrete]
-[[esql-agg-count-distinct-approximate]]
-==== Counts are approximate
-
-Computing exact counts requires loading values into a set and returning its
-size. This doesn't scale when working on high-cardinality sets and/or large
-values as the required memory usage and the need to communicate those
-per-shard sets between nodes would utilize too many resources of the cluster.
-
-This `COUNT_DISTINCT` function is based on the
-https://static.googleusercontent.com/media/research.google.com/fr//pubs/archive/40671.pdf[HyperLogLog++]
-algorithm, which counts based on the hashes of the values with some interesting
-properties:
-
-include::../../aggregations/metrics/cardinality-aggregation.asciidoc[tag=explanation]
-
-The `COUNT_DISTINCT` function takes an optional second parameter to configure
-the precision threshold. The precision_threshold options allows to trade memory
-for accuracy, and defines a unique count below which counts are expected to be
-close to accurate. Above this value, counts might become a bit more fuzzy. The
-maximum supported value is 40000, thresholds above this number will have the
-same effect as a threshold of 40000. The default value is `3000`.
-
 *Supported types*
 
 Can take any field type as input.
@@ -71,3 +48,38 @@ include::{esql-specs}/stats_count_distinct.csv-spec[tag=count-distinct-precision
 |===
 include::{esql-specs}/stats_count_distinct.csv-spec[tag=count-distinct-precision-result]
 |===
+
+The expression can use inline functions. This example splits a string into
+multiple values using the `SPLIT` function and counts the unique values:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats_count_distinct.csv-spec[tag=docsCountDistinctWithExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats_count_distinct.csv-spec[tag=docsCountDistinctWithExpression-result]
+|===
+
+[discrete]
+[[esql-agg-count-distinct-approximate]]
+==== Counts are approximate
+
+Computing exact counts requires loading values into a set and returning its
+size. This doesn't scale when working on high-cardinality sets and/or large
+values as the required memory usage and the need to communicate those
+per-shard sets between nodes would utilize too many resources of the cluster.
+
+This `COUNT_DISTINCT` function is based on the
+https://static.googleusercontent.com/media/research.google.com/fr//pubs/archive/40671.pdf[HyperLogLog++]
+algorithm, which counts based on the hashes of the values with some interesting
+properties:
+
+include::../../aggregations/metrics/cardinality-aggregation.asciidoc[tag=explanation]
+
+The `COUNT_DISTINCT` function takes an optional second parameter to configure
+the precision threshold. The precision_threshold options allows to trade memory
+for accuracy, and defines a unique count below which counts are expected to be
+close to accurate. Above this value, counts might become a bit more fuzzy. The
+maximum supported value is 40000, thresholds above this number will have the
+same effect as a threshold of 40000. The default value is `3000`.

+ 16 - 4
docs/reference/esql/functions/count.asciidoc

@@ -6,14 +6,14 @@
 
 [source,esql]
 ----
-COUNT([input])
+COUNT([expression])
 ----
 
 *Parameters*
 
-`input`::
-Column or literal for which to count the number of values. If omitted, returns a
-count all (the number of rows).
+`expression`::
+Expression that outputs values to be counted.
+If omitted, equivalent to `COUNT(*)` (the number of rows).
 
 *Description*
 
@@ -44,3 +44,15 @@ include::{esql-specs}/docs.csv-spec[tag=countAll]
 |===
 include::{esql-specs}/docs.csv-spec[tag=countAll-result]
 |===
+
+The expression can use inline functions. This example splits a string into
+multiple values using the `SPLIT` function and counts the values:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats.csv-spec[tag=docsCountWithExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats.csv-spec[tag=docsCountWithExpression-result]
+|===

+ 17 - 4
docs/reference/esql/functions/max.asciidoc

@@ -6,17 +6,17 @@
 
 [source,esql]
 ----
-MAX(column)
+MAX(expression)
 ----
 
 *Parameters*
 
-`column`::
-Column from which to return the maximum value.
+`expression`::
+Expression from which to return the maximum value.
 
 *Description*
 
-Returns the maximum value of a numeric column.
+Returns the maximum value of a numeric expression.
 
 *Example*
 
@@ -28,3 +28,16 @@ include::{esql-specs}/stats.csv-spec[tag=max]
 |===
 include::{esql-specs}/stats.csv-spec[tag=max-result]
 |===
+
+The expression can use inline functions. For example, to calculate the maximum
+over an average of a multivalued column, use `MV_AVG` to first average the
+multiple values per row, and use the result with the `MAX` function:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats.csv-spec[tag=docsStatsMaxNestedExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats.csv-spec[tag=docsStatsMaxNestedExpression-result]
+|===

+ 17 - 3
docs/reference/esql/functions/median-absolute-deviation.asciidoc

@@ -6,13 +6,13 @@
 
 [source,esql]
 ----
-MEDIAN_ABSOLUTE_DEVIATION(column)
+MEDIAN_ABSOLUTE_DEVIATION(expression)
 ----
 
 *Parameters*
 
-`column`::
-Column from which to return the median absolute deviation.
+`expression`::
+Expression from which to return the median absolute deviation.
 
 *Description*
 
@@ -44,3 +44,17 @@ include::{esql-specs}/stats_percentile.csv-spec[tag=median-absolute-deviation]
 |===
 include::{esql-specs}/stats_percentile.csv-spec[tag=median-absolute-deviation-result]
 |===
+
+The expression can use inline functions. For example, to calculate the the
+median absolute deviation of the maximum values of a multivalued column, first
+use `MV_MAX` to get the maximum value per row, and use the result with the
+`MEDIAN_ABSOLUTE_DEVIATION` function:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats_percentile.csv-spec[tag=docsStatsMADNestedExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats_percentile.csv-spec[tag=docsStatsMADNestedExpression-result]
+|===

+ 16 - 3
docs/reference/esql/functions/median.asciidoc

@@ -6,13 +6,13 @@
 
 [source,esql]
 ----
-MEDIAN(column)
+MEDIAN(expression)
 ----
 
 *Parameters*
 
-`column`::
-Column from which to return the median value.
+`expression`::
+Expression from which to return the median value.
 
 *Description*
 
@@ -37,3 +37,16 @@ include::{esql-specs}/stats_percentile.csv-spec[tag=median]
 |===
 include::{esql-specs}/stats_percentile.csv-spec[tag=median-result]
 |===
+
+The expression can use inline functions. For example, to calculate the median of
+the maximum values of a multivalued column, first use `MV_MAX` to get the
+maximum value per row, and use the result with the `MEDIAN` function:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats_percentile.csv-spec[tag=docsStatsMedianNestedExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats_percentile.csv-spec[tag=docsStatsMedianNestedExpression-result]
+|===

+ 17 - 4
docs/reference/esql/functions/min.asciidoc

@@ -6,17 +6,17 @@
 
 [source,esql]
 ----
-MIN(column)
+MIN(expression)
 ----
 
 *Parameters*
 
-`column`::
-Column from which to return the minimum value.
+`expression`::
+Expression from which to return the minimum value.
 
 *Description*
 
-Returns the minimum value of a numeric column.
+Returns the minimum value of a numeric expression.
 
 *Example*
 
@@ -28,3 +28,16 @@ include::{esql-specs}/stats.csv-spec[tag=min]
 |===
 include::{esql-specs}/stats.csv-spec[tag=min-result]
 |===
+
+The expression can use inline functions. For example, to calculate the minimum
+over an average of a multivalued column, use `MV_AVG` to first average the
+multiple values per row, and use the result with the `MIN` function:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats.csv-spec[tag=docsStatsMinNestedExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats.csv-spec[tag=docsStatsMinNestedExpression-result]
+|===

+ 28 - 15
docs/reference/esql/functions/percentile.asciidoc

@@ -6,13 +6,13 @@
 
 [source,esql]
 ----
-PERCENTILE(column, percentile)
+PERCENTILE(expression, percentile)
 ----
 
 *Parameters*
 
-`column`::
-Column to convert from multiple values to single value.
+`expression`::
+Expression from which to return a percentile.
 
 `percentile`::
 A constant numeric expression.
@@ -23,18 +23,6 @@ Returns the value at which a certain percentage of observed values occur. For
 example, the 95th percentile is the value which is greater than 95% of the
 observed values and the 50th percentile is the <<esql-agg-median>>.
 
-[discrete]
-[[esql-agg-percentile-approximate]]
-==== `PERCENTILE` is (usually) approximate
-
-include::../../aggregations/metrics/percentile-aggregation.asciidoc[tag=approximate]
-
-[WARNING]
-====
-`PERCENTILE` is also {wikipedia}/Nondeterministic_algorithm[non-deterministic].
-This means you can get slightly different results using the same data.
-====
-
 *Example*
 
 [source.merge.styled,esql]
@@ -45,3 +33,28 @@ include::{esql-specs}/stats_percentile.csv-spec[tag=percentile]
 |===
 include::{esql-specs}/stats_percentile.csv-spec[tag=percentile-result]
 |===
+
+The expression can use inline functions. For example, to calculate a percentile
+of the maximum values of a multivalued column, first use `MV_MAX` to get the
+maximum value per row, and use the result with the `PERCENTILE` function:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats_percentile.csv-spec[tag=docsStatsPercentileNestedExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats_percentile.csv-spec[tag=docsStatsPercentileNestedExpression-result]
+|===
+
+[discrete]
+[[esql-agg-percentile-approximate]]
+==== `PERCENTILE` is (usually) approximate
+
+include::../../aggregations/metrics/percentile-aggregation.asciidoc[tag=approximate]
+
+[WARNING]
+====
+`PERCENTILE` is also {wikipedia}/Nondeterministic_algorithm[non-deterministic].
+This means you can get slightly different results using the same data.
+====

+ 17 - 4
docs/reference/esql/functions/sum.asciidoc

@@ -6,15 +6,15 @@
 
 [source,esql]
 ----
-SUM(column)
+SUM(expression)
 ----
 
-`column`::
-Numeric column.
+`expression`::
+Numeric expression.
 
 *Description*
 
-Returns the sum of a numeric column.
+Returns the sum of a numeric expression.
 
 *Example*
 
@@ -26,3 +26,16 @@ include::{esql-specs}/stats.csv-spec[tag=sum]
 |===
 include::{esql-specs}/stats.csv-spec[tag=sum-result]
 |===
+
+The expression can use inline functions. For example, to calculate
+the sum of each employee's maximum salary changes, apply the
+`MV_MAX` function to each row and then sum the results:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats.csv-spec[tag=docsStatsSumNestedExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats.csv-spec[tag=docsStatsSumNestedExpression-result]
+|===

+ 41 - 7
docs/reference/esql/processing-commands/stats.asciidoc

@@ -6,7 +6,8 @@
 
 [source,esql]
 ----
-STATS [column1 =] expression1[, ..., [columnN =] expressionN] [BY grouping_column1[, ..., grouping_columnN]]
+STATS [column1 =] expression1[, ..., [columnN =] expressionN] 
+[BY grouping_expression1[, ..., grouping_expressionN]]
 ----
 
 *Parameters*
@@ -18,8 +19,10 @@ equal to the corresponding expression (`expressionX`).
 `expressionX`::
 An expression that computes an aggregated value.
 
-`grouping_columnX`::
-The column containing the values to group by.
+`grouping_expressionX`::
+An expression that outputs the values to group by.
+
+NOTE: Individual `null` values are skipped when computing aggregations.
 
 *Description*
 
@@ -28,14 +31,14 @@ and calculate one or more aggregated values over the grouped rows. If `BY` is
 omitted, the output table contains exactly one row with the aggregations applied
 over the entire dataset.
 
-The following aggregation functions are supported:
+The following <<esql-agg-functions,aggregation functions>> are supported:
 
 include::../functions/aggregation-functions.asciidoc[tag=agg_list]
 
 NOTE: `STATS` without any groups is much much faster than adding a group.
 
-NOTE: Grouping on a single column is currently much more optimized than grouping
-      on many columns. In some tests we have seen grouping on a single `keyword`
+NOTE: Grouping on a single expression is currently much more optimized than grouping
+      on many expressions. In some tests we have seen grouping on a single `keyword`
       column to be five times faster than grouping on two `keyword` columns. Do 
       not try to work around this by combining the two columns together with 
       something like <<esql-concat>> and then grouping - that is not going to be
@@ -68,10 +71,14 @@ include::{esql-specs}/stats.csv-spec[tag=statsWithoutBy-result]
 
 It's possible to calculate multiple values:
 
-[source,esql]
+[source.merge.styled,esql]
 ----
 include::{esql-specs}/stats.csv-spec[tag=statsCalcMultipleValues]
 ----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats.csv-spec[tag=statsCalcMultipleValues-result]
+|===
 
 It's also possible to group by multiple values (only supported for long and
 keyword family fields):
@@ -81,6 +88,33 @@ keyword family fields):
 include::{esql-specs}/stats.csv-spec[tag=statsGroupByMultipleValues]
 ----
 
+Both the aggregating functions and the grouping expressions accept other
+functions. This is useful for using `STATS...BY` on multivalue columns.
+For example, to calculate the average salary change, you can use `MV_AVG` to
+first average the multiple values per employee, and use the result with the
+`AVG` function:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats.csv-spec[tag=docsStatsAvgNestedExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats.csv-spec[tag=docsStatsAvgNestedExpression-result]
+|===
+
+An example of grouping by an expression is grouping employees on the first
+letter of their last name:
+
+[source.merge.styled,esql]
+----
+include::{esql-specs}/stats.csv-spec[tag=docsStatsByExpression]
+----
+[%header.monospaced.styled,format=dsv,separator=|]
+|===
+include::{esql-specs}/stats.csv-spec[tag=docsStatsByExpression-result]
+|===
+
 Specifying the output column name is optional. If not specified, the new column
 name is equal to the expression. The following query returns a column named
 `AVG(salary)`:

+ 100 - 2
x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats.csv-spec

@@ -831,8 +831,10 @@ FROM employees
 // end::statsCalcMultipleValues[]
 ;
 
+// tag::statsCalcMultipleValues-result[]
 avg_lang:double | max_lang:integer
 3.1222222222222222|5
+// end::statsCalcMultipleValues-result[]
 ;
 
 docsStatsGroupByMultipleValues
@@ -984,6 +986,104 @@ COUNT(c):long | a:integer
             0 | 1
 ;
 
+docsStatsAvgNestedExpression#[skip:-8.12.99,reason:supported in 8.13+]
+// tag::docsStatsAvgNestedExpression[]
+FROM employees
+| STATS avg_salary_change = AVG(MV_AVG(salary_change))
+// end::docsStatsAvgNestedExpression[]
+;
+
+// tag::docsStatsAvgNestedExpression-result[]
+avg_salary_change:double
+1.3904535864978902
+// end::docsStatsAvgNestedExpression-result[]
+;
+
+docsStatsByExpression#[skip:-8.12.99,reason:supported in 8.13+]
+// tag::docsStatsByExpression[]
+FROM employees
+| STATS my_count = COUNT() BY LEFT(last_name, 1)
+| SORT `LEFT(last_name, 1)`
+// end::docsStatsByExpression[]
+;
+
+// tag::docsStatsByExpression-result[]
+my_count:long    |LEFT(last_name, 1):keyword
+2              |A                 
+11             |B                 
+5              |C                 
+5              |D                 
+2              |E                 
+4              |F                 
+4              |G                 
+6              |H                 
+2              |J                 
+3              |K                 
+5              |L                 
+12             |M                 
+4              |N                 
+1              |O                 
+7              |P                 
+5              |R                 
+13             |S                 
+4              |T                 
+2              |W                 
+3              |Z
+// end::docsStatsByExpression-result[]
+;
+
+docsStatsMaxNestedExpression#[skip:-8.12.99,reason:supported in 8.13+]
+// tag::docsStatsMaxNestedExpression[]
+FROM employees
+| STATS max_avg_salary_change = MAX(MV_AVG(salary_change))
+// end::docsStatsMaxNestedExpression[]
+;
+
+// tag::docsStatsMaxNestedExpression-result[]
+max_avg_salary_change:double
+13.75
+// end::docsStatsMaxNestedExpression-result[]
+;
+
+docsStatsMinNestedExpression#[skip:-8.12.99,reason:supported in 8.13+]
+// tag::docsStatsMinNestedExpression[]
+FROM employees
+| STATS min_avg_salary_change = MIN(MV_AVG(salary_change))
+// end::docsStatsMinNestedExpression[]
+;
+
+// tag::docsStatsMinNestedExpression-result[]
+min_avg_salary_change:double
+-8.46
+// end::docsStatsMinNestedExpression-result[]
+;
+
+docsStatsSumNestedExpression#[skip:-8.12.99,reason:supported in 8.13+]
+// tag::docsStatsSumNestedExpression[]
+FROM employees
+| STATS total_salary_changes = SUM(MV_MAX(salary_change))
+// end::docsStatsSumNestedExpression[]
+;
+
+// tag::docsStatsSumNestedExpression-result[]
+total_salary_changes:double
+446.75
+// end::docsStatsSumNestedExpression-result[]
+;
+
+docsCountWithExpression#[skip:-8.12.99,reason:supported in 8.13+]
+// tag::docsCountWithExpression[]
+ROW words="foo;bar;baz;qux;quux;foo"
+| STATS word_count = COUNT(SPLIT(words, ";"))
+// end::docsCountWithExpression[]
+;
+
+// tag::docsCountWithExpression-result[]
+word_count:long
+6
+// end::docsCountWithExpression-result[]
+;
+
 countMultiValuesRow
 ROW keyword_field = ["foo", "bar"], int_field = [1, 2, 3] | STATS ck = COUNT(keyword_field), ci = COUNT(int_field), c = COUNT(*);
 
@@ -991,7 +1091,6 @@ ck:l | ci:l | c:l
 2    | 3    | 1
 ;
 
-
 countSource
 FROM employees | 
 STATS ck = COUNT(job_positions), 
@@ -1004,4 +1103,3 @@ STATS ck = COUNT(job_positions),
 ck:l | cb:l | cd:l | ci:l | c:l | csv:l 
 221  | 204  | 183  | 183  | 100 | 100
 ;
-

+ 12 - 0
x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats_count_distinct.csv-spec

@@ -153,6 +153,18 @@ m:long  | languages:i
 10   | null
 ;
 
+docsCountDistinctWithExpression#[skip:-8.12.99,reason:supported in 8.13+]
+// tag::docsCountDistinctWithExpression[]
+ROW words="foo;bar;baz;qux;quux;foo"
+| STATS distinct_word_count = COUNT_DISTINCT(SPLIT(words, ";"))
+// end::docsCountDistinctWithExpression[]
+;
+
+// tag::docsCountDistinctWithExpression-result[]
+distinct_word_count:long
+5
+// end::docsCountDistinctWithExpression-result[]
+;
 
 countDistinctWithGroupPrecisionAndNestedExpression#[skip:-8.12.99,reason:supported in 8.13+]
 from employees | stats m = count_distinct(height + 5, 9876) by languages | sort languages;

+ 39 - 0
x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats_percentile.csv-spec

@@ -156,3 +156,42 @@ from employees | stats p50 = percentile(salary_change, -(50-1)+99);
 p50:double
 0.75
 ;
+
+docsStatsMedianNestedExpression#[skip:-8.12.99,reason:supported in 8.13+]
+// tag::docsStatsMedianNestedExpression[]
+FROM employees
+| STATS median_max_salary_change = MEDIAN(MV_MAX(salary_change))
+// end::docsStatsMedianNestedExpression[]
+;
+
+// tag::docsStatsMedianNestedExpression-result[]
+median_max_salary_change:double
+7.69
+// end::docsStatsMedianNestedExpression-result[]
+;
+
+docsStatsMADNestedExpression#[skip:-8.12.99,reason:supported in 8.13+]
+// tag::docsStatsMADNestedExpression[]
+FROM employees
+| STATS m_a_d_max_salary_change = MEDIAN_ABSOLUTE_DEVIATION(MV_MAX(salary_change))
+// end::docsStatsMADNestedExpression[]
+;
+
+// tag::docsStatsMADNestedExpression-result[]
+m_a_d_max_salary_change:double
+5.69
+// end::docsStatsMADNestedExpression-result[]
+;
+
+docsStatsPercentileNestedExpression#[skip:-8.12.99,reason:supported in 8.13+]
+// tag::docsStatsPercentileNestedExpression[]
+FROM employees
+| STATS p80_max_salary_change = PERCENTILE(MV_MAX(salary_change), 80)
+// end::docsStatsPercentileNestedExpression[]
+;
+
+// tag::docsStatsPercentileNestedExpression-result[]
+p80_max_salary_change:double
+12.132
+// end::docsStatsPercentileNestedExpression-result[]
+;