Skip to content

Read Primitives — Approximate Aggregate Functions

read_primitives exercises one-shot approximate aggregates that every modern OLAP engine exposes. These queries measure latency on the approximate path, not approximation error. Sketch persistence and merge across queries are intentionally out of scope for read_primitives — that capability is covered by the sketch category of write_primitives.

Query coverage

Query IDCapabilityCategory
approx_count_distinct_simpleHLL distinct count, single valueaggregation
approx_count_distinct_groupbyHLL distinct count, per groupaggregation
approx_quantile_groupbyT-Digest / KLL single quantilestatistical
approx_quantiles_arrayVector quantiles per groupstatistical
approx_top_k_lineitemApproximate top-Ktopn

approx_quantile_groupby is the renamed successor of the historical intrinsic_appx_median query, which used exact PERCENTILE_CONT despite the “appx” name.

Cross-engine function reference

The base SQL uses DuckDB-style names (APPROX_COUNT_DISTINCT, APPROX_QUANTILE, APPROX_TOP_K). sqlglot rewrites these for most target dialects automatically; explicit YAML variants exist only where sqlglot leaves the SQL non-functional.

Distinct count

EngineSyntaxSource
DuckDBAPPROX_COUNT_DISTINCT(x)base
SnowflakeAPPROX_COUNT_DISTINCT(x)sqlglot rewrite
BigQueryAPPROX_COUNT_DISTINCT(x)sqlglot rewrite
Databricksapprox_count_distinct(x)sqlglot rewrite
ClickHouseuniq(x)sqlglot rewrite
RedshiftAPPROXIMATE COUNT(DISTINCT x)sqlglot rewrite
DataFusionapprox_distinct(x)sqlglot rewrite

Single quantile

EngineSyntaxSource
DuckDBAPPROX_QUANTILE(x, 0.5)base
SnowflakeAPPROX_PERCENTILE(x, 0.5)sqlglot rewrite
BigQueryAPPROX_QUANTILES(x, 100)[OFFSET(50)]YAML variant
DatabricksPERCENTILE_APPROX(x, 0.5)sqlglot rewrite
ClickHousequantileTDigest(0.5)(x)YAML variant
DataFusionapprox_percentile_cont(x, 0.5)YAML variant
RedshiftAPPROXIMATE PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY x)YAML variant

The Redshift variant is hand-written and sent to Redshift as-is. Note: the per-node GROUP BY result-set cap applies; for grouping columns with cardinality near or above the cap, fall back to exact PERCENTILE_CONT.

Vector quantiles (one sketch eval per group)

EngineSyntaxSource
DuckDBAPPROX_QUANTILE(x, [0.25, 0.5, 0.75, 0.95]) → ARRAYbase
BigQuery[APPROX_QUANTILES(x, 100)[OFFSET(25)], …] (per-quantile slice)YAML variant
ClickHousequantilesTDigest(0.25, 0.5, 0.75, 0.95)(x)YAML variant
SnowflakeARRAY_CONSTRUCT(APPROX_PERCENTILE(x, 0.25), …) (no array form)YAML variant
DatabricksPERCENTILE_APPROX(x, ARRAY(0.25, 0.5, 0.75, 0.95))sqlglot rewrite
Redshift—(skipped)
DataFusion—(skipped)

Top-K

EngineSyntaxReturnsSource
DuckDBAPPROX_TOP_K(x, 5)ARRAY<value>base
SnowflakeAPPROX_TOP_K(x, 5)ARRAY<pair>sqlglot rewrite
BigQueryAPPROX_TOP_COUNT(x, 5)ARRAY<STRUCT<value, count>>sqlglot rewrite
Databricksapprox_top_k(x, 5)ARRAY<STRUCT<item, count>>sqlglot rewrite
ClickHousetopK(5)(x)ARRAY<value>YAML variant
Redshift——(skipped)
DataFusion——(skipped)

Top-K result shapes diverge: DuckDB / ClickHouse return an array of values; BigQuery, Snowflake, and Databricks return arrays of value+count pairs. The catalog’s result_contract.capability is set to approximate_top_k so the cross-dialect comparator skips strict value equality and validates capability + array type_class instead.

Verified platforms

The five one-shot approximate aggregates run on Snowflake and Databricks with benchbox run --platform <snowflake|databricks> --benchmark read_primitives --phases load,power. At small scale factors, warehouse warm-up dominates the measured latency, not the sketch evaluation.

ClickHouse Cloud, Firebolt, Starburst/Trino, MotherDuck, Redshift and BigQuery have not been verified for these queries.

DataFrame platform coverage

Three of the five approximate-aggregate queries run on the BenchBox DataFrame surface. The other two are PySpark-only at the DataFrame layer and stay in SKIP_FOR_DATAFRAME.

QueryPolarsPySparkDataFusionPandas / cuDFDask
approx_count_distinct_simpleapprox_n_unique (HLL)approx_count_distinct (HLL)approx_distinct (HLL)nunique (exact)nunique_approx (HLL)
approx_count_distinct_groupbyapprox_n_unique (HLL)approx_count_distinct (HLL)approx_distinct (HLL)nunique (exact)nunique (exact; no groupby HLL API in dask-expr)
approx_quantile_groupbyquantile(0.5) (exact)percentile_approx(col, 0.5) (KLL-equiv)approx_percentile_cont(col, 0.5) (T-Digest)median (exact)median (exact; no groupby T-Digest API in dask-expr)
approx_quantiles_array— (in SKIP_FOR_DATAFRAME)— (in SKIP_FOR_DATAFRAME)———
approx_top_k_lineitem— (in SKIP_FOR_DATAFRAME)— (in SKIP_FOR_DATAFRAME)———

Sketch vs exact-fallback interpretation

When a row above says (exact), the underlying engine has no sketch-backed surface for that aggregate at the DataFrame layer. The benchmark still runs and reports a number, but that number is the exact aggregate’s latency — it is not directly comparable to the sketch-backed engines on the same row, because exact aggregates pay cardinality-proportional cost while sketches pay roughly constant cost. Cross-engine comparisons in this category are most informative within the sketch-backed column group.

Pandas / cuDF have no native sketch surface at all. Dask exposes Series-level nunique_approx() (HLL), which BenchBox uses for the single-value distinct query. Current dask-expr does not expose matching groupby HLL or groupby T-Digest APIs; those groupby queries remain exact fallbacks rather than hand-rolled approximations.

Why two queries stay PySpark-only

  • approx_quantiles_array — only PySpark’s percentile_approx(col, array(...)) returns an array of quantiles from a single sketch evaluation. Polars and DataFusion expose only single-quantile aggregates; Dask has no grouped array-quantile sketch shape in dask-expr. Emulating the array form via a per-quantile loop on those engines defeats the latency measurement that the benchmark is supposed to report.
  • approx_top_k_lineitem — only PySpark 4.1+ ships a native approx_top_k accumulator. Polars’ top_k is largest-by-sort-key (different semantics — not frequency-based), and the other DataFrame engines have no frequency-based top-K sketch.

Single-query scope

These queries exercise only the aggregate latency path: one-shot approximate aggregates. They run in isolation against TPC-H tables with no shared state.

The differentiated capability — building sketches in one query, persisting them as binary columns, then merging them in a later query — does not fit read_primitives’ single-query execution model. That capability is exercised by the sketch category of write_primitives, which shares this doc’s cross-engine reference for the underlying sketch-family function names.