Skip to main content

ParadeDB Predicate

In order for ParadeDB to push down an aggregate, a ParadeDB predicate must be present in the query.
If your query does not contain a ParadeDB operator, a way to “force” aggregate pushdown is to append the all query to the query’s 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 session TimeZone.
  • DATE(text_column::timestamp) and other non-bare timestamp expressions.
  • Other scalar grouping functions, such as date_trunc(...) and lower(...).

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, run SET 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, and min_doc_count: 0, which lists every term of the column. 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.

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 BY columns, or aggregate arguments are not indexed columns
  • The query uses window functions (OVER ...), ROLLUP, CUBE, GROUPING SETS, LATERAL, or DISTINCT ON. The single-table faceted query form pdb.agg(...) OVER () is pushed down, but not across joins
  • GROUP BY uses a scalar function like date_trunc(...) or lower(...) (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
When a fallback happens, the query still runs correctly through Postgres’ native planner. ParadeDB simply does not accelerate it.