ParadeDB Predicate
In order for ParadeDB to push down an aggregate, a ParadeDB predicate must be present in the query.WHERE clause.
Date Grouping
GROUP BY DATE(created_at) and GROUP BY created_at::date support aggregate pushdown when created_at is a bare timestamp without time zone column that is columnar indexed. The usual aggregate pushdown requirements still apply, including a ParadeDB operator in the query.
The result matches Postgres for the full timestamp range, including infinity, -infinity and NULL. Date grouping can be combined with other grouping columns and per-aggregate FILTER clauses.
The following forms still fall back to native Postgres execution:
DATE(timestamptz_column), because the result depends on the sessionTimeZone.DATE(text_column::timestamp)and other non-bare timestamp expressions.- Other scalar grouping functions, such as
date_trunc(...)andlower(...).
Join Support
Aggregate pushdown works across joins as well as single tables. When every participating table has a ParadeDB index, ParadeDB computes the result directly from the index’s columnar storage, without scanning the underlying table rows. The custom aggregate scan is enabled by default; to turn it off, runSET paradedb.enable_aggregate_custom_scan TO off;.
Supported shapes
pdb.agg() over joins
A pdb.agg() field name must belong to exactly one indexed table in the join. When the same field name exists in several tables, qualify it with the table alias, as in "field": "p.category". The qualifier is also accepted when the planner reduces the join to that one table. Postgres cannot see the fields inside a spec, so an outer join whose table is read only there is removed as unused and the spec fails with an unknown field; reference such a table outside the spec, or use an inner join.
pdb.agg() over a NUMERIC field runs on this path as well, on a single table or over a join, while a missing value is not accepted for such a field. terms on an array field is also supported over joins; metric aggregations over array fields are not.
A few pdb.agg() shapes run on a single table but not over a join: metric aggregations over JSON sub-fields, such as {"sum": {"field": "metadata.qty"}}, terms on a JSON sub-field that holds numbers, min_doc_count: 0, which lists every term of the column, and a field indexed from a computed expression, such as (upper(name)::pdb.literal('alias=name_upper')). A field indexed through a plain tokenizer cast, such as (name::pdb.literal), works over a join like the column itself.
pdb.agg() has no native Postgres implementation, so it cannot fall back. A pdb.agg() over a join that uses an aggregation outside the list above, such as range, histogram, date_histogram, filter, composite, stats, percentiles, or top_hits, raises an error.
pdb.agg() and pdb.combine_agg() as window functions
The faceted query form pdb.agg(...) OVER () computes an aggregate across all matching rows alongside ORDER BY ... LIMIT results. In a GROUP BY query, use pdb.combine_agg(pdb.agg(...)) OVER () (alongside optional COUNT, SUM, AVG, MIN, or MAX OVER () window aggregates over group columns or aggregates).
Window aggregate pushdown requires an empty OVER () clause, and is not supported with HAVING, FETCH FIRST ... WITH TIES, or alongside per-group pdb.agg() in the same query. Over a join without GROUP BY, pdb.agg(...) OVER () and the COUNT, SUM, AVG, MIN, and MAX OVER () window aggregates accept a FILTER (WHERE ...) clause that compares indexed columnar fields or uses search operators such as |||. A filter the scan cannot evaluate sends a SQL window aggregate to Postgres, and makes pdb.agg() raise an error. In a GROUP BY query, pdb.combine_agg(pdb.agg(...)) OVER () and the window aggregates beside it do not accept a FILTER. With SELECT DISTINCT over a join, an entry can be a window aggregate or an expression that reads only window aggregates (such as pdb.agg(...) OVER () ->> 'value'), but not an expression that combines a window aggregate with another column. Ungrouped pdb.agg(...) OVER () additionally requires a LIMIT and default visibility.
Fallback to Postgres
For aggregates over joins, ParadeDB falls back to native Postgres execution when any of the following are true:- One or more tables in the join lacks a ParadeDB index
- The join has no equality join condition (e.g.
CROSS JOIN) - Join keys,
GROUP BYcolumns, or aggregate arguments are not indexed columns - The query uses
ROLLUP,CUBE,GROUPING SETS,LATERAL,DISTINCT ON, or window functions other than the pushed-downpdb.agg(...) OVER ()andpdb.combine_agg(pdb.agg(...)) OVER ()forms described above GROUP BYuses a scalar function likedate_trunc(...)orlower(...)(JSON sub-field access via->>is supported)- The aggregate argument or result is wrapped in an expression such as
COALESCE(SUM(...), 0)or a cast