-- 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, buffers off, 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');
psql:include/agg_bookends_load.sql:6: WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
psql:include/agg_bookends_load.sql:6: WARNING:  column type "timestamp without time zone" used for "time_alt" does not follow best practices
 schema_name | table_name | created 
-------------+------------+---------
 public      | btest      | t

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');
psql:include/agg_bookends_load.sql:16: WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
 schema_name |  table_name   | created 
-------------+---------------+---------
 public      | btest_numeric | t

\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

:PREFIX SELECT time, gp, temp FROM btest ORDER BY time;
--- QUERY PLAN ---
 Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=6.00 loops=1)

:PREFIX SELECT last(temp, time) FROM btest;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)
                 Index Cond: ("time" IS NOT NULL)

:PREFIX SELECT first(temp, time) FROM btest;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)

:PREFIX SELECT last(temp, time_alt) FROM btest;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)

:PREFIX SELECT first(temp, time_alt) FROM btest;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)

:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;
--- QUERY PLAN ---
 Sort (actual rows=2.00 loops=1)
   Sort Key: _hyper_1_1_chunk.gp
   Sort Method: quicksort 
   ->  HashAggregate (actual rows=2.00 loops=1)
         Group Key: _hyper_1_1_chunk.gp
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)

:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;
--- QUERY PLAN ---
 Sort (actual rows=2.00 loops=1)
   Sort Key: _hyper_1_1_chunk.gp
   Sort Method: quicksort 
   ->  HashAggregate (actual rows=2.00 loops=1)
         Group Key: _hyper_1_1_chunk.gp
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)

--check whole row
:PREFIX SELECT gp, first(btest, time) FROM btest GROUP BY gp ORDER BY gp;
--- QUERY PLAN ---
 Sort (actual rows=2.00 loops=1)
   Sort Key: _hyper_1_1_chunk.gp
   Sort Method: quicksort 
   ->  HashAggregate (actual rows=2.00 loops=1)
         Group Key: _hyper_1_1_chunk.gp
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)

--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.00 loops=1)
   Sort Key: _hyper_1_1_chunk.gp
   Sort Method: quicksort 
   ->  HashAggregate (actual rows=2.00 loops=1)
         Group Key: _hyper_1_1_chunk.gp
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)

:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;
--- QUERY PLAN ---
 Sort (actual rows=2.00 loops=1)
   Sort Key: _hyper_1_1_chunk.gp
   Sort Method: quicksort 
   ->  HashAggregate (actual rows=2.00 loops=1)
         Group Key: _hyper_1_1_chunk.gp
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)

:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;
--- QUERY PLAN ---
 Sort (actual rows=2.00 loops=1)
   Sort Key: _hyper_1_1_chunk.gp
   Sort Method: quicksort 
   ->  HashAggregate (actual rows=2.00 loops=1)
         Group Key: _hyper_1_1_chunk.gp
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)

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.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time" DESC
                 ->  Index Scan using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (actual rows=1.00 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)

--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.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time" DESC
                 ->  Index Scan using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (actual rows=1.00 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)

--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.00 loops=1)
   ->  Append (actual rows=9.00 loops=1)
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)

-- fist returns NULL value
:PREFIX SELECT first(temp, time_alt) FROM btest;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Append (actual rows=9.00 loops=1)
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)

-- 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.00 loops=1)
   ->  Append (actual rows=10.00 loops=1)
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)
         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)

--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.00 loops=1)
   ->  Append (actual rows=11.00 loops=1)
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)
         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)
         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)

:PREFIX SELECT first(temp, time_alt) FROM btest;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Append (actual rows=11.00 loops=1)
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)
         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)
         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)

--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.00 loops=1)
   Sort Key: btest.gp
   Sort Method: quicksort 
   ->  HashAggregate (actual rows=2.00 loops=1)
         Group Key: btest.gp
         ->  Append (actual rows=11.00 loops=1)
               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)

--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.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Index Scan using _hyper_2_6_chunk_btest_numeric_time_idx on _hyper_2_6_chunk (actual rows=1.00 loops=1)
                 Index Cond: ("time" IS NOT NULL)

--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.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 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.00 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)

-- do index scan for last
:PREFIX SELECT last(temp, time) FROM btest;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time" DESC
                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 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)

-- do index scan for first
:PREFIX SELECT first(temp, time) FROM btest;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time"
                 ->  Index Scan Backward using _hyper_1_4_chunk_btest_time_idx on _hyper_1_4_chunk (actual rows=1.00 loops=1)
                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (never executed)
                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk (never executed)
                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)
                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed)

-- 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.00 loops=1)
   ->  Append (actual rows=11.00 loops=1)
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)
         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)
         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)

-- do index scan for subquery
:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time" DESC
                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 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)

-- 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.00 loops=1)
   Sort Key: btest.gp
   Sort Method: quicksort 
   ->  HashAggregate (actual rows=2.00 loops=1)
         Group Key: btest.gp
         ->  Append (actual rows=11.00 loops=1)
               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)

-- 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.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time" DESC
                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 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)

-- 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.00 loops=1)
   InitPlan 1 (returns $1)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time" DESC
                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 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.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1.00 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.00 loops=1)
                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)
                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed)
                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed)
                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed)

-- verify results when using both FIRST and LAST
:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $1)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time" DESC
                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 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.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest btest_1 (actual rows=1.00 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.00 loops=1)
                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk _hyper_1_1_chunk_1 (never executed)
                 ->  Index Scan Backward using _hyper_1_2_chunk_btest_time_idx on _hyper_1_2_chunk _hyper_1_2_chunk_1 (never executed)
                 ->  Index Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk _hyper_1_3_chunk_1 (never executed)
                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk _hyper_1_5_chunk_1 (never executed)

-- 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.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time" DESC
                 ->  Index Scan using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 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)

-- 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.00 loops=1)
   ->  Append (actual rows=11.00 loops=1)
         ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
         ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)
         ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)
         ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)

-- 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.00 loops=1)
   Sort Key: (last(btest.temp, btest."time"))
   Sort Method: quicksort 
   ->  Aggregate (actual rows=1.00 loops=1)
         ->  Append (actual rows=11.00 loops=1)
               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)

-- do index scan
:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time" DESC
                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=0.00 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.00 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.00 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.00 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)

-- 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.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time"
                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_idx on _hyper_1_1_chunk (actual rows=1.00 loops=1)
                       Index Cond: ("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 Scan Backward using _hyper_1_3_chunk_btest_time_idx on _hyper_1_3_chunk (never executed)
                 ->  Index Scan Backward using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (never executed)

-- 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.00 loops=1)
   ->  Sort (actual rows=11.00 loops=1)
         Sort Key: btest.gp
         Sort Method: quicksort 
         ->  Append (actual rows=11.00 loops=1)
               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)

-- test constants
:PREFIX SELECT first(100, 100) FROM btest;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Result (actual rows=1.00 loops=1)
                 ->  Append (actual rows=1.00 loops=1)
                       ->  Seq Scan on _hyper_1_1_chunk (actual rows=1.00 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)

-- 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.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Merge Append (actual rows=1.00 loops=1)
                 Sort Key: btest.time_alt DESC
                 ->  Index Scan Backward using _hyper_1_1_chunk_btest_time_alt_idx on _hyper_1_1_chunk (actual rows=1.00 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.00 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.00 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.00 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.00 loops=1)
                       Index Cond: (time_alt IS NOT NULL)

--test nested FIRST/LAST - should optimize
:PREFIX SELECT abs(last(temp, time)) FROM btest;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest (actual rows=1.00 loops=1)
                 Order: btest."time" DESC
                 ->  Index Scan using _hyper_1_5_chunk_btest_time_idx on _hyper_1_5_chunk (actual rows=1.00 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)

-- 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.00 loops=1)
   Sort Key: (abs(last(btest.temp, btest."time")))
   Sort Method: quicksort 
   ->  Aggregate (actual rows=1.00 loops=1)
         ->  Append (actual rows=11.00 loops=1)
               ->  Seq Scan on _hyper_1_1_chunk (actual rows=6.00 loops=1)
               ->  Seq Scan on _hyper_1_2_chunk (actual rows=2.00 loops=1)
               ->  Seq Scan on _hyper_1_3_chunk (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_1_4_chunk (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_1_5_chunk (actual rows=1.00 loops=1)

ROLLBACK;
-- Test with NULL numeric values
BEGIN;
TRUNCATE btest_numeric;
-- Empty table
:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Result (actual rows=0.00 loops=1)
         One-Time Filter: false

:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Result (actual rows=0.00 loops=1)
         One-Time Filter: false

-- Only NULL values
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Index Scan Backward using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)

:PREFIX SELECT last(quantity, time) FROM btest_numeric;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Index Scan using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)
                 Index Cond: ("time" IS NOT NULL)

:PREFIX SELECT first(time, quantity) FROM btest_numeric;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)

:PREFIX SELECT last(time, quantity) FROM btest_numeric;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)

-- NULL values followed by non-NULL values
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)
                 Order: btest_numeric."time"
                 ->  Index Scan Backward using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (actual rows=1.00 loops=1)
                 ->  Index Scan Backward using _hyper_2_9_chunk_btest_numeric_time_idx on _hyper_2_9_chunk (never executed)

:PREFIX SELECT last(quantity, time) FROM btest_numeric;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)
                 Order: btest_numeric."time" DESC
                 ->  Index Scan using _hyper_2_9_chunk_btest_numeric_time_idx on _hyper_2_9_chunk (actual rows=1.00 loops=1)
                       Index Cond: ("time" IS NOT NULL)
                 ->  Index Scan using _hyper_2_8_chunk_btest_numeric_time_idx on _hyper_2_8_chunk (never executed)
                       Index Cond: ("time" IS NOT NULL)

:PREFIX SELECT first(time, quantity) FROM btest_numeric;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Append (actual rows=4.00 loops=1)
         ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_2_9_chunk (actual rows=2.00 loops=1)

:PREFIX SELECT last(time, quantity) FROM btest_numeric;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Append (actual rows=4.00 loops=1)
         ->  Seq Scan on _hyper_2_8_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_2_9_chunk (actual rows=2.00 loops=1)

TRUNCATE btest_numeric;
-- non-NULL values followed by NULL values
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)
                 Order: btest_numeric."time"
                 ->  Index Scan Backward using _hyper_2_11_chunk_btest_numeric_time_idx on _hyper_2_11_chunk (actual rows=1.00 loops=1)
                 ->  Index Scan Backward using _hyper_2_10_chunk_btest_numeric_time_idx on _hyper_2_10_chunk (never executed)

:PREFIX SELECT last(quantity, time) FROM btest_numeric;
--- QUERY PLAN ---
 Result (actual rows=1.00 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit (actual rows=1.00 loops=1)
           ->  Custom Scan (ChunkAppend) on btest_numeric (actual rows=1.00 loops=1)
                 Order: btest_numeric."time" DESC
                 ->  Index Scan using _hyper_2_10_chunk_btest_numeric_time_idx on _hyper_2_10_chunk (actual rows=1.00 loops=1)
                       Index Cond: ("time" IS NOT NULL)
                 ->  Index Scan using _hyper_2_11_chunk_btest_numeric_time_idx on _hyper_2_11_chunk (never executed)
                       Index Cond: ("time" IS NOT NULL)

:PREFIX SELECT first(time, quantity) FROM btest_numeric;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Append (actual rows=4.00 loops=1)
         ->  Seq Scan on _hyper_2_10_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_2_11_chunk (actual rows=2.00 loops=1)

:PREFIX SELECT last(time, quantity) FROM btest_numeric;
--- QUERY PLAN ---
 Aggregate (actual rows=1.00 loops=1)
   ->  Append (actual rows=4.00 loops=1)
         ->  Seq Scan on _hyper_2_10_chunk (actual rows=2.00 loops=1)
         ->  Seq Scan on _hyper_2_11_chunk (actual rows=2.00 loops=1)

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

: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

:PREFIX SELECT last(temp, time) FROM btest;
 last 
------
 25.1

:PREFIX SELECT first(temp, time) FROM btest;
 first 
-------
  22.5

:PREFIX SELECT last(temp, time_alt) FROM btest;
 last 
------
 22.5

:PREFIX SELECT first(temp, time_alt) FROM btest;
 first 
-------
  30.2

:PREFIX SELECT gp, last(temp, time) FROM btest GROUP BY gp ORDER BY gp;
 gp | last 
----+------
  1 | 25.1
  2 | 20.1

:PREFIX SELECT gp, first(temp, time) FROM btest GROUP BY gp ORDER BY gp;
 gp | first 
----+-------
  1 |  22.5
  2 |  35.5

--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)

--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

:PREFIX SELECT gp, last(temp, strid) FROM btest GROUP BY gp ORDER BY gp;
 gp | last 
----+------
  1 | 22.5
  2 | 20.1

:PREFIX SELECT gp, last(strid, temp) FROM btest GROUP BY gp ORDER BY gp;
 gp |  last   
----+---------
  1 | testing
  2 | testing

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 
------
     

--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

--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

-- fist returns NULL value
:PREFIX SELECT first(temp, time_alt) FROM btest;
 first 
-------
      

-- 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

--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

:PREFIX SELECT first(temp, time_alt) FROM btest;
 first 
-------
  36.5

--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

--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 
------
     

--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

-- do index scan for last
:PREFIX SELECT last(temp, time) FROM btest;
 last 
------
 35.3

-- do index scan for first
:PREFIX SELECT first(temp, time) FROM btest;
 first 
-------
  36.5

-- can't do index scan when ordering on non-index column
:PREFIX SELECT first(temp, time_alt) FROM btest;
 first 
-------
  36.5

-- do index scan for subquery
:PREFIX SELECT * FROM (SELECT last(temp, time) FROM btest) last;
 last 
------
 35.3

-- 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

-- 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

-- 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

-- verify results when using both FIRST and LAST
:PREFIX SELECT first(temp, time), last(temp, time) FROM btest;
 first | last 
-------+------
  36.5 | 35.3

-- do index scan when using WHERE
:PREFIX SELECT last(temp, time) FROM btest WHERE time <= '2017-01-20T09:00:02';
 last 
------
 35.5

-- 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

-- 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

-- do index scan
:PREFIX SELECT last(temp, time) FROM btest WHERE temp < 30;
 last 
------
 25.1

-- 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

-- 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

-- test constants
:PREFIX SELECT first(100, 100) FROM btest;
 first 
-------
   100

-- 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

--test nested FIRST/LAST - should optimize
:PREFIX SELECT abs(last(temp, time)) FROM btest;
 abs  
------
 35.3

-- 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

ROLLBACK;
-- Test with NULL numeric values
BEGIN;
TRUNCATE btest_numeric;
-- Empty table
:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;
 first 
-------
 

:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;
 last 
------
 

-- Only NULL values
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
 first 
-------
      

:PREFIX SELECT last(quantity, time) FROM btest_numeric;
 last 
------
     

:PREFIX SELECT first(time, quantity) FROM btest_numeric;
 first 
-------
 

:PREFIX SELECT last(time, quantity) FROM btest_numeric;
 last 
------
 

-- NULL values followed by non-NULL values
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
 first 
-------
      

:PREFIX SELECT last(quantity, time) FROM btest_numeric;
 last 
------
    1

:PREFIX SELECT first(time, quantity) FROM btest_numeric;
          first           
--------------------------
 Sun Jan 20 09:00:43 2019

:PREFIX SELECT last(time, quantity) FROM btest_numeric;
           last           
--------------------------
 Sun Jan 20 09:00:43 2019

TRUNCATE btest_numeric;
-- non-NULL values followed by NULL values
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
 first 
-------
      

:PREFIX SELECT last(quantity, time) FROM btest_numeric;
 last 
------
    1

:PREFIX SELECT first(time, quantity) FROM btest_numeric;
          first           
--------------------------
 Sun Jan 20 09:00:43 2019

:PREFIX SELECT last(time, quantity) FROM btest_numeric;
           last           
--------------------------
 Sun Jan 20 09:00:43 2019

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;
-- Test with NULL numeric values
BEGIN;
TRUNCATE btest_numeric;
-- Empty table
:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;
:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;
-- Only NULL values
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
:PREFIX SELECT last(quantity, time) FROM btest_numeric;
:PREFIX SELECT first(time, quantity) FROM btest_numeric;
:PREFIX SELECT last(time, quantity) FROM btest_numeric;
-- NULL values followed by non-NULL values
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
:PREFIX SELECT last(quantity, time) FROM btest_numeric;
:PREFIX SELECT first(time, quantity) FROM btest_numeric;
:PREFIX SELECT last(time, quantity) FROM btest_numeric;
TRUNCATE btest_numeric;
-- non-NULL values followed by NULL values
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
:PREFIX SELECT last(quantity, time) FROM btest_numeric;
:PREFIX SELECT first(time, quantity) FROM btest_numeric;
:PREFIX SELECT last(time, quantity) FROM btest_numeric;
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;
-- Test with NULL numeric values
BEGIN;
TRUNCATE btest_numeric;
-- Empty table
:PREFIX SELECT first(btest_numeric, time) FROM btest_numeric;
:PREFIX SELECT last(btest_numeric, time) FROM btest_numeric;
-- Only NULL values
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
:PREFIX SELECT last(quantity, time) FROM btest_numeric;
:PREFIX SELECT first(time, quantity) FROM btest_numeric;
:PREFIX SELECT last(time, quantity) FROM btest_numeric;
-- NULL values followed by non-NULL values
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
:PREFIX SELECT last(quantity, time) FROM btest_numeric;
:PREFIX SELECT first(time, quantity) FROM btest_numeric;
:PREFIX SELECT last(time, quantity) FROM btest_numeric;
TRUNCATE btest_numeric;
-- non-NULL values followed by NULL values
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 1);
INSERT INTO btest_numeric VALUES('2019-01-20T09:00:43', 2);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
INSERT INTO btest_numeric VALUES('2018-01-20T09:00:43', NULL);
:PREFIX SELECT first(quantity, time) FROM btest_numeric;
:PREFIX SELECT last(quantity, time) FROM btest_numeric;
:PREFIX SELECT first(time, quantity) FROM btest_numeric;
:PREFIX SELECT last(time, quantity) FROM btest_numeric;
ROLLBACK;
\o
:DIFF_CMD
--- Unoptimized result
+++ Optimized result
@@ -1,6 +1,6 @@
              setting              | value 
 ----------------------------------+-------
- timescaledb.enable_optimizations | off
+ timescaledb.enable_optimizations | on
 
            time           | gp | temp 
-- Test partial aggregation
CREATE TABLE partial_aggregation (time timestamptz NOT NULL, quantity numeric, longvalue text);
SELECT schema_name, table_name, created FROM create_hypertable('partial_aggregation', 'time');
 schema_name |     table_name      | created 
-------------+---------------------+---------
 public      | partial_aggregation | t

INSERT INTO partial_aggregation VALUES('2018-01-20T09:00:43', NULL, NULL);
INSERT INTO partial_aggregation VALUES('2018-01-20T09:00:44', NULL, NULL);
INSERT INTO partial_aggregation VALUES('2019-01-20T09:00:43', 1, 'hello');
INSERT INTO partial_aggregation VALUES('2019-01-20T09:00:44', 2, 'world');
INSERT INTO partial_aggregation VALUES('2020-01-20T09:00:43', 3.1, 'some1');
INSERT INTO partial_aggregation VALUES('2020-01-20T09:00:44', 3.2, 'more1');
INSERT INTO partial_aggregation VALUES('2021-01-20T09:00:43', 3.3, 'some2');
INSERT INTO partial_aggregation VALUES('2021-01-20T09:00:44', 3.4, 'more2');
INSERT INTO partial_aggregation VALUES('2022-01-20T09:00:43', 4, 'word1');
INSERT INTO partial_aggregation VALUES('2022-01-20T09:00:44', 5, 'word2');
INSERT INTO partial_aggregation VALUES('2023-01-20T09:00:43', 6, 'word3');
INSERT INTO partial_aggregation VALUES('2023-01-20T09:00:44', 7, 'word4');
-- Use enable_partitionwise_aggregate to create partial aggregates per chunk
SET enable_partitionwise_aggregate = ON;
SELECT
    format('SELECT %3$s, %1$s FROM partial_aggregation WHERE %2$s GROUP BY %3$s ORDER BY 1, 2;',
            function, condition, grouping)
FROM
    unnest(array[
            'first(time, quantity), last(time, quantity)',
            'last(longvalue, quantity)',
            'last(quantity, longvalue)',
            'last(quantity, time)',
            'last(time, longvalue)']) AS function,
    unnest(array[
            'true',
            $$time < '2021-01-01'$$,
            'quantity is null',
            'quantity is not null',
            'quantity >= 4']) AS condition,
    unnest(array[
            '777::text' /* dummy grouping column */,
            'longvalue',
            'quantity',
            $$time_bucket('1 year', time)$$,
            $$time_bucket('3 year', time)$$]) AS grouping
\gexec
SELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;
 text |            first             |             last             
------+------------------------------+------------------------------
 777  | Sun Jan 20 09:00:43 2019 PST | Fri Jan 20 09:00:44 2023 PST

SELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;
 text |            first             |             last             
------+------------------------------+------------------------------
 777  | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST

SELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;
 text | first | last 
------+-------+------
 777  |       | 

SELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;
 text |            first             |             last             
------+------------------------------+------------------------------
 777  | Sun Jan 20 09:00:43 2019 PST | Fri Jan 20 09:00:44 2023 PST

SELECT 777::text, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;
 text |            first             |             last             
------+------------------------------+------------------------------
 777  | Thu Jan 20 09:00:43 2022 PST | Fri Jan 20 09:00:44 2023 PST

SELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;
 text | last  
------+-------
 777  | word4

SELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;
 text | last  
------+-------
 777  | more1

SELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  | 

SELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;
 text | last  
------+-------
 777  | word4

SELECT 777::text, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;
 text | last  
------+-------
 777  | word4

SELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  |    2

SELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  |    2

SELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  |     

SELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  |    2

SELECT 777::text, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  |    7

SELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  |    7

SELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  |  3.2

SELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  |     

SELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  |    7

SELECT 777::text, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  |    7

SELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY 777::text ORDER BY 1, 2;
 text |             last             
------+------------------------------
 777  | Sun Jan 20 09:00:44 2019 PST

SELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY 777::text ORDER BY 1, 2;
 text |             last             
------+------------------------------
 777  | Sun Jan 20 09:00:44 2019 PST

SELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY 777::text ORDER BY 1, 2;
 text | last 
------+------
 777  | 

SELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY 777::text ORDER BY 1, 2;
 text |             last             
------+------------------------------
 777  | Sun Jan 20 09:00:44 2019 PST

SELECT 777::text, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY 777::text ORDER BY 1, 2;
 text |             last             
------+------------------------------
 777  | Fri Jan 20 09:00:44 2023 PST

SELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;
 longvalue |            first             |             last             
-----------+------------------------------+------------------------------
 hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST
 more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST
 more2     | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST
 some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST
 some2     | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST
 word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST
 word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST
 word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST
 word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST
 world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST
           |                              | 

SELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;
 longvalue |            first             |             last             
-----------+------------------------------+------------------------------
 hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST
 more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST
 some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST
 world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST
           |                              | 

SELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;
 longvalue | first | last 
-----------+-------+------
           |       | 

SELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;
 longvalue |            first             |             last             
-----------+------------------------------+------------------------------
 hello     | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST
 more1     | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST
 more2     | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST
 some1     | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST
 some2     | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST
 word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST
 word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST
 word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST
 word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST
 world     | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST

SELECT longvalue, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;
 longvalue |            first             |             last             
-----------+------------------------------+------------------------------
 word1     | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST
 word2     | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST
 word3     | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST
 word4     | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST

SELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last  
-----------+-------
 hello     | hello
 more1     | more1
 more2     | more2
 some1     | some1
 some2     | some2
 word1     | word1
 word2     | word2
 word3     | word3
 word4     | word4
 world     | world
           | 

SELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last  
-----------+-------
 hello     | hello
 more1     | more1
 some1     | some1
 world     | world
           | 

SELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
           | 

SELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last  
-----------+-------
 hello     | hello
 more1     | more1
 more2     | more2
 some1     | some1
 some2     | some2
 word1     | word1
 word2     | word2
 word3     | word3
 word4     | word4
 world     | world

SELECT longvalue, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last  
-----------+-------
 word1     | word1
 word2     | word2
 word3     | word3
 word4     | word4

SELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
 hello     |    1
 more1     |  3.2
 more2     |  3.4
 some1     |  3.1
 some2     |  3.3
 word1     |    4
 word2     |    5
 word3     |    6
 word4     |    7
 world     |    2
           |     

SELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
 hello     |    1
 more1     |  3.2
 some1     |  3.1
 world     |    2
           |     

SELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
           |     

SELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
 hello     |    1
 more1     |  3.2
 more2     |  3.4
 some1     |  3.1
 some2     |  3.3
 word1     |    4
 word2     |    5
 word3     |    6
 word4     |    7
 world     |    2

SELECT longvalue, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
 word1     |    4
 word2     |    5
 word3     |    6
 word4     |    7

SELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
 hello     |    1
 more1     |  3.2
 more2     |  3.4
 some1     |  3.1
 some2     |  3.3
 word1     |    4
 word2     |    5
 word3     |    6
 word4     |    7
 world     |    2
           |     

SELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
 hello     |    1
 more1     |  3.2
 some1     |  3.1
 world     |    2
           |     

SELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
           |     

SELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
 hello     |    1
 more1     |  3.2
 more2     |  3.4
 some1     |  3.1
 some2     |  3.3
 word1     |    4
 word2     |    5
 word3     |    6
 word4     |    7
 world     |    2

SELECT longvalue, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
 word1     |    4
 word2     |    5
 word3     |    6
 word4     |    7

SELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY longvalue ORDER BY 1, 2;
 longvalue |             last             
-----------+------------------------------
 hello     | Sun Jan 20 09:00:43 2019 PST
 more1     | Mon Jan 20 09:00:44 2020 PST
 more2     | Wed Jan 20 09:00:44 2021 PST
 some1     | Mon Jan 20 09:00:43 2020 PST
 some2     | Wed Jan 20 09:00:43 2021 PST
 word1     | Thu Jan 20 09:00:43 2022 PST
 word2     | Thu Jan 20 09:00:44 2022 PST
 word3     | Fri Jan 20 09:00:43 2023 PST
 word4     | Fri Jan 20 09:00:44 2023 PST
 world     | Sun Jan 20 09:00:44 2019 PST
           | 

SELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY longvalue ORDER BY 1, 2;
 longvalue |             last             
-----------+------------------------------
 hello     | Sun Jan 20 09:00:43 2019 PST
 more1     | Mon Jan 20 09:00:44 2020 PST
 some1     | Mon Jan 20 09:00:43 2020 PST
 world     | Sun Jan 20 09:00:44 2019 PST
           | 

SELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY longvalue ORDER BY 1, 2;
 longvalue | last 
-----------+------
           | 

SELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY longvalue ORDER BY 1, 2;
 longvalue |             last             
-----------+------------------------------
 hello     | Sun Jan 20 09:00:43 2019 PST
 more1     | Mon Jan 20 09:00:44 2020 PST
 more2     | Wed Jan 20 09:00:44 2021 PST
 some1     | Mon Jan 20 09:00:43 2020 PST
 some2     | Wed Jan 20 09:00:43 2021 PST
 word1     | Thu Jan 20 09:00:43 2022 PST
 word2     | Thu Jan 20 09:00:44 2022 PST
 word3     | Fri Jan 20 09:00:43 2023 PST
 word4     | Fri Jan 20 09:00:44 2023 PST
 world     | Sun Jan 20 09:00:44 2019 PST

SELECT longvalue, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY longvalue ORDER BY 1, 2;
 longvalue |             last             
-----------+------------------------------
 word1     | Thu Jan 20 09:00:43 2022 PST
 word2     | Thu Jan 20 09:00:44 2022 PST
 word3     | Fri Jan 20 09:00:43 2023 PST
 word4     | Fri Jan 20 09:00:44 2023 PST

SELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;
 quantity |            first             |             last             
----------+------------------------------+------------------------------
        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST
        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST
      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST
      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST
      3.3 | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST
      3.4 | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST
        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST
        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST
        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST
        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST
          |                              | 

SELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;
 quantity |            first             |             last             
----------+------------------------------+------------------------------
        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST
        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST
      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST
      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST
          |                              | 

SELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;
 quantity | first | last 
----------+-------+------
          |       | 

SELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;
 quantity |            first             |             last             
----------+------------------------------+------------------------------
        1 | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:43 2019 PST
        2 | Sun Jan 20 09:00:44 2019 PST | Sun Jan 20 09:00:44 2019 PST
      3.1 | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:43 2020 PST
      3.2 | Mon Jan 20 09:00:44 2020 PST | Mon Jan 20 09:00:44 2020 PST
      3.3 | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:43 2021 PST
      3.4 | Wed Jan 20 09:00:44 2021 PST | Wed Jan 20 09:00:44 2021 PST
        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST
        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST
        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST
        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST

SELECT quantity, first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;
 quantity |            first             |             last             
----------+------------------------------+------------------------------
        4 | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:43 2022 PST
        5 | Thu Jan 20 09:00:44 2022 PST | Thu Jan 20 09:00:44 2022 PST
        6 | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:43 2023 PST
        7 | Fri Jan 20 09:00:44 2023 PST | Fri Jan 20 09:00:44 2023 PST

SELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;
 quantity | last  
----------+-------
        1 | hello
        2 | world
      3.1 | some1
      3.2 | more1
      3.3 | some2
      3.4 | more2
        4 | word1
        5 | word2
        6 | word3
        7 | word4
          | 

SELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;
 quantity | last  
----------+-------
        1 | hello
        2 | world
      3.1 | some1
      3.2 | more1
          | 

SELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
          | 

SELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;
 quantity | last  
----------+-------
        1 | hello
        2 | world
      3.1 | some1
      3.2 | more1
      3.3 | some2
      3.4 | more2
        4 | word1
        5 | word2
        6 | word3
        7 | word4

SELECT quantity, last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;
 quantity | last  
----------+-------
        4 | word1
        5 | word2
        6 | word3
        7 | word4

SELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
        1 |    1
        2 |    2
      3.1 |  3.1
      3.2 |  3.2
      3.3 |  3.3
      3.4 |  3.4
        4 |    4
        5 |    5
        6 |    6
        7 |    7
          |     

SELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
        1 |    1
        2 |    2
      3.1 |  3.1
      3.2 |  3.2
          |     

SELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
          |     

SELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
        1 |    1
        2 |    2
      3.1 |  3.1
      3.2 |  3.2
      3.3 |  3.3
      3.4 |  3.4
        4 |    4
        5 |    5
        6 |    6
        7 |    7

SELECT quantity, last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
        4 |    4
        5 |    5
        6 |    6
        7 |    7

SELECT quantity, last(quantity, time) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
        1 |    1
        2 |    2
      3.1 |  3.1
      3.2 |  3.2
      3.3 |  3.3
      3.4 |  3.4
        4 |    4
        5 |    5
        6 |    6
        7 |    7
          |     

SELECT quantity, last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
        1 |    1
        2 |    2
      3.1 |  3.1
      3.2 |  3.2
          |     

SELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
          |     

SELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
        1 |    1
        2 |    2
      3.1 |  3.1
      3.2 |  3.2
      3.3 |  3.3
      3.4 |  3.4
        4 |    4
        5 |    5
        6 |    6
        7 |    7

SELECT quantity, last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
        4 |    4
        5 |    5
        6 |    6
        7 |    7

SELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY quantity ORDER BY 1, 2;
 quantity |             last             
----------+------------------------------
        1 | Sun Jan 20 09:00:43 2019 PST
        2 | Sun Jan 20 09:00:44 2019 PST
      3.1 | Mon Jan 20 09:00:43 2020 PST
      3.2 | Mon Jan 20 09:00:44 2020 PST
      3.3 | Wed Jan 20 09:00:43 2021 PST
      3.4 | Wed Jan 20 09:00:44 2021 PST
        4 | Thu Jan 20 09:00:43 2022 PST
        5 | Thu Jan 20 09:00:44 2022 PST
        6 | Fri Jan 20 09:00:43 2023 PST
        7 | Fri Jan 20 09:00:44 2023 PST
          | 

SELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY quantity ORDER BY 1, 2;
 quantity |             last             
----------+------------------------------
        1 | Sun Jan 20 09:00:43 2019 PST
        2 | Sun Jan 20 09:00:44 2019 PST
      3.1 | Mon Jan 20 09:00:43 2020 PST
      3.2 | Mon Jan 20 09:00:44 2020 PST
          | 

SELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY quantity ORDER BY 1, 2;
 quantity | last 
----------+------
          | 

SELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY quantity ORDER BY 1, 2;
 quantity |             last             
----------+------------------------------
        1 | Sun Jan 20 09:00:43 2019 PST
        2 | Sun Jan 20 09:00:44 2019 PST
      3.1 | Mon Jan 20 09:00:43 2020 PST
      3.2 | Mon Jan 20 09:00:44 2020 PST
      3.3 | Wed Jan 20 09:00:43 2021 PST
      3.4 | Wed Jan 20 09:00:44 2021 PST
        4 | Thu Jan 20 09:00:43 2022 PST
        5 | Thu Jan 20 09:00:44 2022 PST
        6 | Fri Jan 20 09:00:43 2023 PST
        7 | Fri Jan 20 09:00:44 2023 PST

SELECT quantity, last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY quantity ORDER BY 1, 2;
 quantity |             last             
----------+------------------------------
        4 | Thu Jan 20 09:00:43 2022 PST
        5 | Thu Jan 20 09:00:44 2022 PST
        6 | Fri Jan 20 09:00:43 2023 PST
        7 | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          |            first             |             last             
------------------------------+------------------------------+------------------------------
 Sun Dec 31 16:00:00 2017 PST |                              | 
 Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST
 Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST
 Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:44 2021 PST
 Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST
 Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          |            first             |             last             
------------------------------+------------------------------+------------------------------
 Sun Dec 31 16:00:00 2017 PST |                              | 
 Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST
 Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST

SELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | first | last 
------------------------------+-------+------
 Sun Dec 31 16:00:00 2017 PST |       | 

SELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          |            first             |             last             
------------------------------+------------------------------+------------------------------
 Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:43 2019 PST | Sun Jan 20 09:00:44 2019 PST
 Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST | Mon Jan 20 09:00:44 2020 PST
 Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Wed Jan 20 09:00:44 2021 PST
 Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST
 Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('1 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          |            first             |             last             
------------------------------+------------------------------+------------------------------
 Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:43 2022 PST | Thu Jan 20 09:00:44 2022 PST
 Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:43 2023 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last  
------------------------------+-------
 Sun Dec 31 16:00:00 2017 PST | 
 Mon Dec 31 16:00:00 2018 PST | world
 Tue Dec 31 16:00:00 2019 PST | more1
 Thu Dec 31 16:00:00 2020 PST | more2
 Fri Dec 31 16:00:00 2021 PST | word2
 Sat Dec 31 16:00:00 2022 PST | word4

SELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last  
------------------------------+-------
 Sun Dec 31 16:00:00 2017 PST | 
 Mon Dec 31 16:00:00 2018 PST | world
 Tue Dec 31 16:00:00 2019 PST | more1

SELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST | 

SELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last  
------------------------------+-------
 Mon Dec 31 16:00:00 2018 PST | world
 Tue Dec 31 16:00:00 2019 PST | more1
 Thu Dec 31 16:00:00 2020 PST | more2
 Fri Dec 31 16:00:00 2021 PST | word2
 Sat Dec 31 16:00:00 2022 PST | word4

SELECT time_bucket('1 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last  
------------------------------+-------
 Fri Dec 31 16:00:00 2021 PST | word2
 Sat Dec 31 16:00:00 2022 PST | word4

SELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |     
 Mon Dec 31 16:00:00 2018 PST |    2
 Tue Dec 31 16:00:00 2019 PST |  3.1
 Thu Dec 31 16:00:00 2020 PST |  3.3
 Fri Dec 31 16:00:00 2021 PST |    5
 Sat Dec 31 16:00:00 2022 PST |    7

SELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |     
 Mon Dec 31 16:00:00 2018 PST |    2
 Tue Dec 31 16:00:00 2019 PST |  3.1

SELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |     

SELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Mon Dec 31 16:00:00 2018 PST |    2
 Tue Dec 31 16:00:00 2019 PST |  3.1
 Thu Dec 31 16:00:00 2020 PST |  3.3
 Fri Dec 31 16:00:00 2021 PST |    5
 Sat Dec 31 16:00:00 2022 PST |    7

SELECT time_bucket('1 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Fri Dec 31 16:00:00 2021 PST |    5
 Sat Dec 31 16:00:00 2022 PST |    7

SELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |     
 Mon Dec 31 16:00:00 2018 PST |    2
 Tue Dec 31 16:00:00 2019 PST |  3.2
 Thu Dec 31 16:00:00 2020 PST |  3.4
 Fri Dec 31 16:00:00 2021 PST |    5
 Sat Dec 31 16:00:00 2022 PST |    7

SELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |     
 Mon Dec 31 16:00:00 2018 PST |    2
 Tue Dec 31 16:00:00 2019 PST |  3.2

SELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |     

SELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Mon Dec 31 16:00:00 2018 PST |    2
 Tue Dec 31 16:00:00 2019 PST |  3.2
 Thu Dec 31 16:00:00 2020 PST |  3.4
 Fri Dec 31 16:00:00 2021 PST |    5
 Sat Dec 31 16:00:00 2022 PST |    7

SELECT time_bucket('1 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Fri Dec 31 16:00:00 2021 PST |    5
 Sat Dec 31 16:00:00 2022 PST |    7

SELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          |             last             
------------------------------+------------------------------
 Sun Dec 31 16:00:00 2017 PST | 
 Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST
 Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST
 Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST
 Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST
 Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          |             last             
------------------------------+------------------------------
 Sun Dec 31 16:00:00 2017 PST | 
 Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST
 Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST

SELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST | 

SELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          |             last             
------------------------------+------------------------------
 Mon Dec 31 16:00:00 2018 PST | Sun Jan 20 09:00:44 2019 PST
 Tue Dec 31 16:00:00 2019 PST | Mon Jan 20 09:00:43 2020 PST
 Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST
 Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST
 Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('1 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('1 year', time) ORDER BY 1, 2;
         time_bucket          |             last             
------------------------------+------------------------------
 Fri Dec 31 16:00:00 2021 PST | Thu Jan 20 09:00:44 2022 PST
 Sat Dec 31 16:00:00 2022 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          |            first             |             last             
------------------------------+------------------------------+------------------------------
 Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST
 Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          |            first             |             last             
------------------------------+------------------------------+------------------------------
 Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST

SELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | first | last 
------------------------------+-------+------
 Sun Dec 31 16:00:00 2017 PST |       | 

SELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          |            first             |             last             
------------------------------+------------------------------+------------------------------
 Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:43 2019 PST | Mon Jan 20 09:00:44 2020 PST
 Thu Dec 31 16:00:00 2020 PST | Wed Jan 20 09:00:43 2021 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('3 year', time), first(time, quantity), last(time, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          |            first             |             last             
------------------------------+------------------------------+------------------------------
 Thu Dec 31 16:00:00 2020 PST | Thu Jan 20 09:00:43 2022 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last  
------------------------------+-------
 Sun Dec 31 16:00:00 2017 PST | more1
 Thu Dec 31 16:00:00 2020 PST | word4

SELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last  
------------------------------+-------
 Sun Dec 31 16:00:00 2017 PST | more1

SELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST | 

SELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last  
------------------------------+-------
 Sun Dec 31 16:00:00 2017 PST | more1
 Thu Dec 31 16:00:00 2020 PST | word4

SELECT time_bucket('3 year', time), last(longvalue, quantity) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last  
------------------------------+-------
 Thu Dec 31 16:00:00 2020 PST | word4

SELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |    2
 Thu Dec 31 16:00:00 2020 PST |    7

SELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |    2

SELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |     

SELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |    2
 Thu Dec 31 16:00:00 2020 PST |    7

SELECT time_bucket('3 year', time), last(quantity, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Thu Dec 31 16:00:00 2020 PST |    7

SELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |  3.2
 Thu Dec 31 16:00:00 2020 PST |    7

SELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |  3.2

SELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |     

SELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST |  3.2
 Thu Dec 31 16:00:00 2020 PST |    7

SELECT time_bucket('3 year', time), last(quantity, time) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Thu Dec 31 16:00:00 2020 PST |    7

SELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE true GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          |             last             
------------------------------+------------------------------
 Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST
 Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE time < '2021-01-01' GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          |             last             
------------------------------+------------------------------
 Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST

SELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          | last 
------------------------------+------
 Sun Dec 31 16:00:00 2017 PST | 

SELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity is not null GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          |             last             
------------------------------+------------------------------
 Sun Dec 31 16:00:00 2017 PST | Sun Jan 20 09:00:44 2019 PST
 Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST

SELECT time_bucket('3 year', time), last(time, longvalue) FROM partial_aggregation WHERE quantity >= 4 GROUP BY time_bucket('3 year', time) ORDER BY 1, 2;
         time_bucket          |             last             
------------------------------+------------------------------
 Thu Dec 31 16:00:00 2020 PST | Fri Jan 20 09:00:44 2023 PST

SET enable_partitionwise_aggregate = OFF;
