add batch remove batch split batch comment selection show hidden batches hide batch highlight batch
db<>fiddle
donate feedback about
By using db<>fiddle, you agree to license everything you submit by Creative Commons CC0.
Help with an interesting Postgres question: Why isn't an Index Only Scan used on a partition accessed via the parent table?.
CREATE TABLE foo (
id integer NOT NULL
, ts timestamp NOT NULL
, count integer DEFAULT 0
, PRIMARY KEY (ts, id) -- better!
)
PARTITION BY RANGE (ts);

CREATE TABLE foo_p1 PARTITION OF foo FOR VALUES FROM ('2023-01-01') TO ('2023-01-02');
CREATE TABLE foo_p2 PARTITION OF foo FOR VALUES FROM ('2023-01-02') TO ('2023-01-03');
CREATE TABLE foo_p3 PARTITION OF foo FOR VALUES FROM ('2023-01-03') TO ('2023-01-04');
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
SHOW enable_partition_pruning;
SET enable_seqscan = off; -- force idx scan for minimal table
enable_partition_pruning
on
SHOW
SET
-- partition pruning done during query planning
-- already visible with EXPLAIN - without executing the query

EXPLAIN -- !!!
SELECT sum(foo.count) AS total,
date_trunc('day', foo.ts) AS day_date
FROM foo
-- WHERE foo.date BETWEEN '2023-01-01' AND '2023-01-02'
WHERE foo.ts >= '2023-01-01'
AND foo.ts < '2023-01-02' -- ! exclude upper bound
GROUP BY day_date;
QUERY PLAN
HashAggregate (cost=14.88..14.99 rows=9 width=16)
  Group Key: date_trunc('day'::text, foo.ts)
  -> Bitmap Heap Scan on foo_p1 foo (cost=4.24..14.83 rows=9 width=12)
        Recheck Cond: ((ts >= '2023-01-01 00:00:00'::timestamp without time zone) AND (ts < '2023-01-02 00:00:00'::timestamp without time zone))
        -> Bitmap Index Scan on foo_p1_pkey (cost=0.00..4.24 rows=9 width=0)
              Index Cond: ((ts >= '2023-01-01 00:00:00'::timestamp without time zone) AND (ts < '2023-01-02 00:00:00'::timestamp without time zone))
EXPLAIN
-- partition pruning done during execution
-- only visible with EXPLAIN (ANALYZE) - actually executing the query

EXPLAIN (ANALYZE) -- !!!
WITH times AS ( -- cheaper with 1x generate_series() !
SELECT ts AS ts1, ts + interval '4 hours' AS ts2
FROM generate_series(timestamp '2023-01-01 12:00', timestamp '2023-01-02 16:00', '1 hour') ts
)
SELECT t.ts1, sum(f.count) AS total
FROM times t
JOIN foo f ON f.ts >= t.ts1 AND f.ts < t.ts2 -- ! exclude upper bound
GROUP BY t.ts1;
QUERY PLAN
HashAggregate (cost=25320.34..25322.34 rows=200 width=16) (actual time=0.119..0.120 rows=0 loops=1)
  Group Key: ts.ts
  Batches: 1 Memory Usage: 40kB
  -> Nested Loop (cost=0.16..22237.00 rows=616667 width=12) (actual time=0.116..0.117 rows=0 loops=1)
        -> Function Scan on generate_series ts (cost=0.00..10.00 rows=1000 width=8) (actual time=0.020..0.024 rows=29 loops=1)
        -> Append (cost=0.15..16.05 rows=618 width=12) (actual time=0.003..0.003 rows=0 loops=29)
              -> Index Scan using foo_p1_pkey on foo_p1 f_1 (cost=0.15..4.32 rows=206 width=12) (actual time=0.002..0.002 rows=0 loops=12)
                    Index Cond: ((ts >= ts.ts) AND (ts < (ts.ts + '04:00:00'::interval)))
              -> Index Scan using foo_p2_pkey on foo_p2 f_2 (cost=0.15..4.32 rows=206 width=12) (actual time=0.001..0.001 rows=0 loops=20)
                    Index Cond: ((ts >= ts.ts) AND (ts < (ts.ts + '04:00:00'::interval)))
              -> Index Scan using foo_p3_pkey on foo_p3 f_3 (cost=0.15..4.32 rows=206 width=12) (never executed)
                    Index Cond: ((ts >= ts.ts) AND (ts < (ts.ts + '04:00:00'::interval)))
Planning Time: 0.821 ms
Execution Time: 0.353 ms
EXPLAIN