Open fr3aker opened 1 year ago
Thanks for taking the time to report this. I can easily reproduce the issue with the given steps. We will log this and will see how can we rectify the issue.
Hello there, on discussing this further within team, we found out that there is a workaround for this, you can reformulate your query like this,
WITH gapfill AS ( SELECT time_bucket_gapfill('1 day', time) AS day, device_id, count(*) as count FROM metrics WHERE time >= '2023-01-01T00:00:00Z' AND time < '2023-01-08T00:00:00Z' GROUP BY day, device_id ) SELECT * FROM gapfill WHERE count IS NULL;
Because as per your original query, filtering happens before gapfill runs so if your having filter removes all groups then gapfill will not see any groups explaining the completely empty result and this is only relevant if you have additional grouping columns beside time column. Hopefully this will help you. Please let us know if you need any more inputs here.
Thank you very much for the response. The workaround is working well. Is the current behaviour intended or will there be a fix in a future version?
Just a note regarding the workaround (which is working!), this seemingly equivalent query is exhibiting the same broken behaviour:
SELECT * FROM ( SELECT time_bucket_gapfill('1 day', time) AS day, device_id, count(*) as count FROM metrics WHERE time >= '2023-01-01T00:00:00Z' AND time < '2023-01-08T00:00:00Z' GROUP BY day, device_id ) AS gapfill WHERE count IS NULL;
Probably has something to do with postgresql's query optimiser.
the suggested variant using the WITH
clause generates a query plan with CTE (common table expression); meanwhile - your last query uses a subquery.
CTE Scan on gapfill (cost=35.96..36.12 rows=1 width=20) (actual time=0.048..0.050 rows=5 loops=1)
Filter: (count IS NULL)
Rows Removed by Filter: 2
CTE gapfill
-> Custom Scan (GapFill) (cost=35.94..35.96 rows=8 width=0) (actual time=0.047..0.048 rows=7 loops=1)
-> Sort (cost=35.94..35.96 rows=8 width=0) (actual time=0.045..0.046 rows=2 loops=1)
Sort Key: metrics.device_id, (time_bucket_gapfill('1 day'::interval, metrics."time", NULL::timestamp with time zone, NULL::timestamp with time zone))
Sort Method: quicksort Memory: 25kB
-> GroupAggregate (cost=35.64..35.82 rows=8 width=0) (actual time=0.015..0.016 rows=2 loops=1)
Group Key: (time_bucket_gapfill('1 day'::interval, metrics."time", NULL::timestamp with time zone, NULL::timestamp with time zone)), metrics.device_id
-> Sort (cost=35.64..35.66 rows=8 width=12) (actual time=0.012..0.013 rows=4 loops=1)
Sort Key: (time_bucket_gapfill('1 day'::interval, metrics."time", NULL::timestamp with time zone, NULL::timestamp with time zone)), metrics.device_id
Sort Method: quicksort Memory: 25kB
-> Seq Scan on metrics (cost=0.00..35.52 rows=8 width=12) (actual time=0.003..0.004 rows=4 loops=1)
Filter: (("time" >= '2023-01-01 00:00:00+00'::timestamp with time zone) AND ("time" < '2023-01-08 00:00:00+00'::timestamp with time zone))
Planning Time: 0.223 ms
Execution Time: 0.159 ms
meanwhile the subquery/having plan looks like this:
Custom Scan (GapFill) (cost=35.81..35.82 rows=1 width=0) (actual time=0.043..0.043 rows=0 loops=1)
-> Sort (cost=35.81..35.82 rows=1 width=0) (actual time=0.042..0.043 rows=0 loops=1)
Sort Key: metrics.device_id, (time_bucket_gapfill('1 day'::interval, metrics."time", NULL::timestamp with time zone, NULL::timestamp with time zone))
Sort Method: quicksort Memory: 25kB
-> GroupAggregate (cost=35.64..35.80 rows=1 width=0) (actual time=0.017..0.017 rows=0 loops=1)
Group Key: (time_bucket_gapfill('1 day'::interval, metrics."time", NULL::timestamp with time zone, NULL::timestamp with time zone)), metrics.device_id
Filter: (count(*) IS NULL)
Rows Removed by Filter: 2
-> Sort (cost=35.64..35.66 rows=8 width=12) (actual time=0.013..0.013 rows=4 loops=1)
Sort Key: (time_bucket_gapfill('1 day'::interval, metrics."time", NULL::timestamp with time zone, NULL::timestamp with time zone)), metrics.device_id
Sort Method: quicksort Memory: 25kB
-> Seq Scan on metrics (cost=0.00..35.52 rows=8 width=12) (actual time=0.003..0.004 rows=4 loops=1)
Filter: (("time" >= '2023-01-01 00:00:00+00'::timestamp with time zone) AND ("time" < '2023-01-08 00:00:00+00'::timestamp with time zone))
Planning Time: 0.219 ms
Execution Time: 0.143 ms
as of now - this second one is having
(:D) trouble in your case - base you want to formulate a condition for the columns populated by the GapFill
scan.
I think one way to permanently fix this could be to make the following changes during planning:
Filter
from the GroupAggregate
Filter
on top of the CustomScan
Thank you @fr3aker for reporting this; I'll move this into the backlog!
What type of bug is this?
Incorrect result
What subsystems and features are affected?
Gapfill
What happened?
Using a
HAVING
clause withtime_bucket_gapfill
is returning weird (incorrect) results, swallowing expected rows or returning erroneous rows.TimescaleDB version affected
2.9.1, 2.8.1
PostgreSQL version used
14.5
What operating system did you use?
gentoo x86-64
What installation method did you use?
Other
What platform did you run on?
On prem/Self-hosted
Relevant log output and stack trace
No response
How can we reproduce the bug?
No HAVING clause working correctly
*HAVING count() IS NULL* or `count() < 1` not returning anything
Expected result
*HAVING count() < 2** returning erroneous row
Expected result