-- This file and its contents are licensed under the Apache License 2.0. -- Please see the included NOTICE for copyright information and -- LICENSE-APACHE for a copy of the license. \set TEST_BASE_NAME agg_bookends SELECT format('include/%s_load.sql', :'TEST_BASE_NAME') as "TEST_LOAD_NAME", format('include/%s_query.sql', :'TEST_BASE_NAME') as "TEST_QUERY_NAME", format('%s/results/%s_results_optimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as "TEST_RESULTS_OPTIMIZED", format('%s/results/%s_results_unoptimized.out', :'TEST_OUTPUT_DIR', :'TEST_BASE_NAME') as "TEST_RESULTS_UNOPTIMIZED" \gset SELECT format('\! diff -u --label "Unoptimized result" --label "Optimized result" %s %s', :'TEST_RESULTS_UNOPTIMIZED', :'TEST_RESULTS_OPTIMIZED') as "DIFF_CMD" \gset \set PREFIX 'EXPLAIN (analyze, costs off, timing off, summary off)' \ir :TEST_LOAD_NAME -- This file and its contents are licensed under the Apache License 2.0. -- Please see the included NOTICE for copyright information and -- LICENSE-APACHE for a copy of the license. CREATE TABLE btest(time timestamp NOT NULL, time_alt timestamp, gp INTEGER, temp float, strid TEXT DEFAULT 'testing'); SELECT schema_name, table_name, created FROM create_hypertable('btest', 'time'); schema_name | table_name | created -------------+------------+--------- public | btest | t (1 row) INSERT INTO btest VALUES('2017-01-20T09:00:01', '2017-01-20T10:00:00', 1, 22.5); INSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:59', 1, 21.2); INSERT INTO btest VALUES('2017-01-20T09:00:47', '2017-01-20T09:00:58', 1, 25.1); INSERT INTO btest VALUES('2017-01-20T09:00:02', '2017-01-20T09:00:57', 2, 35.5); INSERT INTO btest VALUES('2017-01-20T09:00:21', '2017-01-20T09:00:56', 2, 30.2); --TOASTED; INSERT INTO btest VALUES('2017-01-20T09:00:43', '2017-01-20T09:01:55', 2, 20.1, repeat('xyz', 1000000) ); CREATE TABLE btest_numeric (time timestamp NOT NULL, quantity numeric); SELECT schema_name, table_name, created FROM create_hypertable('btest_numeric', 'time'); schema_name | table_name | created -------------+---------------+--------- public | btest_numeric | t (1 row) \ir :TEST_QUERY_NAME -- This file and its contents are licensed under the Apache License 2.0. -- Please see the included NOTICE for copyright information and -- LICENSE-APACHE for a copy of the license. -- canary for results diff -- this should be only output of results diff SELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting); setting | value ----------------------------------+------- timescaledb.enable_optimizations | on (1 row) :PREFIX SELECT time, gp, temp FROM btest ORDER BY time; QUERY PLAN ------------------------------------------------------------------------------------------------------------- Custom Scan (ChunkAppend) on btest (actual rows=6 loops=1) Order: btest."time" -> Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=6 loops=1) (3 rows) :PREFIX SELECT last(temp, time) FROM btest; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) (7 rows) :PREFIX SELECT first(temp, time) FROM btest; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" -> Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) (7 rows) :PREFIX SELECT last(temp, time_alt) FROM btest; QUERY PLAN ------------------------------------------------------------ Aggregate (actual rows=1 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) (2 rows) :PREFIX SELECT first(temp, time_alt) FROM btest; QUERY PLAN ------------------------------------------------------------ Aggregate (actual rows=1 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) (2 rows) :PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp; QUERY PLAN ------------------------------------------------------------------ Sort (actual rows=2 loops=1) Sort Key: _hyper_1_1_chunk.gp Sort Method: quicksort -> HashAggregate (actual rows=2 loops=1) Group Key: _hyper_1_1_chunk.gp Batches: 1 -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) (7 rows) :PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp; QUERY PLAN ------------------------------------------------------------------ Sort (actual rows=2 loops=1) Sort Key: _hyper_1_1_chunk.gp Sort Method: quicksort -> HashAggregate (actual rows=2 loops=1) Group Key: _hyper_1_1_chunk.gp Batches: 1 -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) (7 rows) --check whole row :PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp; QUERY PLAN ------------------------------------------------------------------ Sort (actual rows=2 loops=1) Sort Key: _hyper_1_1_chunk.gp Sort Method: quicksort -> HashAggregate (actual rows=2 loops=1) Group Key: _hyper_1_1_chunk.gp Batches: 1 -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) (7 rows) --check toasted col :PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp; QUERY PLAN ------------------------------------------------------------------ Sort (actual rows=2 loops=1) Sort Key: _hyper_1_1_chunk.gp Sort Method: quicksort -> HashAggregate (actual rows=2 loops=1) Group Key: _hyper_1_1_chunk.gp Batches: 1 -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) (7 rows) :PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp; QUERY PLAN ------------------------------------------------------------------ Sort (actual rows=2 loops=1) Sort Key: _hyper_1_1_chunk.gp Sort Method: quicksort -> HashAggregate (actual rows=2 loops=1) Group Key: _hyper_1_1_chunk.gp Batches: 1 -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) (7 rows) :PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp; QUERY PLAN ------------------------------------------------------------------ Sort (actual rows=2 loops=1) Sort Key: _hyper_1_1_chunk.gp Sort Method: quicksort -> HashAggregate (actual rows=2 loops=1) Group Key: _hyper_1_1_chunk.gp Batches: 1 -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) (7 rows) BEGIN; --check null value as last element INSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL); :PREFIX SELECT last(temp, time) FROM btest; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed) Index Cond: ("time" IS NOT NULL) (9 rows) --check non-null element "overrides" NULL because it comes after. INSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5); :PREFIX SELECT last(temp, time) FROM btest; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed) Index Cond: ("time" IS NOT NULL) (11 rows) --check null cmp element is skipped INSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3); :PREFIX SELECT last(temp, time_alt) FROM btest; QUERY PLAN ------------------------------------------------------------------ Aggregate (actual rows=1 loops=1) -> Append (actual rows=9 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) (5 rows) -- fist returns NULL value :PREFIX SELECT first(temp, time_alt) FROM btest; QUERY PLAN ------------------------------------------------------------------ Aggregate (actual rows=1 loops=1) -> Append (actual rows=9 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) (5 rows) -- test first return non NULL value INSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5); :PREFIX SELECT first(temp, time_alt) FROM btest; QUERY PLAN ------------------------------------------------------------------ Aggregate (actual rows=1 loops=1) -> Append (actual rows=10 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_4_chunk (actual rows=1 loops=1) (6 rows) --check non null cmp element insert after null cmp INSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3); :PREFIX SELECT last(temp, time_alt) FROM btest; QUERY PLAN ------------------------------------------------------------------ Aggregate (actual rows=1 loops=1) -> Append (actual rows=11 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_4_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_5_chunk (actual rows=1 loops=1) (7 rows) :PREFIX SELECT first(temp, time_alt) FROM btest; QUERY PLAN ------------------------------------------------------------------ Aggregate (actual rows=1 loops=1) -> Append (actual rows=11 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_4_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_5_chunk (actual rows=1 loops=1) (7 rows) --cmp nulls should be ignored and not present in groups :PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp; QUERY PLAN ------------------------------------------------------------------------ Sort (actual rows=2 loops=1) Sort Key: _hyper_1_1_chunk.gp Sort Method: quicksort -> HashAggregate (actual rows=2 loops=1) Group Key: _hyper_1_1_chunk.gp Batches: 1 -> Append (actual rows=11 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_4_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_5_chunk (actual rows=1 loops=1) (12 rows) --Previously, some bugs were found with NULLS and numeric types, so test that INSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL); :PREFIX SELECT last(quantity, time) FROM btest_numeric; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest_numeric (actual rows=1 loops=1) Order: btest_numeric."time" DESC -> Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) (7 rows) --check non-null element "overrides" NULL because it comes after. INSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5); :PREFIX SELECT last(quantity, time) FROM btest_numeric; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest_numeric (actual rows=1 loops=1) Order: btest_numeric."time" DESC -> Index Scan using _hyper_2_7_chunk_btest_numeric_time_idx on _hyper_2_7_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (never executed) Index Cond: ("time" IS NOT NULL) (9 rows) -- do index scan for last :PREFIX SELECT last(temp, time) FROM btest; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed) Index Cond: ("time" IS NOT NULL) (15 rows) -- do index scan for first :PREFIX SELECT first(temp, time) FROM btest; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" -> Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed) Index Cond: ("time" IS NOT NULL) (15 rows) -- can't do index scan when ordering on non-index column :PREFIX SELECT first(temp, time_alt) FROM btest; QUERY PLAN ------------------------------------------------------------------ Aggregate (actual rows=1 loops=1) -> Append (actual rows=11 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_4_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_5_chunk (actual rows=1 loops=1) (7 rows) -- do index scan for subquery :PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed) Index Cond: ("time" IS NOT NULL) (15 rows) -- can't do index scan when using group by :PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp; QUERY PLAN ------------------------------------------------------------------------ Sort (actual rows=2 loops=1) Sort Key: _hyper_1_1_chunk.gp Sort Method: quicksort -> HashAggregate (actual rows=2 loops=1) Group Key: _hyper_1_1_chunk.gp Batches: 1 -> Append (actual rows=11 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_4_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_5_chunk (actual rows=1 loops=1) (12 rows) -- do index scan when agg function is used in CTE subquery :PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed) Index Cond: ("time" IS NOT NULL) (15 rows) -- do index scan when using both FIRST and LAST aggregate functions :PREFIX SELECT first(temp, time), last(temp, time) FROM btest; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------- Result (actual rows=1 loops=1) InitPlan 1 (returns $1) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed) Index Cond: ("time" IS NOT NULL) InitPlan 2 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1 loops=1) Order: btest_1."time" -> Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk _hyper_1_4_chunk_1 (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed) Index Cond: ("time" IS NOT NULL) (29 rows) -- verify results when using both FIRST and LAST :PREFIX SELECT first(temp, time), last(temp, time) FROM btest; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------- Result (actual rows=1 loops=1) InitPlan 1 (returns $1) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed) Index Cond: ("time" IS NOT NULL) InitPlan 2 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1 loops=1) Order: btest_1."time" -> Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk _hyper_1_4_chunk_1 (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed) Index Cond: ("time" IS NOT NULL) (29 rows) -- do index scan when using WHERE :PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02'; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------- Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1 loops=1) Index Cond: (("time" IS NOT NULL) AND ("time" <= 'Fri Jan 20 09:00:02 2017'::timestamp without time zone)) -> Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed) Index Cond: (("time" IS NOT NULL) AND ("time" <= 'Fri Jan 20 09:00:02 2017'::timestamp without time zone)) (9 rows) -- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions) :PREFIX SELECT max(time), last(temp, time) FROM btest; QUERY PLAN ------------------------------------------------------------------ Aggregate (actual rows=1 loops=1) -> Append (actual rows=11 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_4_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_5_chunk (actual rows=1 loops=1) (7 rows) -- can't do index scan when using FIRST/LAST in ORDER BY :PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time); QUERY PLAN ------------------------------------------------------------------------ Sort (actual rows=1 loops=1) Sort Key: (last(_hyper_1_1_chunk.temp, _hyper_1_1_chunk."time")) Sort Method: quicksort -> Aggregate (actual rows=1 loops=1) -> Append (actual rows=11 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_4_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_5_chunk (actual rows=1 loops=1) (10 rows) -- do index scan :PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=0 loops=1) Index Cond: ("time" IS NOT NULL) Filter: (temp < '30'::double precision) Rows Removed by Filter: 1 -> Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=0 loops=1) Index Cond: ("time" IS NOT NULL) Filter: (temp < '30'::double precision) Rows Removed by Filter: 1 -> Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=0 loops=1) Index Cond: ("time" IS NOT NULL) Filter: (temp < '30'::double precision) Rows Removed by Filter: 2 -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) Filter: (temp < '30'::double precision) -> Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed) Index Cond: ("time" IS NOT NULL) Filter: (temp < '30'::double precision) (23 rows) -- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47'; -- do index scan :PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47'; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------- Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" -> Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1 loops=1) Index Cond: (("time" IS NOT NULL) AND ("time" >= 'Fri Jan 20 09:00:47 2017'::timestamp without time zone)) -> Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed) Index Cond: (("time" IS NOT NULL) AND ("time" >= 'Fri Jan 20 09:00:47 2017'::timestamp without time zone)) -> Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed) Index Cond: (("time" IS NOT NULL) AND ("time" >= 'Fri Jan 20 09:00:47 2017'::timestamp without time zone)) -> Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed) Index Cond: (("time" IS NOT NULL) AND ("time" >= 'Fri Jan 20 09:00:47 2017'::timestamp without time zone)) (13 rows) -- can't do index scan when using WINDOW function :PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest; QUERY PLAN ------------------------------------------------------------------------ WindowAgg (actual rows=11 loops=1) -> Sort (actual rows=11 loops=1) Sort Key: _hyper_1_1_chunk.gp Sort Method: quicksort -> Append (actual rows=11 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_4_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_5_chunk (actual rows=1 loops=1) (10 rows) -- test constants :PREFIX SELECT first(100, 100) FROM btest; QUERY PLAN -------------------------------------------------------------------------------- Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Result (actual rows=1 loops=1) -> Append (actual rows=1 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_2_chunk (never executed) -> Seq Scan on _hyper_1_3_chunk (never executed) -> Seq Scan on _hyper_1_4_chunk (never executed) -> Seq Scan on _hyper_1_5_chunk (never executed) (10 rows) -- create an index so we can test optimization CREATE INDEX btest_time_alt_idx ON btest(time_alt); :PREFIX SELECT last(temp, time_alt) FROM btest; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------- Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Merge Append (actual rows=1 loops=1) Sort Key: _hyper_1_1_chunk.time_alt DESC -> Index Scan Backward using _hyper_1_1_chunk_btest_time_alt_idx on _hyper_1_1_chunk (actual rows=1 loops=1) Index Cond: (time_alt IS NOT NULL) -> Index Scan Backward using _hyper_1_2_chunk_btest_time_alt_idx on _hyper_1_2_chunk (actual rows=1 loops=1) Index Cond: (time_alt IS NOT NULL) -> Index Scan Backward using _hyper_1_3_chunk_btest_time_alt_idx on _hyper_1_3_chunk (actual rows=1 loops=1) Index Cond: (time_alt IS NOT NULL) -> Index Scan Backward using _hyper_1_4_chunk_btest_time_alt_idx on _hyper_1_4_chunk (actual rows=1 loops=1) Index Cond: (time_alt IS NOT NULL) -> Index Scan Backward using _hyper_1_5_chunk_btest_time_alt_idx on _hyper_1_5_chunk (actual rows=1 loops=1) Index Cond: (time_alt IS NOT NULL) (15 rows) --test nested FIRST/LAST - should optimize :PREFIX SELECT abs(last(temp, time)) FROM btest; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Result (actual rows=1 loops=1) InitPlan 1 (returns $0) -> Limit (actual rows=1 loops=1) -> Custom Scan (ChunkAppend) on btest (actual rows=1 loops=1) Order: btest."time" DESC -> Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1 loops=1) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed) Index Cond: ("time" IS NOT NULL) -> Index Scan using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (never executed) Index Cond: ("time" IS NOT NULL) (15 rows) -- test nested FIRST/LAST in ORDER BY - no optimization possible :PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time)); QUERY PLAN ------------------------------------------------------------------------- Sort (actual rows=1 loops=1) Sort Key: (abs(last(_hyper_1_1_chunk.temp, _hyper_1_1_chunk."time"))) Sort Method: quicksort -> Aggregate (actual rows=1 loops=1) -> Append (actual rows=11 loops=1) -> Seq Scan on _hyper_1_1_chunk (actual rows=6 loops=1) -> Seq Scan on _hyper_1_2_chunk (actual rows=2 loops=1) -> Seq Scan on _hyper_1_3_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_4_chunk (actual rows=1 loops=1) -> Seq Scan on _hyper_1_5_chunk (actual rows=1 loops=1) (10 rows) ROLLBACK; -- we want test results as part of the output too to make sure we produce correct output \set PREFIX '' \ir :TEST_QUERY_NAME -- This file and its contents are licensed under the Apache License 2.0. -- Please see the included NOTICE for copyright information and -- LICENSE-APACHE for a copy of the license. -- canary for results diff -- this should be only output of results diff SELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting); setting | value ----------------------------------+------- timescaledb.enable_optimizations | on (1 row) :PREFIX SELECT time, gp, temp FROM btest ORDER BY time; time | gp | temp --------------------------+----+------ Fri Jan 20 09:00:01 2017 | 1 | 22.5 Fri Jan 20 09:00:02 2017 | 2 | 35.5 Fri Jan 20 09:00:21 2017 | 1 | 21.2 Fri Jan 20 09:00:21 2017 | 2 | 30.2 Fri Jan 20 09:00:43 2017 | 2 | 20.1 Fri Jan 20 09:00:47 2017 | 1 | 25.1 (6 rows) :PREFIX SELECT last(temp, time) FROM btest; last ------ 25.1 (1 row) :PREFIX SELECT first(temp, time) FROM btest; first ------- 22.5 (1 row) :PREFIX SELECT last(temp, time_alt) FROM btest; last ------ 22.5 (1 row) :PREFIX SELECT first(temp, time_alt) FROM btest; first ------- 30.2 (1 row) :PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp; gp | last ----+------ 1 | 25.1 2 | 20.1 (2 rows) :PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp; gp | first ----+------- 1 | 22.5 2 | 35.5 (2 rows) --check whole row :PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp; gp | first ----+------------------------------------------------------------------------ 1 | ("Fri Jan 20 09:00:01 2017","Fri Jan 20 10:00:00 2017",1,22.5,testing) 2 | ("Fri Jan 20 09:00:02 2017","Fri Jan 20 09:00:57 2017",2,35.5,testing) (2 rows) --check toasted col :PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp; gp | left ----+------------ 1 | testing 2 | xyzxyzxyzx (2 rows) :PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp; gp | last ----+------ 1 | 22.5 2 | 20.1 (2 rows) :PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp; gp | last ----+--------- 1 | testing 2 | testing (2 rows) BEGIN; --check null value as last element INSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL); :PREFIX SELECT last(temp, time) FROM btest; last ------ (1 row) --check non-null element "overrides" NULL because it comes after. INSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5); :PREFIX SELECT last(temp, time) FROM btest; last ------ 30.5 (1 row) --check null cmp element is skipped INSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3); :PREFIX SELECT last(temp, time_alt) FROM btest; last ------ 30.5 (1 row) -- fist returns NULL value :PREFIX SELECT first(temp, time_alt) FROM btest; first ------- (1 row) -- test first return non NULL value INSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5); :PREFIX SELECT first(temp, time_alt) FROM btest; first ------- 36.5 (1 row) --check non null cmp element insert after null cmp INSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3); :PREFIX SELECT last(temp, time_alt) FROM btest; last ------ 35.3 (1 row) :PREFIX SELECT first(temp, time_alt) FROM btest; first ------- 36.5 (1 row) --cmp nulls should be ignored and not present in groups :PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp; gp | last ----+------ 1 | 22.5 2 | 35.3 (2 rows) --Previously, some bugs were found with NULLS and numeric types, so test that INSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL); :PREFIX SELECT last(quantity, time) FROM btest_numeric; last ------ (1 row) --check non-null element "overrides" NULL because it comes after. INSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5); :PREFIX SELECT last(quantity, time) FROM btest_numeric; last ------ 30.5 (1 row) -- do index scan for last :PREFIX SELECT last(temp, time) FROM btest; last ------ 35.3 (1 row) -- do index scan for first :PREFIX SELECT first(temp, time) FROM btest; first ------- 36.5 (1 row) -- can't do index scan when ordering on non-index column :PREFIX SELECT first(temp, time_alt) FROM btest; first ------- 36.5 (1 row) -- do index scan for subquery :PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last; last ------ 35.3 (1 row) -- can't do index scan when using group by :PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp; last ------ 25.1 35.3 (2 rows) -- do index scan when agg function is used in CTE subquery :PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp; last ------ 35.3 (1 row) -- do index scan when using both FIRST and LAST aggregate functions :PREFIX SELECT first(temp, time), last(temp, time) FROM btest; first | last -------+------ 36.5 | 35.3 (1 row) -- verify results when using both FIRST and LAST :PREFIX SELECT first(temp, time), last(temp, time) FROM btest; first | last -------+------ 36.5 | 35.3 (1 row) -- do index scan when using WHERE :PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02'; last ------ 35.5 (1 row) -- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions) :PREFIX SELECT max(time), last(temp, time) FROM btest; max | last --------------------------+------ Mon Jan 20 09:00:43 2020 | 35.3 (1 row) -- can't do index scan when using FIRST/LAST in ORDER BY :PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time); last ------ 35.3 (1 row) -- do index scan :PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30; last ------ 25.1 (1 row) -- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47'; -- do index scan :PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47'; first ------- 25.1 (1 row) -- can't do index scan when using WINDOW function :PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest; gp | last ----+------ 1 | 25.1 1 | 25.1 1 | 25.1 2 | 35.3 2 | 35.3 2 | 35.3 2 | 35.3 2 | 35.3 2 | 35.3 2 | 35.3 2 | 35.3 (11 rows) -- test constants :PREFIX SELECT first(100, 100) FROM btest; first ------- 100 (1 row) -- create an index so we can test optimization CREATE INDEX btest_time_alt_idx ON btest(time_alt); :PREFIX SELECT last(temp, time_alt) FROM btest; last ------ 35.3 (1 row) --test nested FIRST/LAST - should optimize :PREFIX SELECT abs(last(temp, time)) FROM btest; abs ------ 35.3 (1 row) -- test nested FIRST/LAST in ORDER BY - no optimization possible :PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time)); abs ------ 35.3 (1 row) ROLLBACK; -- diff results with optimizations disabled and enabled \o :TEST_RESULTS_UNOPTIMIZED SET timescaledb.enable_optimizations TO false; \ir :TEST_QUERY_NAME -- This file and its contents are licensed under the Apache License 2.0. -- Please see the included NOTICE for copyright information and -- LICENSE-APACHE for a copy of the license. -- canary for results diff -- this should be only output of results diff SELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting); :PREFIX SELECT time, gp, temp FROM btest ORDER BY time; :PREFIX SELECT last(temp, time) FROM btest; :PREFIX SELECT first(temp, time) FROM btest; :PREFIX SELECT last(temp, time_alt) FROM btest; :PREFIX SELECT first(temp, time_alt) FROM btest; :PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp; :PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp; --check whole row :PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp; --check toasted col :PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp; :PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp; :PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp; BEGIN; --check null value as last element INSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL); :PREFIX SELECT last(temp, time) FROM btest; --check non-null element "overrides" NULL because it comes after. INSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5); :PREFIX SELECT last(temp, time) FROM btest; --check null cmp element is skipped INSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3); :PREFIX SELECT last(temp, time_alt) FROM btest; -- fist returns NULL value :PREFIX SELECT first(temp, time_alt) FROM btest; -- test first return non NULL value INSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5); :PREFIX SELECT first(temp, time_alt) FROM btest; --check non null cmp element insert after null cmp INSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3); :PREFIX SELECT last(temp, time_alt) FROM btest; :PREFIX SELECT first(temp, time_alt) FROM btest; --cmp nulls should be ignored and not present in groups :PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp; --Previously, some bugs were found with NULLS and numeric types, so test that INSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL); :PREFIX SELECT last(quantity, time) FROM btest_numeric; --check non-null element "overrides" NULL because it comes after. INSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5); :PREFIX SELECT last(quantity, time) FROM btest_numeric; -- do index scan for last :PREFIX SELECT last(temp, time) FROM btest; -- do index scan for first :PREFIX SELECT first(temp, time) FROM btest; -- can't do index scan when ordering on non-index column :PREFIX SELECT first(temp, time_alt) FROM btest; -- do index scan for subquery :PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last; -- can't do index scan when using group by :PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp; -- do index scan when agg function is used in CTE subquery :PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp; -- do index scan when using both FIRST and LAST aggregate functions :PREFIX SELECT first(temp, time), last(temp, time) FROM btest; -- verify results when using both FIRST and LAST :PREFIX SELECT first(temp, time), last(temp, time) FROM btest; -- do index scan when using WHERE :PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02'; -- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions) :PREFIX SELECT max(time), last(temp, time) FROM btest; -- can't do index scan when using FIRST/LAST in ORDER BY :PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time); -- do index scan :PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30; -- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47'; -- do index scan :PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47'; -- can't do index scan when using WINDOW function :PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest; -- test constants :PREFIX SELECT first(100, 100) FROM btest; -- create an index so we can test optimization CREATE INDEX btest_time_alt_idx ON btest(time_alt); :PREFIX SELECT last(temp, time_alt) FROM btest; --test nested FIRST/LAST - should optimize :PREFIX SELECT abs(last(temp, time)) FROM btest; -- test nested FIRST/LAST in ORDER BY - no optimization possible :PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time)); ROLLBACK; \o \o :TEST_RESULTS_OPTIMIZED SET timescaledb.enable_optimizations TO true; \ir :TEST_QUERY_NAME -- This file and its contents are licensed under the Apache License 2.0. -- Please see the included NOTICE for copyright information and -- LICENSE-APACHE for a copy of the license. -- canary for results diff -- this should be only output of results diff SELECT setting, current_setting(setting) AS value from (VALUES ('timescaledb.enable_optimizations')) v(setting); :PREFIX SELECT time, gp, temp FROM btest ORDER BY time; :PREFIX SELECT last(temp, time) FROM btest; :PREFIX SELECT first(temp, time) FROM btest; :PREFIX SELECT last(temp, time_alt) FROM btest; :PREFIX SELECT first(temp, time_alt) FROM btest; :PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp; :PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp; --check whole row :PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp; --check toasted col :PREFIX SELECT gp, left(last(strid, time), 10) FROM btest GROUP BY gp ORDER BY gp; :PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp; :PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp; BEGIN; --check null value as last element INSERT INTO btest VALUES('2018-01-20T09:00:43', '2017-01-20T09:00:55', 2, NULL); :PREFIX SELECT last(temp, time) FROM btest; --check non-null element "overrides" NULL because it comes after. INSERT INTO btest VALUES('2019-01-20T09:00:43', '2018-01-20T09:00:55', 2, 30.5); :PREFIX SELECT last(temp, time) FROM btest; --check null cmp element is skipped INSERT INTO btest VALUES('2018-01-20T09:00:43', NULL, 2, 32.3); :PREFIX SELECT last(temp, time_alt) FROM btest; -- fist returns NULL value :PREFIX SELECT first(temp, time_alt) FROM btest; -- test first return non NULL value INSERT INTO btest VALUES('2016-01-20T09:00:00', '2016-01-20T09:00:00', 2, 36.5); :PREFIX SELECT first(temp, time_alt) FROM btest; --check non null cmp element insert after null cmp INSERT INTO btest VALUES('2020-01-20T09:00:43', '2020-01-20T09:00:43', 2, 35.3); :PREFIX SELECT last(temp, time_alt) FROM btest; :PREFIX SELECT first(temp, time_alt) FROM btest; --cmp nulls should be ignored and not present in groups :PREFIX SELECT gp, last(temp, time_alt) FROM btest GROUP BY gp ORDER BY gp; --Previously, some bugs were found with NULLS and numeric types, so test that INSERT INTO btest_numeric VALUES ('2019-01-20T09:00:43', NULL); :PREFIX SELECT last(quantity, time) FROM btest_numeric; --check non-null element "overrides" NULL because it comes after. INSERT INTO btest_numeric VALUES('2020-01-20T09:00:43', 30.5); :PREFIX SELECT last(quantity, time) FROM btest_numeric; -- do index scan for last :PREFIX SELECT last(temp, time) FROM btest; -- do index scan for first :PREFIX SELECT first(temp, time) FROM btest; -- can't do index scan when ordering on non-index column :PREFIX SELECT first(temp, time_alt) FROM btest; -- do index scan for subquery :PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last; -- can't do index scan when using group by :PREFIX SELECT last(temp, time) FROM btest GROUP BY gp ORDER BY gp; -- do index scan when agg function is used in CTE subquery :PREFIX WITH last_temp AS (SELECT last(temp, time) FROM btest) SELECT * from last_temp; -- do index scan when using both FIRST and LAST aggregate functions :PREFIX SELECT first(temp, time), last(temp, time) FROM btest; -- verify results when using both FIRST and LAST :PREFIX SELECT first(temp, time), last(temp, time) FROM btest; -- do index scan when using WHERE :PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02'; -- can't do index scan for MAX and LAST combined (MinMax optimization fails when having different aggregate functions) :PREFIX SELECT max(time), last(temp, time) FROM btest; -- can't do index scan when using FIRST/LAST in ORDER BY :PREFIX SELECT last(temp, time) FROM btest ORDER BY last(temp, time); -- do index scan :PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30; -- SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47'; -- do index scan :PREFIX SELECT first(temp, time) FROM btest WHERE time >= '2017-01-20 09:00:47'; -- can't do index scan when using WINDOW function :PREFIX SELECT gp, last(temp, time) OVER (PARTITION BY gp) AS last FROM btest; -- test constants :PREFIX SELECT first(100, 100) FROM btest; -- create an index so we can test optimization CREATE INDEX btest_time_alt_idx ON btest(time_alt); :PREFIX SELECT last(temp, time_alt) FROM btest; --test nested FIRST/LAST - should optimize :PREFIX SELECT abs(last(temp, time)) FROM btest; -- test nested FIRST/LAST in ORDER BY - no optimization possible :PREFIX SELECT abs(last(temp, time)) FROM btest ORDER BY abs(last(temp,time)); ROLLBACK; \o :DIFF_CMD --- Unoptimized result +++ Optimized result @@ -1,6 +1,6 @@ setting | value ----------------------------------+------- - timescaledb.enable_optimizations | off + timescaledb.enable_optimizations | on (1 row) time | gp | temp