Skip to main content
ParadeDB can execute aggregate work using the columnar portion of the ParadeDB index, which can significantly accelerate performance compared to vanilla Postgres. There are two aggregate surfaces:
  • Native SQL aggregates for normal Postgres queries: COUNT, SUM, AVG, MIN, MAX, STDDEV, VARIANCE, BOOL_AND, BOOL_OR, ARRAY_AGG, STRING_AGG, GROUP BY, HAVING, and per-aggregate FILTER.
  • pdb.agg JSON aggregates for Elasticsearch-compatible bucket and metric aggregations, including facets, histograms, percentiles, cardinality, and top hits.

Native SQL Aggregates

Use standard SQL aggregate functions when you want ordinary Postgres-shaped results. ParadeDB can push these aggregates into the index when the query contains a ParadeDB predicate and the aggregate fields are in the ParadeDB index.
Grouped aggregates use normal GROUP BY syntax:
If a query does not otherwise need a full-text predicate, add pdb.all() to make the query eligible for aggregate pushdown:
See Limitations for the full list of supported aggregate shapes and fallback cases.

JSON Aggregates

The pdb.agg function accepts an Elasticsearch-compatible JSON aggregate query string. This is useful when you want bucketed or faceted responses in Elasticsearch-compatible JSON. For example, the following query counts the total number of results for a search query:
Expected Response
This query counts the number of results for every distinct group:
Expected Response

Multiple Aggregations

To compute multiple aggregations at once, simply include multiple pdb.agg functions in the target list:
Expected Response

Performance Optimization

By default, pdb.agg runs transaction visibility checks so that deleted or updated-away rows are not factored into the result set. This behavior is controlled by the visibility argument, which takes one of three modes. If your table is not frequently updated or you can tolerate an approximate result, the performance of aggregate queries can be improved by skipping these visibility checks. To do so, set visibility to 'raw'.
Skipping this check can improve query times by 2-4x in some cases, at the expense of correctness.

Thresholded Visibility

An unvacuumed dead tuple skews a small result visibly and a large one barely at all, so the tradeoff above is really a function of how many rows the query matches. visibility => 'threshold' makes that decision per query instead of hardcoding it in your application: visibility checks run when the estimated matching row count is below paradedb.visibility_threshold, and the aggregate reads raw index data otherwise.
paradedb.visibility_threshold defaults to 10000 and can be set at the system, database, session, or transaction level.
The estimate is made for the query as a whole, so a bucketed aggregation such as GROUP BY or terms resolves to one decision for every bucket. A query matching a million rows across fifty thousand buckets aggregates every bucket from raw index data, including the low-cardinality ones.
If a single query contains multiple pdb.agg calls, all of them must use the same visibility setting. Omitting the argument selects 'transaction', so an omitted argument alongside an explicit 'raw' is a conflict, not a default.
The solve_mvcc argument is deprecated in favor of visibility. Passing a boolean is still accepted, where true means 'transaction' and false means 'raw'.

JSON Fields

If metadata is a JSON field with key color, use metadata.color as the field name:
If a text or JSON field is used inside pdb.agg, it must use the literal or literal normalized tokenizer.