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() as a window function over joins
The faceted query form pdb.agg(...) OVER () runs over a join as well. The document covers every row the join produces, while ORDER BY ... LIMIT picks the rows returned; each returned row carries the same document. The document can be used inside other target list expressions, such as pdb.agg(...) OVER () ->> 'value'. The spec follows the rules of pdb.agg() over joins above.
This path turns down the following shapes. Because pdb.agg() cannot fall back to Postgres, a query with one of them raises an error rather than running natively:
- No
LIMIT, or aLIMITorOFFSETwhose value is not known when the query is planned, such as a parameter under a generic plan - A
visibilityother than the default - A
FILTER (WHERE ...)clause on the window function PARTITION BY,ORDER BY, or a frame inside theOVERclauseSELECT DISTINCTwith the bare document in the target list, or with the document inside an expression that also reads a column, such as(pdb.agg(...) OVER () ->> 'value') || p.category. UnderDISTINCTthe document is only accepted inside an expression that reads no other column, such aspdb.agg(...) OVER () ->> 'value'
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 window functions (
OVER ...),ROLLUP,CUBE,GROUPING SETS,LATERAL, orDISTINCT ON. The faceted query formpdb.agg(...) OVER ()is pushed down, on a single table and over a join, as 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