Aggregations and additivity
This guide explains the authoring format and semantics. Target support varies; see Support and limitations.
This document defines the canonical behavior of every Semantic Hub Aggregation, the structural parameters accepted by AggregateDefinition, and the rules for distinctness, deduplication, rollup, semi-additivity, empty populations, and SQL-target coverage.
Metrics.json is authoritative for authored shape. Expression language defines parsed aggregate-call syntax, Metric evaluation and composition defines dependency staging, and Grain and multi-fact query planning defines physical compilation.
1. Aggregate definition contract
A named AGGREGATE metric and a MetricInput.inline_metric share AggregateDefinition:
expression: revenue
aggregation: SUM
aggregation_parameters: {}
null_handling:
empty_result: AUTO
null_result: "NULL"
The properties have separate roles:
| Property | Contract |
|---|---|
expression |
The canonical row-level GENERIC input. Required for every aggregation except COUNT_ROWS; forbidden for COUNT_ROWS. |
aggregation |
One of the 26 canonical Aggregation values. Required. |
aggregation_parameters |
Structural parameters whose legality depends on the selected aggregation. |
null_handling.empty_result |
Result when no rows contribute. AUTO preserves function-specific behavior. |
null_handling.null_result |
Final fallback when a non-empty calculation produces null. It never rewrites intermediate aggregate inputs. |
filter |
Named AggregateMetric only. An intrinsic predicate conditionally supplies this metric's inputs; it is not a shared query filter. |
aggregation_behavior |
Named AggregateMetric only. Declares additive, non-additive, or semi-additive business behavior. |
An inline metric deliberately omits name, metric_type, presentation metadata, common grain and filter contexts, overrides, and nested metric inputs. Its containing MetricInput may add a local filter or offset.
2. Complete aggregation catalog
In the tables below, x is AggregateDefinition.expression, y is exactly one of aggregation_parameters.second_field_ref or .expression, and p is aggregation_parameters.percentile.
2.1 Additive, counting, and selection aggregates
| Aggregation | Canonical operation | Required input and parameters | Empty AUTO |
Decomposability and rollup notes |
|---|---|---|---|---|
SUM |
Sum non-null x values. |
x. Optional key deduplication. |
Null | Distributive over compatible disjoint populations. Often additive, but grain and business behavior can restrict it. |
AVERAGE |
Arithmetic mean of non-null x values. |
x. Optional key deduplication. |
Null | Algebraic; retain sum and non-null count. Never average partial averages without weights. |
MIN |
Minimum non-null x. |
x. |
Null | Distributive with MIN as merge operation. This does not mean values may be summed across dimensions. |
MAX |
Maximum non-null x. |
x. |
Null | Distributive with MAX as merge operation. |
COUNT_ROWS |
Count contributing rows. | No expression. |
Zero | Distributive over disjoint row populations. |
COUNT_NON_NULL |
Count rows whose x is non-null. |
x. |
Zero | Distributive over disjoint row populations. |
COUNT_DISTINCT |
Count distinct non-null x values. |
x. |
Zero | Holistic unless exact distinct state is retained. Scalar subgroup counts are not additive. |
APPROX_COUNT_DISTINCT |
Estimate the number of distinct non-null x values. |
x; optional approximate_accuracy. |
Zero | Sketch-based. Only compatible mergeable sketch states can roll up; scalar estimates cannot be summed. |
SUM_DISTINCT |
Sum each distinct non-null x value once. |
x. |
Null | Holistic value-distinct operation. It is not entity deduplication. |
AVERAGE_DISTINCT |
Average distinct non-null x values. |
x. |
Null | Holistic value-distinct operation. It is not entity deduplication. |
MEDIAN |
Middle value, or canonical target-equivalent median, over non-null x. |
x. |
Null | Holistic and order-dependent by value. Requires exact-grain values or a proven equivalent state. |
MODE |
Most frequent non-null x under the canonical target mapping. |
x. |
Null | Holistic. Tie behavior must be defined by the dialect mapping or the target is unsupported. |
ANY_VALUE |
An arbitrary x value selected under the target's canonical mapping. |
x. |
Null | Distributive but nondeterministic unless a uniqueness or functional-dependency contract proves one possible value. Native null-selection behavior must match the canonical capability. Do not use it to suppress grouping errors. |
Empty AUTO states the canonical function-specific result expected by Semantic Hub. A target mapping must preserve it even when native target behavior differs.
2.2 Statistical and percentile aggregates
| Aggregation | Canonical operation | Required input and parameters | Empty AUTO |
Decomposability and notes |
|---|---|---|---|---|
STDDEV_SAMP |
Sample standard deviation of non-null x. |
x. |
Null | Algebraic; sufficient count, sum, and squared-deviation state must be retained. Fewer than two values produce the canonical null state. |
STDDEV_POP |
Population standard deviation of non-null x. |
x. |
Null | Algebraic; population variant must not be substituted with sample behavior. |
VAR_SAMP |
Sample variance of non-null x. |
x. |
Null | Algebraic. VARIANCE(x) normalizes to this variant. |
VAR_POP |
Population variance of non-null x. |
x. |
Null | Algebraic; population variant. |
PERCENTILE_CONT |
Continuous percentile p, interpolating between ordered values when required. |
x and percentile. |
Null | Holistic ordered-set operation. |
PERCENTILE_DISC |
Discrete percentile p, returning a value from the ordered population. |
x and percentile. |
Null | Holistic ordered-set operation. |
APPROX_PERCENTILE |
Target-neutral approximate percentile request. | x and percentile; optional approximate_accuracy. |
Null | Sketch-based. Algorithm, accuracy interpretation, and mergeability are target capabilities. |
CORRELATION |
Correlation of paired non-null values (x, y). |
x and exactly one second input. |
Null | Algebraic paired statistic. Input roles and paired-row population must be preserved. |
COVAR_SAMP |
Sample covariance of paired (x, y). |
x and exactly one second input. |
Null | Algebraic paired statistic; sample semantics. |
COVAR_POP |
Population covariance of paired (x, y). |
x and exactly one second input. |
Null | Algebraic paired statistic; population semantics. |
REGR_SLOPE |
Regression slope over paired (x, y) with target argument order mapped explicitly. |
x and exactly one second input. |
Null | Algebraic. Semantic first- and second-input roles must not be reversed. |
REGR_INTERCEPT |
Regression intercept over paired (x, y) with target argument order mapped explicitly. |
x and exactly one second input. |
Null | Algebraic. Semantic first- and second-input roles must not be reversed. |
percentile is inclusive from 0 through 1. 0.9 means the 90th percentile; 90 is invalid.
For every paired statistic, the two expressions are evaluated on the same source row population and at the same pairing grain. Rows for which either conditioned input is null do not form a pair. A compiler must reject inputs that require incompatible anchors or fanout paths.
2.3 Ordered list aggregation
| Aggregation | Canonical operation | Required input and parameters | Empty AUTO |
Decomposability and notes |
|---|---|---|---|---|
LIST_AGG |
Concatenate non-null x values using delimiter and the declared semantic order. |
x; optional delimiter defaults to ,; order_by is required for deterministic output. |
Null | Holistic and order-sensitive. Partial lists are mergeable only with preserved global ordering and equivalent target overflow behavior. |
The JSON Schema restricts delimiter and order_by to LIST_AGG. It permits omission of order_by, but the result is then nondeterministic. A validation policy intended for reproducible models should reject or warn on unordered LIST_AGG; caches and conformance fixtures must not treat an unordered result as stable.
Target-specific overflow limits, truncation syntax, collation, and text coercion are capabilities. Silent truncation is not a valid mapping.
3. Structural aggregation parameters
Semantic validation narrows AggregationParameters beyond basic JSON object shape.
| Parameter | Legal aggregation | Requirement |
|---|---|---|
percentile |
PERCENTILE_CONT, PERCENTILE_DISC, APPROX_PERCENTILE |
Required, numeric, from 0 through 1. Illegal for other functions. |
approximate_accuracy |
APPROX_COUNT_DISTINCT, APPROX_PERCENTILE |
Optional positive integer hint. Illegal for exact functions. A target must document whether and how it honors the hint. |
deduplicate_by_dimensions |
SUM, AVERAGE |
Optional non-empty field-reference list. Establishes one logical input per composite key before aggregation. |
delimiter |
LIST_AGG |
Optional string; default ,. Illegal for other functions. |
order_by |
LIST_AGG |
Ordered non-empty list when supplied; required for deterministic list output. Illegal for other aggregate definitions unless a future schema names that use. |
second_field_ref |
CORRELATION, COVAR_SAMP, COVAR_POP, REGR_SLOPE, REGR_INTERCEPT |
Exactly one of this property and parameter expression is required. |
expression |
Same paired statistics | Canonical row expression for the second input. Exactly one second-input representation is required. |
Unknown or inapplicable parameters are semantic errors even where a generic JSON Schema property definition alone cannot express the complete function dispatch.
- name: order_value_p90
metric_type: AGGREGATE
expression: revenue
aggregation: PERCENTILE_CONT
aggregation_parameters:
percentile: 0.9
- name: price_quantity_correlation
metric_type: AGGREGATE
expression: unit_price
aggregation: CORRELATION
aggregation_parameters:
expression: "COALESCE(quantity, 0) * package_size"
4. Value distinctness is not key deduplication
4.1 Value-distinct aggregates
COUNT_DISTINCT, SUM_DISTINCT, and AVERAGE_DISTINCT deduplicate by the evaluated x value. If two different entities produce the same value, that value contributes once.
- name: distinct_shipping_value_sum
metric_type: AGGREGATE
expression: shipping_amount
aggregation: SUM_DISTINCT
If orders 101 and 102 both have shipping amount 10, the sum of distinct values contributes 10, not 20. This is correct only when value distinctness is the intended business definition.
4.2 Key-based deduplication
Use deduplicate_by_dimensions when a denormalized dataset repeats one logical entity-level value:
- name: shipping_revenue
metric_type: AGGREGATE
expression: shipping_amount
aggregation: SUM
aggregation_parameters:
deduplicate_by_dimensions: [order_id]
The logical evaluation is:
- Bind the deduplication fields from the aggregate metric's dataset anchor.
- Apply the dataset, security, intrinsic metric, input, and applicable query filters.
- Group eligible rows by the composite deduplication key plus dimensions required by the metric's evaluation grain.
- Verify that all contributing non-null
expressionvalues for one key agree. - Produce one logical input value per key.
- Apply
SUMorAVERAGEto those logical inputs.
Conflicting values for one key are an error. The compiler must not choose MIN, MAX, ANY_VALUE, or the first physical row as a repair.
4.3 Scope and null keys
The deduplication key is part of the metric's canonical identity. Its fields must resolve unambiguously and form a fanout-safe determinant for the logical input population. If nullable keys can collapse unrelated entities or target null-grouping behavior would change identity, validation fails unless the applicable key contract proves the intended semantics.
Deduplication is performed before the final aggregate and after filters that define the input population. Moving it before a metric's intrinsic filter or after a fanout-producing join can change results and is invalid.
5. Intrinsic filters and aggregate inputs
An AggregateMetric.filter belongs to one metric:
- name: completed_revenue
metric_type: AGGREGATE
expression: revenue
aggregation: SUM
filter: "status = 'COMPLETED'"
Conceptually:
SUM(CASE WHEN status = 'COMPLETED' THEN revenue ELSE NULL END)
The compiler uses function-specific conditioning:
| Aggregate shape | Conceptual conditioned input |
|---|---|
COUNT_ROWS |
COUNT(CASE WHEN p THEN 1 END) |
COUNT_NON_NULL(x) |
COUNT(CASE WHEN p THEN x END) |
COUNT_DISTINCT(x) |
COUNT(DISTINCT CASE WHEN p THEN x END) |
SUM(x) |
SUM(CASE WHEN p THEN x ELSE NULL END) |
| Ordered-set percentile | Condition the ordered input x; keep p as the percentile parameter. |
| Paired statistic | Condition both x and y with the same predicate before null-pair exclusion. |
The excluded-row null is an intermediate “does not contribute” marker. null_handling.null_result must not replace it before aggregation. The predicate may become a native target aggregate filter only when the dialect proves identical null, distinct, order, and pair semantics.
The predicate cannot become a shared query WHERE unless every affected branch has the same effective filter and the rewrite preserves sibling metrics.
6. Additivity and decomposability are different
aggregation_behavior describes whether a business measure can be combined across dimensions. Decomposability describes what intermediate state an aggregation requires. Neither substitutes for the other.
| Concept | Question |
|---|---|
| Additivity | Is combining the business measure across this dimension meaningful? |
| Decomposability | What state is required to reproduce the same aggregate at a coarser grain? |
| Grain safety | Can the required rows or states be reached without duplication or loss? |
| Target capability | Can the selected dialect render or emulate the operation exactly? |
For example, MIN is distributive because partial minima can be merged with MIN. It is not additive in the ordinary business sense and must not be rolled up with SUM. COUNT_DISTINCT may represent an additive-looking concept such as customers, but scalar subgroup distinct counts are holistic and cannot be summed when customers overlap.
6.1 ADDITIVE
ADDITIVE declares that the metric can be combined across all applicable dimensions using its authorized rollup state and operation. It does not authorize raw-row fanout, arbitrary SUM, incompatible filter populations, or merging of scalar holistic results.
Typical candidates are sums and counts over disjoint populations. The compiler still proves disjointness and the correct merge operation.
6.2 NON_ADDITIVE
NON_ADDITIVE declares that precomputed scalar results cannot be combined across dimensions. Ratios, percentages, unit prices, medians, distinct counts, and other holistic results are common examples.
At a coarser grain the compiler must recompute from dependencies or sufficient state. If that state is unavailable, it rejects the rollup.
6.3 SEMI_ADDITIVE
SEMI_ADDITIVE means the metric combines normally across some dimensions but requires explicit selection across each declared entry in non_additive_dimensions.
- name: ending_account_balance
metric_type: AGGREGATE
expression: balance
aggregation: SUM
aggregation_behavior:
additivity: SEMI_ADDITIVE
non_additive_dimensions:
- dimension: snapshot_date
selection: LAST_NON_EMPTY
group_by_dimensions: [account_id]
order_by:
- field: snapshot_date
direction: ASC
The operation is “choose a boundary balance per account across time, then sum accounts,” not “sum every snapshot.”
SEMI_ADDITIVE requires at least one non_additive_dimensions entry. That array is illegal for ADDITIVE and NON_ADDITIVE.
7. Semi-additive selection
7.1 Selection modes
| Selection | Contract |
|---|---|
FIRST_VALUE |
Select the first ordered row, even when its metric input is null. |
LAST_VALUE |
Select the last ordered row, even when its metric input is null. |
FIRST_NON_EMPTY |
Select the first ordered row whose metric input contributes a non-empty value. |
LAST_NON_EMPTY |
Select the last ordered row whose metric input contributes a non-empty value. |
AVERAGE_OVER_DIMENSION |
Evaluate the metric at positions along the non-additive dimension, then average those position values using preserved population state. |
“Non-empty” is not the same as non-null. An existing position whose non-empty calculation returns null remains distinguishable from an absent position. The selection uses the metric's retained presence state, not a blanket value IS NOT NULL test unless that is proven equivalent.
7.2 Grouping before selection
group_by_dimensions names the entities for which a boundary is chosen. For balances it is commonly account; for inventory it may be product and warehouse. Omitting an entity key means one boundary value is chosen for the entire current partition, which is rarely equivalent.
After boundary selection, the ordinary aggregate combines the selected entity values across compatible dimensions.
7.3 Ordering
First and last selection requires deterministic ordering. order_by normally includes the non-additive dimension and may include additional tie-breakers. If two eligible rows remain tied and the model does not establish functional uniqueness, validation fails or returns a configured nondeterminism warning; a physical row order is never inferred.
7.4 Multiple non-additive dimensions
Every declared rule is a semantic restriction. The compiler must derive a jointly valid selection plan. Because the schema does not assign procedural order to the array, a result that changes when rules are applied in a different sequence is ambiguous and must be rejected rather than choosing array order.
7.5 Interaction with time filling
Missing-period filling occurs according to the metric's time context. ZERO creates a business zero only when explicitly declared; it must not silently turn a missing snapshot into an observed zero balance. Boundary selection and time filling must preserve the distinction required by FIRST_NON_EMPTY and LAST_NON_EMPTY.
8. Empty, all-null, and non-empty results
An aggregate result carries at least two conceptual facts: whether any row contributed, and the aggregate value. These states are distinct:
| Input state | Example | Policy boundary |
|---|---|---|
| No contributing rows | Intrinsic filter excludes every row. | empty_result |
Rows contribute but every x is null |
Three eligible rows, all with null revenue. | Function result, then null_result if final value is null. |
| Rows contribute a valid zero | Values sum to numeric zero. | Ordinary value; no null fallback. |
| Rows exist but no non-null pair exists | Every statistical row has null x or y. |
Non-empty calculation with a null function result, then null_result. |
empty_result: AUTO preserves the cataloged function behavior: count operations return zero; other current aggregations return null. A literal fallback must type-check against result_type, and reserved ZERO is numeric-only.
Policy application is:
- evaluate row expressions without applying the metric's final fallback;
- condition inputs with the intrinsic metric predicate;
- perform key deduplication when declared;
- execute the aggregate and retain population state;
- apply
empty_resultwhen no rows contributed; otherwise - apply
null_resultif the final aggregate value is null.
The plan may carry a row count, non-null count, paired count, or explicit presence flag beside the value. A scalar aggregate alone is not always sufficient.
9. Rollup and materialization state
The decomposability categories are:
| Category | Required state |
|---|---|
| Distributive | One partial value that can be merged with the same canonical operation, such as sum, count, minimum, or maximum. |
| Algebraic | Fixed-size sufficient state, such as sum and count for average or moment state for variance. |
| Holistic | Complete input values or an exact-grain result; no bounded universal scalar state. |
| Sketch-based | A compatible algorithm- and version-specific mergeable sketch. |
Cache eligibility additionally requires identical grain, filter population, null behavior, ordering, collation, security context, and target semantics.
Invalid rollups include:
- averaging averages without counts;
- summing distinct counts or approximate scalar estimates;
- merging percentiles without complete values or a compatible sketch;
- concatenating partial lists without a preserved global order;
- summing fixed-grain values broadcast to finer output rows; and
- summing snapshots across a declared non-additive time dimension.
grain.rollup_aggregation: RECOMPUTE is the safe default. A declared scalar rollup is legal only when both additivity and required intermediate state prove equivalence.
10. Canonical syntax and normalization
Authors of structural metrics use the Aggregation enum values. Aggregate calls inside DERIVED.expression use standard GENERIC syntax and normalize to the same identities:
| Parsed input | Canonical aggregation |
|---|---|
AVG(x) |
AVERAGE |
COUNT(*) |
COUNT_ROWS |
COUNT(x) |
COUNT_NON_NULL |
COUNT(DISTINCT x) |
COUNT_DISTINCT |
SUM(DISTINCT x) |
SUM_DISTINCT |
AVG(DISTINCT x) |
AVERAGE_DISTINCT |
STDDEV(x) |
STDDEV_SAMP |
VARIANCE(x) |
VAR_SAMP |
PERCENTILE_CONT(p) WITHIN GROUP (ORDER BY x) |
PERCENTILE_CONT with structural percentile. |
APPROX_PERCENTILE(x, p) |
APPROX_PERCENTILE with structural percentile. |
Accepted aliases do not create separate lineage, cache, dependency, or target-capability identities.
11. Target coverage contract
The typed SQL targets are Snowflake, PostgreSQL, DuckDB, Oracle, and Databricks. DuckDB has its own dialect and capability definition; it is not a PostgreSQL alias. OTHER or a generic connection is unsupported until an explicit dialect is added.
Canonical availability in Metrics.json does not promise that every target has a native function. For each aggregation and target, the versioned dialect capability definition reports one of:
| Coverage state | Meaning |
|---|---|
| Native | The target has a function or construct with equivalent types, null behavior, ordering, distinctness, and edge cases. |
| Exact rewrite | The compiler emits a tested expression or subplan that preserves canonical semantics. |
| Conditional | Support depends on type, parameter, accuracy hint, collation, ordering, server version, or another declared capability. |
| Unsupported | No exact mapping is available; compilation fails with a structured target-capability diagnostic. |
The compiler must not silently substitute:
- sample for population statistics or the reverse;
- exact for approximate behavior when accuracy or resource semantics matter, or approximate for exact behavior;
- continuous for discrete percentiles;
- value distinctness for key deduplication;
- nondeterministic selection for deterministic first, last, mode, or list behavior;
- scalar approximate results for mergeable sketch state; or
- a target's convenient null or empty behavior for the canonical policy.
11.1 Capability surface
A target capability entry for an aggregation includes, as applicable:
- canonical aggregation identity and target/server version range;
- accepted input and result types, precision, and nullability;
- native function or exact rewrite strategy;
- parameter mapping and bounds;
- distinct and key-deduplication support;
- ordered-set, collation, null-order, and tie behavior;
- approximate algorithm, accuracy-hint interpretation, and sketch compatibility;
- partial-state and rollup support;
- empty, all-null, overflow, and insufficient-sample behavior; and
- whether filtered inputs use
CASE, nativeFILTER, or another proven equivalent.
Model validation can report that a model is not deployable to one selected target even when it is valid canonically. Query compilation rechecks the capabilities actually required by the expanded dependency DAG.
11.2 Required target tests
Every aggregation feature requires:
- a positive test for each target that advertises native, exact-rewrite, or applicable conditional support;
- a negative test for each unsupported target or condition;
- null, empty, all-null, and boundary-parameter fixtures;
- filtered-input tests, including paired statistics where applicable;
- rollup or explicit non-rollup tests based on decomposability;
- deterministic order and tie fixtures for order-sensitive operations; and
- result-type and precision assertions.
A dialect must not advertise support solely because representative SQL parses.
12. Validation failures
Semantic Hub rejects an aggregate definition or query for conditions including:
- missing
expressionfor a non-COUNT_ROWSaggregation; - an
expressionsupplied toCOUNT_ROWS; - missing, out-of-range, or inapplicable
percentile; - a second input missing from, duplicated in, or supplied to the wrong aggregation;
deduplicate_by_dimensionson a function other thanSUMorAVERAGE;- conflicting non-null values for one deduplication key;
- a paired statistic whose inputs do not share one row population and pairing grain;
- a non-Boolean intrinsic filter;
- a semi-additive declaration without a non-additive dimension;
- a non-additive-dimension rule on a non-semi-additive metric;
- nondeterministic first, last, or ordered-list behavior where deterministic output is required;
- a holistic or sketch scalar rolled up with an invalid operation;
- a fanout-producing relationship path that duplicates aggregate inputs; or
- an unsupported target function, type, parameter, ordering, accuracy, or null behavior.
Diagnostics include the metric or anonymous dependency identity, aggregation, property and source span, native grain, relevant population or rollup state, target when applicable, and a concrete remediation.
13. Authoring guidance
- Choose the aggregation that states the business operation; do not choose one merely because a target exposes a convenient function.
- Declare dataset grain and validate relationship cardinality before relying on aggregate results.
- Use ordinary
SUMorAVERAGEplusdeduplicate_by_dimensionsfor repeated entity-level values. - Use value-distinct aggregates only when equality of values, not identity of entities, defines duplication.
- Treat ratios, percentages, distinct counts, percentiles, medians, and broadcast fixed-grain values as non-additive unless a stronger canonical state proves otherwise.
- Model snapshot and balance behavior with
SEMI_ADDITIVEboundary selection, including entity grouping and deterministic order. - Preserve
AUTOempty semantics unless a business zero or other fallback is genuinely defined. - Require ordered
LIST_AGGand deterministic first/last behavior for reproducible output. - Use a metric target override only as a documented last resort; it does not change canonical dependencies, grain, filters, additivity, or null behavior.
14. Conformance checklist
A conforming implementation demonstrates that:
- All 26
Aggregationvalues are parsed, bound, normalized, and either rendered exactly or rejected per target capability. - Function-specific required and forbidden parameters are validated after JSON Schema shape validation.
- Filter conditioning preserves count, distinct, ordered-set, paired-statistic, empty, and null semantics.
- Value distinctness and key-based deduplication produce different, tested plans.
- Conflicting values for a deduplication key fail instead of being selected arbitrarily.
- Additivity, decomposability, grain safety, and target support remain independent proofs.
- Semi-additive selection preserves entity grouping, deterministic ordering, and empty versus null state.
- Materialized partial state is sufficient for every authorized rollup.
- Snowflake, PostgreSQL, DuckDB, Oracle, and Databricks have explicit positive and unsupported-capability tests; DuckDB is tested as its own dialect.
- Unsupported or unsafe aggregate behavior never falls back to approximate SQL or an unmodeled
DISTINCT.