Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1qatKj-00Acxh-EH for pgsql-performance@arkaria.postgresql.org; Tue, 29 Aug 2023 07:40:18 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1qatKi-004cRH-Am for pgsql-performance@arkaria.postgresql.org; Tue, 29 Aug 2023 07:40:16 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1qatKh-004cQ2-J9 for pgsql-performance@lists.postgresql.org; Tue, 29 Aug 2023 07:40:15 +0000 Received: from out-252.mta1.migadu.com ([95.215.58.252]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1qatKd-001YvX-Ek for pgsql-performance@postgresql.org; Tue, 29 Aug 2023 07:40:14 +0000 Date: Tue, 29 Aug 2023 09:40:06 +0200 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=philpep.org; s=key1; t=1693294808; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type; bh=vClY/AGnUpVbzjjSMWqZSp6dRTQGOhALSPsnVIXycPk=; b=chKfwJxxSGk+LyJKS2D2WqolBOoeza8VzEIOIvjEdNLshdEvHK1WHixmEKcqpeIsPUacKv iRgU5tJfcw08wrO7V2vPyd00ytnnW5B1S3z5eFjS6iRaGQHQNulAqWe3mxM9t25zxuQLmV zGR3q7oj/w6LBC65RVyzIVxD4IjnOyuyR8e08jVmbQB2WQY1qwVbpldddQxH6R32cMh8cj I1Ns3G93cyVKRueLbQ8iT952BxEIwiIFiW4lNRx9Sx2FP12ocj+B1Xrxyv/tFmrzAkoYJC dWRp/0BbAxkCIUvlSIkb3QlSyLZCaE+h90DHePTMw8AJNC4Pp7gum25YYkLtKw== X-Report-Abuse: Please report any abuse attempt to abuse@migadu.com and include these headers. From: Philippe Pepiot To: pgsql-performance@postgresql.org Subject: Range partitioning query performance with date_trunc (vs timescaledb) Message-ID: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline X-Migadu-Flow: FLOW_OUT List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi, I'm trying to implement some range partitioning on timeseries data. But it looks some queries involving date_trunc() doesn't make use of partitioning. BEGIN; CREATE TABLE test ( time TIMESTAMP WITHOUT TIME ZONE NOT NULL, value FLOAT NOT NULL ) PARTITION BY RANGE (time); CREATE INDEX test_time_idx ON test(time DESC); CREATE TABLE test_y2010 PARTITION OF test FOR VALUES FROM ('2020-01-01') TO ('2021-01-01'); CREATE TABLE test_y2011 PARTITION OF test FOR VALUES FROM ('2021-01-01') TO ('2022-01-01'); CREATE VIEW vtest AS SELECT DATE_TRUNC('year', time) AS time, SUM(value) AS value FROM test GROUP BY 1; EXPLAIN (COSTS OFF) SELECT * FROM vtest WHERE time >= TIMESTAMP '2021-01-01'; ROLLBACK; The plan query all partitions: HashAggregate Group Key: (date_trunc('year'::text, test."time")) -> Append -> Seq Scan on test_y2010 test_1 Filter: (date_trunc('year'::text, "time") >= '2021-01-01 00:00:00'::timestamp without time zone) -> Seq Scan on test_y2011 test_2 Filter: (date_trunc('year'::text, "time") >= '2021-01-01 00:00:00'::timestamp without time zone) The view is there so show the use case, but we get almost similar plan with SELECT * FROM test WHERE DATE_TRUNC('year', time) >= TIMESTAMP '2021-01-01'; I tested a variation with timescaledb which seem using trigger based partitioning: BEGIN; CREATE EXTENSION IF NOT EXISTS timescaledb; CREATE TABLE test ( time TIMESTAMP WITHOUT TIME ZONE NOT NULL, value FLOAT NOT NULL ); SELECT create_hypertable('test', 'time', chunk_time_interval => INTERVAL '1 year'); CREATE VIEW vtest AS SELECT time_bucket('1 year', time) AS time, SUM(value) AS value FROM test GROUP BY 1; -- insert some data as partitions are created on the fly INSERT INTO test VALUES (TIMESTAMP '2020-01-15', 1.0), (TIMESTAMP '2021-12-15', 2.0); \d+ test EXPLAIN (COSTS OFF) SELECT * FROM vtest WHERE time >= TIMESTAMP '2021-01-01'; ROLLBACK; The plan query a single partition: GroupAggregate Group Key: (time_bucket('1 year'::interval, _hyper_1_2_chunk."time")) -> Result -> Index Scan Backward using _hyper_1_2_chunk_test_time_idx on _hyper_1_2_chunk Index Cond: ("time" >= '2021-01-01 00:00:00'::timestamp without time zone) Filter: (time_bucket('1 year'::interval, "time") >= '2021-01-01 00:00:00'::timestamp without time zone) Note single partition query only works with time_bucket(), not with date_trunc(), I guess there is some magic regarding this in time_bucket() implementation. I wonder if there is a way with a reasonable amount of SQL code to achieve this with vanilla postgres ? Maybe by taking assumption that DATE_TRUNC(..., time) <= time ? Thanks!