$ lexprog.com

// notes from an old coder -- php, databases, and the occasional rant

[May 01, 2025] PostgreSQL

PostgreSQL Conditional Aggregation

PostgreSQL Conditional Aggregation

────────────────────────────────────────────────────────

PostgreSQL Conditional Aggregation

Tip: FILTER Clause

SELECT
    count(*) FILTER (WHERE status = 'published') as published,
    count(*) FILTER (WHERE status = 'draft') as draft,
    count(*) as total
FROM posts;

Gotcha: FILTER vs CASE

count(CASE WHEN status = 'published' THEN 1 END)

FILTER is cleaner and often faster.

Tip: Conditional SUM

SELECT
    sum(amount) FILTER (WHERE type = 'income') as income,
    sum(amount) FILTER (WHERE type = 'expense') as expense
FROM transactions;

Gotcha: Multiple Aggregates

One query can compute many conditional aggregates. No need for multiple queries.

Tip: FILTER with AVG

SELECT avg(rating) FILTER (WHERE rating > 0) FROM reviews;

Excludes zero ratings from the average.

Gotcha: GROUP BY with FILTER

SELECT category_id,
    count(*) FILTER (WHERE published = true) as published_count
FROM posts GROUP BY category_id;

Tip: EXPLAIN (ANALYZE, BUFFERS) Is Your Best Friend

For query debugging, always use EXPLAIN (ANALYZE, BUFFERS) instead of plain EXPLAIN. The BUFFERS option shows hit/miss rates for every node, revealing whether your indexes are actually in memory.

Tip: Partial Indexes Are Underutilized

CREATE INDEX ON orders (status) WHERE status = 'pending' creates a tiny index that covers only the rows your query needs. It's faster to scan and cheaper to maintain than a full-column index.

Gotcha: NULL Sorting Is Non-Obvious

By default, NULLs sort AFTER non-null values in ascending order. ORDER BY col DESC puts NULLs FIRST. Use NULLS LAST or NULLS FIRST to be explicit.

Senior Insight

PostgreSQL's FILTER (WHERE ...) clause for aggregate functions is more readable and often faster than CASE-based conditional aggregation. COUNT(*) FILTER (WHERE status = 'active') is clearer than COUNT(CASE WHEN status = 'active' THEN 1 END). I've migrated all reporting queries to use FILTER syntax — it's standard SQL and PostgreSQL 9.4+ optimizes it better than CASE expressions in aggregates.

Source: pganalyze Blog (https://pganalyze.com/blog), PostgreSQL Docs (https://www.postgresql.org/docs/current/), Crunchy Data Blog (https://www.crunchydata.com/blog)

────────────────────────────────────────────────────────
<-- back to posts