diff --git a/.unreleased/pr_9735 b/.unreleased/pr_9735 new file mode 100644 index 00000000000..6822d90f037 --- /dev/null +++ b/.unreleased/pr_9735 @@ -0,0 +1,2 @@ +Implements: #9735 Improve GapFill row count estimate +Thanks: @Fabian-2596 for suggesting more accurate GapFill row count estimate diff --git a/tsl/src/nodes/gapfill/gapfill.h b/tsl/src/nodes/gapfill/gapfill.h index a4c7dab2a51..205d0113421 100644 --- a/tsl/src/nodes/gapfill/gapfill.h +++ b/tsl/src/nodes/gapfill/gapfill.h @@ -8,6 +8,7 @@ #include #include #include +#include #define GAPFILL_FUNCTION "time_bucket_gapfill" #define GAPFILL_LOCF_FUNCTION "locf" @@ -34,3 +35,29 @@ typedef struct GapFillPath CustomPath cpath; FuncExpr *func; /* time_bucket_gapfill function call */ } GapFillPath; + +typedef enum GapFillBoundary +{ + GAPFILL_START, + GAPFILL_END, +} GapFillBoundary; + +typedef struct GapFillArgEvalContext +{ + PlannerInfo *root; /* needed for evaluation at planning time */ + CustomScanState *state; /* needed for evaluation at execution time */ + Oid typid; /* gapfill function type */ + List *args; /* gapfill function arguments */ + FromExpr *jt; /* needed for inferring boundaries from WHERE */ + bool estimate_failed; /* whether estimation during planning failed */ +} GapFillArgEvalContext; + +extern int64 infer_gapfill_boundary(GapFillArgEvalContext *gapfill_ctx, GapFillBoundary boundary); +extern Datum gapfill_estimate_arg(GapFillArgEvalContext *gapfill_plan_ctx, Node *arg, bool *isnull); + +extern bool gapfill_is_const_null(Expr *expr); +extern bool gapfill_is_simple_expr(Expr *node); +extern Const *make_const_value_for_gapfill_internal(Oid typid, int64 value); +extern int64 gapfill_datum_get_internal(Datum, Oid); +extern int64 gapfill_bucket_width_get_internal(Oid timetype, Oid argtype, Datum arg, + Interval **interval); diff --git a/tsl/src/nodes/gapfill/gapfill_exec.c b/tsl/src/nodes/gapfill/gapfill_exec.c index 6cc87d76e20..92946170e6c 100644 --- a/tsl/src/nodes/gapfill/gapfill_exec.c +++ b/tsl/src/nodes/gapfill/gapfill_exec.c @@ -28,6 +28,7 @@ #include #include +#include "estimate.h" #include "gapfill.h" #include "gapfill_internal.h" #include "interpolate.h" @@ -35,12 +36,6 @@ #include "time_bucket.h" #include -typedef enum GapFillBoundary -{ - GAPFILL_START, - GAPFILL_END, -} GapFillBoundary; - typedef union GapFillColumnStateUnion { GapFillColumnState *base; @@ -175,8 +170,8 @@ get_timezone_arg(GapFillState *state) return lthird(state->args); } -static inline int64 -gapfill_period_get_internal(Oid timetype, Oid argtype, Datum arg, Interval **interval) +int64 +gapfill_bucket_width_get_internal(Oid timetype, Oid argtype, Datum arg, Interval **interval) { switch (timetype) { @@ -240,8 +235,8 @@ gapfill_state_create(CustomScan *cscan) return (Node *) state; } -static bool -is_const_null(Expr *expr) +bool +gapfill_is_const_null(Expr *expr) { return IsA(expr, Const) && castNode(Const, expr)->constisnull; } @@ -288,7 +283,7 @@ var_equal(Var *v1, Var *v2) } static bool -is_simple_expr_walker(Node *node, void *context) +gapfill_is_simple_expr_walker(Node *node, void *context) { if (node == NULL) { @@ -328,22 +323,22 @@ is_simple_expr_walker(Node *node, void *context) default: return true; } - return expression_tree_walker(node, is_simple_expr_walker, context); + return expression_tree_walker(node, gapfill_is_simple_expr_walker, context); } /* * check if expression is simple expression and contains only simple * subexpressions */ -static bool -is_simple_expr(Expr *node) +bool +gapfill_is_simple_expr(Expr *node) { /* * since expression_tree_walker does early exit on true and we use that to * skip processing on first non-simple expression we invert return value * from expression_tree_walker here */ - return !is_simple_expr_walker((Node *) node, NULL); + return !gapfill_is_simple_expr_walker((Node *) node, NULL); } /* @@ -360,7 +355,7 @@ align_with_time_bucket(GapFillState *state, Expr *expr) Datum value; bool isnull; - if (!is_simple_expr(expr)) + if (!gapfill_is_simple_expr(expr)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -399,39 +394,40 @@ align_with_time_bucket(GapFillState *state, Expr *expr) return gapfill_datum_get_internal(value, state->gapfill_typid); } -static int64 -get_boundary_expr_value(GapFillState *state, GapFillBoundary boundary, Expr *expr) +/* Can be called either with PlannerInfo to estimate boundary value at planning time + * or with GapFillState to evaluate boundary value at execution time. + */ +static Datum +get_boundary_expr_value(GapFillArgEvalContext *gapfill_ctx, GapFillBoundary boundary, Expr *expr, + bool *isnull) { - Datum arg_value; - bool isnull; - + *isnull = false; + Datum arg_value = 0; /* * add an explicit cast here if types do not match */ - if (exprType((Node *) expr) != state->gapfill_typid) + Oid gapfill_typid = gapfill_ctx->typid; + if (exprType((Node *) expr) != gapfill_typid) { - Oid cast_oid = get_cast_func(exprType((Node *) expr), state->gapfill_typid); - - expr = (Expr *) makeFuncExpr(cast_oid, - state->gapfill_typid, - list_make1(expr), - InvalidOid, - InvalidOid, - 0); - } + Oid cast_oid = get_cast_func(exprType((Node *) expr), gapfill_typid); - arg_value = gapfill_exec_expr(state, state->scanslot, expr, &isnull); + expr = (Expr *) + makeFuncExpr(cast_oid, gapfill_typid, list_make1(expr), InvalidOid, InvalidOid, 0); + } - if (isnull) + if (gapfill_ctx->state) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid time_bucket_gapfill argument: %s cannot be NULL", - boundary == GAPFILL_START ? "start" : "finish"), - errhint("Specify start and finish as arguments or in the WHERE clause."))); + GapFillState *state = (GapFillState *) gapfill_ctx->state; + arg_value = gapfill_exec_expr(state, state->scanslot, expr, isnull); + } + else + { + /* Estimating start/end boundary at planning time: + * bail out with no error if cannot estimate. */ + arg_value = gapfill_estimate_arg(gapfill_ctx, (Node *) expr, isnull); } - return gapfill_datum_get_internal(arg_value, state->gapfill_typid); + return arg_value; } typedef struct CollectBoundaryContext @@ -543,15 +539,20 @@ collect_boundary_expressions(Node *node, Var *ts_var) return context.quals; } -static int64 -infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary) +/* Generic method to collect WHERE quals and obtain suitable start/end gapfill boundaries. + * Can be called from the planner with PlannerInfo to estimate boundaries + * or with GapFillState to evaluate boundaries at execution time. + */ +int64 +infer_gapfill_boundary(GapFillArgEvalContext *gapfill_ctx, GapFillBoundary boundary) { - CustomScan *cscan = castNode(CustomScan, state->csstate.ss.ps.plan); - FuncExpr *func = list_nth(cscan->custom_private, GFP_GapfillFunc); - FromExpr *jt = list_nth(cscan->custom_private, GFP_JoinTree); + Oid gapfill_typid = gapfill_ctx->typid; + List *args = gapfill_ctx->args; + FromExpr *jt = gapfill_ctx->jt; + ListCell *lc; Var *ts_var; - TypeCacheEntry *tce = lookup_type_cache(state->gapfill_typid, TYPECACHE_BTREE_OPFAMILY); + TypeCacheEntry *tce = lookup_type_cache(gapfill_typid, TYPECACHE_BTREE_OPFAMILY); int strategy; Oid lefttype, righttype; List *quals; @@ -563,7 +564,7 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary) * if the second argument to time_bucket_gapfill is not a column reference * we cannot match WHERE clause to the time column */ - if (!IsA(lsecond(func->args), Var)) + if (!IsA(lsecond(args), Var)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -572,7 +573,7 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary) errhint("Specify start and finish as arguments or in the WHERE clause."))); } - ts_var = castNode(Var, lsecond(func->args)); + ts_var = castNode(Var, lsecond(args)); quals = collect_boundary_expressions((Node *) jt, ts_var); @@ -613,7 +614,7 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary) * at this stage and Vars will not work either because we execute in * separate execution context */ - if (!is_simple_expr(expr) || !var_equal(ts_var, var)) + if (!gapfill_is_simple_expr(expr) || !var_equal(ts_var, var)) { continue; } @@ -631,7 +632,35 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary) continue; } - value = get_boundary_expr_value(state, boundary, expr); + bool isnull; + Datum arg_value = get_boundary_expr_value(gapfill_ctx, boundary, expr, &isnull); + /* If boundary is NULL bail out on the rest of the quals */ + if (isnull) + { + /* bail out if during execution */ + if (gapfill_ctx->state) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid time_bucket_gapfill argument: %s cannot be NULL", + boundary == GAPFILL_START ? "start" : "finish"), + errhint("Specify start and finish as arguments or in the WHERE clause."))); + } + /* don't estimate the rest of the boundaries but allow planning */ + else + { + Assert(gapfill_ctx->root && gapfill_ctx->estimate_failed); + return INVALID_ESTIMATE; + } + } + /* Cannot estimate this boundary at planning time: reset and try the next one */ + if (gapfill_ctx->root && gapfill_ctx->estimate_failed) + { + gapfill_ctx->estimate_failed = false; + continue; + } + + value = gapfill_datum_get_internal(arg_value, gapfill_ctx->typid); /* * if the boundary expression operator does not match the operator @@ -669,6 +698,15 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary) return boundary_value; } + /* Could not estimate any boundaries at planning time */ + if (gapfill_ctx->root) + { + gapfill_ctx->estimate_failed = true; + return INVALID_ESTIMATE; + } + + /* Cannot evaluate boundaries at execution time: bail out */ + Assert(gapfill_ctx->state); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("missing time_bucket_gapfill argument: could not infer %s from WHERE clause", @@ -677,7 +715,7 @@ infer_gapfill_boundary(GapFillState *state, GapFillBoundary boundary) pg_unreachable(); } -static Const * +Const * make_const_value_for_gapfill_internal(Oid typid, int64 value) { TypeCacheEntry *tce = lookup_type_cache(typid, 0); @@ -807,7 +845,7 @@ gapfill_initialize_arguments(GapFillState *state) Datum arg_value; /* bucket_width */ - if (!is_simple_expr(linitial(state->args))) + if (!gapfill_is_simple_expr(linitial(state->args))) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -823,10 +861,10 @@ gapfill_initialize_arguments(GapFillState *state) errmsg("invalid time_bucket_gapfill argument: bucket_width cannot be NULL"))); } - state->gapfill_period = gapfill_period_get_internal(state->gapfill_typid, - exprType(linitial(state->args)), - arg_value, - &state->gapfill_interval); + state->gapfill_period = gapfill_bucket_width_get_internal(state->gapfill_typid, + exprType(linitial(state->args)), + arg_value, + &state->gapfill_interval); /* * this would error when trying to align start and stop to bucket_width as well below @@ -844,9 +882,19 @@ gapfill_initialize_arguments(GapFillState *state) * check if gapfill start was left out so we have to infer from WHERE * clause */ - if (is_const_null(get_start_arg(state))) + CustomScan *cscan = castNode(CustomScan, state->csstate.ss.ps.plan); + GapFillArgEvalContext gapfill_exec_ctx = { + .root = NULL, + .state = (CustomScanState *) state, + .typid = state->gapfill_typid, + .args = state->args, + .jt = list_nth(cscan->custom_private, GFP_JoinTree), + .estimate_failed = false, + }; + + if (gapfill_is_const_null(get_start_arg(state))) { - int64 start = infer_gapfill_boundary(state, GAPFILL_START); + int64 start = infer_gapfill_boundary(&gapfill_exec_ctx, GAPFILL_START); Const *expr = make_const_value_for_gapfill_internal(state->gapfill_typid, start); state->gapfill_start = align_with_time_bucket(state, (Expr *) expr); @@ -863,13 +911,13 @@ gapfill_initialize_arguments(GapFillState *state) state->next_offset = state->gapfill_interval; /* gap fill end */ - if (is_const_null(get_finish_arg(state))) + if (gapfill_is_const_null(get_finish_arg(state))) { - state->gapfill_end = infer_gapfill_boundary(state, GAPFILL_END); + state->gapfill_end = infer_gapfill_boundary(&gapfill_exec_ctx, GAPFILL_END); } else { - if (!is_simple_expr(get_finish_arg(state))) + if (!gapfill_is_simple_expr(get_finish_arg(state))) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), diff --git a/tsl/src/nodes/gapfill/gapfill_internal.h b/tsl/src/nodes/gapfill/gapfill_internal.h index ea300c15745..dc8f8f69ae5 100644 --- a/tsl/src/nodes/gapfill/gapfill_internal.h +++ b/tsl/src/nodes/gapfill/gapfill_internal.h @@ -126,4 +126,3 @@ typedef struct GapFillState Node *gapfill_state_create(CustomScan *); Expr *gapfill_adjust_varnos(GapFillState *state, Expr *expr); Datum gapfill_exec_expr(GapFillState *state, TupleTableSlot *, Expr *expr, bool *isnull); -int64 gapfill_datum_get_internal(Datum, Oid); diff --git a/tsl/src/nodes/gapfill/gapfill_plan.c b/tsl/src/nodes/gapfill/gapfill_plan.c index dc2aae766e0..9211e4264ad 100644 --- a/tsl/src/nodes/gapfill/gapfill_plan.c +++ b/tsl/src/nodes/gapfill/gapfill_plan.c @@ -16,9 +16,11 @@ #include #include #include +#include #include "compat/compat.h" +#include "estimate.h" #include "gapfill.h" #include "gapfill_internal.h" #include "import/list.h" @@ -242,6 +244,140 @@ gapfill_correct_order(PlannerInfo *root, Path *subpath, FuncExpr *func) return false; } +/* + * Estimate simple expression + * and if it resolves to a non-null Const evaluate it, + * otherwise set it to NULL. + */ +Datum +gapfill_estimate_arg(GapFillArgEvalContext *gapfill_plan_ctx, Node *arg, bool *isnull) +{ + Assert(gapfill_plan_ctx->root); + Node *est_arg = estimate_expression_value(gapfill_plan_ctx->root, arg); + if (est_arg && IsA(est_arg, Const)) + { + if (gapfill_is_const_null((Expr *) est_arg)) + { + /* Bail out on estimation if some arguments are NULL */ + gapfill_plan_ctx->estimate_failed = true; + *isnull = true; + } + return castNode(Const, est_arg)->constvalue; + } + else + { + gapfill_plan_ctx->estimate_failed = true; + return (Datum) 0; + } +} + +static int64 +gapfill_estimate_boundary(GapFillArgEvalContext *gapfill_plan_ctx, GapFillBoundary boundary) +{ + List *args = gapfill_plan_ctx->args; + int64 result = -1; + Node *arg = NULL; + if (boundary == GAPFILL_START) + { + arg = (list_length(args) < 5 ? lthird(args) : lfourth(args)); + } + else + { + arg = (list_length(args) < 5 ? lfourth(args) : lfifth(args)); + } + + if (gapfill_is_const_null((Expr *) arg)) + { + result = infer_gapfill_boundary(gapfill_plan_ctx, boundary); + } + else + { + bool isnull = false; + Datum value = gapfill_estimate_arg(gapfill_plan_ctx, arg, &isnull); + if (!gapfill_plan_ctx->estimate_failed) + { + Assert(!isnull); + result = gapfill_datum_get_internal(value, gapfill_plan_ctx->typid); + } + } + return result; +} + +/* Estimate bucket_width in int64 internal format. + * For variable-length intervals use approximation. + * Error out on invalid bucket_width. */ +static int64 +estimate_and_check_gapfill_bucket_width(GapFillArgEvalContext *gapfill_plan_ctx) +{ + Node *bucket_width_arg = linitial(gapfill_plan_ctx->args); + bool isnull = false; + Datum arg_value = gapfill_estimate_arg(gapfill_plan_ctx, bucket_width_arg, &isnull); + /* Cannot estimate bucket_width now: let it be checked during execution */ + if (gapfill_plan_ctx->estimate_failed) + { + return INVALID_ESTIMATE; + } + Assert(!isnull); + Interval *gapfill_interval = NULL; + int64 gapfill_bucket_width = + gapfill_bucket_width_get_internal(gapfill_plan_ctx->typid, + exprType((Node *) bucket_width_arg), + arg_value, + &gapfill_interval); + if (gapfill_interval) + { + gapfill_bucket_width = ts_get_interval_period_approx(gapfill_interval); + } + + /* negative bucket_width can be reported here */ + if (gapfill_bucket_width <= 0) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg( + "invalid time_bucket_gapfill argument: bucket_width must be greater than 0"))); + } + + return gapfill_bucket_width; +} + +/* Try to get start and finish from args or from WHERE clause, + * estimate bucket_width including when it's a variable-length interval, + * bail out and return 0 rows if start/finish/bucket_width are missing or invalid: + * we will fall back to subplan rows in this case. */ +static double +check_args_and_estimate_gapfill_rows(PlannerInfo *root, FuncExpr *func) +{ + GapFillArgEvalContext gapfill_plan_ctx = { + .root = root, + .state = NULL, + .typid = func->funcresulttype, + .args = func->args, + .jt = root->parse->jointree, + .estimate_failed = false, + }; + + int64 bucket_width = estimate_and_check_gapfill_bucket_width(&gapfill_plan_ctx); + if (gapfill_plan_ctx.estimate_failed) + { + return 0; + } + int64 start = gapfill_estimate_boundary(&gapfill_plan_ctx, GAPFILL_START); + if (gapfill_plan_ctx.estimate_failed) + { + return 0; + } + int64 finish = gapfill_estimate_boundary(&gapfill_plan_ctx, GAPFILL_END); + if (gapfill_plan_ctx.estimate_failed) + { + return 0; + } + + Assert(bucket_width > 0); + /* Cap row count at 0 if (start > finish) */ + return Max(0.0, (((double) finish - (double) start) / (double) bucket_width)); +} + /* Create a gapfill plan node in the form of a CustomScan node. The * purpose of this plan node is to insert tuples for missing groups. * @@ -441,6 +577,38 @@ gapfill_build_pathtarget(PathTarget *pt_upper, PathTarget *pt_path, PathTarget * } } +static int +estimate_gapfill_groups(PlannerInfo *root, int path_rows) +{ +#if PG16_GE + List *group_exprs = get_sortgrouplist_exprs(root->processed_groupClause, root->processed_tlist); +#else + List *group_exprs = get_sortgrouplist_exprs(root->parse->groupClause, root->parse->targetList); +#endif + /* If we group on something beside time_bucket_gapfill, estimate number of groups */ + List *group_exprs_without_gapfill = NULL; + if (list_length(group_exprs) > 1) + { + ListCell *lc; + foreach (lc, group_exprs) + { + Node *group_expr = lfirst(lc); + if (IsA(group_expr, FuncExpr) && + is_gapfill_function_call(castNode(FuncExpr, group_expr))) + { + continue; + } + group_exprs_without_gapfill = lappend(group_exprs_without_gapfill, group_expr); + } + } + int num_groups = 1; + if (group_exprs_without_gapfill != NULL) + { + num_groups = estimate_num_groups(root, group_exprs_without_gapfill, path_rows, NULL, NULL); + } + return num_groups; +} + /* * Create a Gapfill Path node. * @@ -457,12 +625,28 @@ gapfill_path_create(PlannerInfo *root, Path *subpath, FuncExpr *func) path->cpath.path.pathtype = T_CustomScan; path->cpath.methods = &gapfill_path_methods; + /* try to infer start/end boundaries and a bucket_width for time_bucket_gapfill in int64 + * internal format, estimate rows as (end-start)/(bucket_width), return 0 if + * start/end/bucket_width are not inferable. + */ + double gapfill_rows = check_args_and_estimate_gapfill_rows(root, func); + + /* If we can estimate gapfills, we should estimate number of non-gapfill groups + * as gapfills will be repeated for each group. */ + int num_groups = 1; + if (gapfill_rows > 0) + { + num_groups = estimate_gapfill_groups(root, subpath->rows); + } /* * parallel_safe must be false because it is not safe to execute this node * in parallel, but it is safe for child nodes to be parallel */ Assert(!path->cpath.path.parallel_safe); - path->cpath.path.rows = subpath->rows; + /* we may have filled gaps in addition to original tuples + * filled gaps set per each group */ + path->cpath.path.rows = subpath->rows + gapfill_rows * num_groups; + path->cpath.path.parent = subpath->parent; path->cpath.path.param_info = subpath->param_info; path->cpath.flags = 0; diff --git a/tsl/src/nodes/gapfill/interpolate.c b/tsl/src/nodes/gapfill/interpolate.c index 3e7eff66d45..55366f1d1dd 100644 --- a/tsl/src/nodes/gapfill/interpolate.c +++ b/tsl/src/nodes/gapfill/interpolate.c @@ -13,6 +13,7 @@ #include #include "compat/compat.h" +#include "gapfill.h" #include "gapfill_internal.h" #include "interpolate.h" diff --git a/tsl/test/shared/expected/gapfill-15.out b/tsl/test/shared/expected/gapfill-15.out index 78a84466d77..d60b0e57454 100644 --- a/tsl/test/shared/expected/gapfill-15.out +++ b/tsl/test/shared/expected/gapfill-15.out @@ -3751,3 +3751,358 @@ HINT: Specify start and finish as arguments or in the WHERE clause. \set ON_ERROR_STOP 1 reset enable_indexscan; reset enable_bitmapscan; +-- Gapfill row count estimate should be ~((finish - start)/period + 1) instead of subpath row count, +-- otherwise Gapfill can have row count = 0 if range is outside of known data. +CREATE TABLE metrics_int_loc( + time int NOT NULL, + device_id int, + sensor_id int, + value float); +INSERT INTO metrics_int_loc VALUES + (-100,1,1,0.0), + (-100,1,2,-100.0), + (0,1,1,5.0), + (5,1,2,10.0), + (100,1,1,0.0), + (100,1,2,-100.0); +ANALYZE metrics_int_loc; +CREATE TABLE metrics_tstz_loc(time timestamptz, device_id INT, v1 float, v2 int); +INSERT INTO metrics_tstz_loc VALUES + (timestamptz '2018-01-01 05:00:00 PST', 1, 0.5, 10), + (timestamptz '2018-01-01 05:00:00 PST', 2, 0.7, 20), + (timestamptz '2018-01-01 05:00:00 PST', 3, 0.9, 30), + (timestamptz '2018-01-01 07:00:00 PST', 1, 0.0, 0), + (timestamptz '2018-01-01 07:00:00 PST', 2, 1.4, 40), + (timestamptz '2018-01-01 07:00:00 PST', 3, 0.9, 30) +; +ANALYZE metrics_tstz_loc; +\set PREFIX 'EXPLAIN (analyze, timing off, summary off, buffers off)' +-- Date/time period +-- gapfill inside known data +-- subpath row count > gapfill row count +:PREFIX SELECT + time_bucket_gapfill(interval '1h',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..1.17 rows=4 width=16) (actual rows=3.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 hour'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 1 hour'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- subpath row count < gapfill row count +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..8.36 rows=1442 width=16) (actual rows=1441.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- Estimate boundaries in WHERE +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-01 06:00:00-8' AND time < '2018-01-01 06:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..2.93 rows=361 width=16) (actual rows=360.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Mon Jan 01 06:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Mon Jan 01 06:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- gapfill outside known data +-- includes known data +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time, timestamptz '2018-01-02 06:00:00-8', timestamptz '2018-01-02 06:30:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..1.31 rows=32 width=16) (actual rows=32.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone, 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 1 min'::interval, "time", 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone, 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- excludes known data +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-02 06:00:00-8' AND time < '2018-01-02 06:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.28 rows=31 width=16) (actual rows=30.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- start > finish: should estimate as subplan rows +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-01 07:00:00-8' AND time < '2018-01-01 05:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=1 width=16) (actual rows=0.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Mon Jan 01 05:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- multiple groups: estimation should be multiplied by number of groups +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + device_id, + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1, 2 +ORDER BY 1; +--- QUERY PLAN --- + Sort (cost=262.26..294.68 rows=4323 width=20) (actual rows=4323.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> Custom Scan (GapFill) (cost=1.19..1.20 rows=4323 width=0) (actual rows=4323.00 loops=1) + -> Sort (cost=1.19..1.20 rows=3 width=0) (actual rows=6.00 loops=1) + Sort Key: device_id, (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.12..1.17 rows=3 width=0) (actual rows=6.00 loops=1) + Group Key: time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone), device_id + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=20) (actual rows=6.00 loops=1) + +-- Integer period +-- gapfill inside known data +-- subpath row count > gapfill row count +:PREFIX SELECT + time_bucket_gapfill(5,time,0,5), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.24 rows=5 width=12) (actual rows=4.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", 0, 5)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(5, "time", 0, 5) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- subpath row count < gapfill row count +:PREFIX SELECT + time_bucket_gapfill(1,time,-100,100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..2.23 rows=204 width=12) (actual rows=201.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", '-100'::integer, 100) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- Estimate boundaries in WHERE +:PREFIX SELECT + time_bucket_gapfill(1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= 0 AND time < 50 AND time >= 2*5 +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.12..1.35 rows=41 width=12) (actual rows=40.00 loops=1) + -> GroupAggregate (cost=1.12..1.14 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.12..1.12 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.11 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= 0) AND ("time" < 50) AND ("time" >= 10)) + Rows Removed by Filter: 6 + +-- gapfill outside known data +-- includes known data +:PREFIX SELECT + time_bucket_gapfill(5,time,200,300), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.33 rows=24 width=12) (actual rows=24.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", 200, 300)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(5, "time", 200, 300) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- excludes known data +:PREFIX SELECT + time_bucket_gapfill(5,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= 200 AND time < 300 +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.23 rows=21 width=12) (actual rows=20.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(5, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.10..1.11 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.09 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= 200) AND ("time" < 300)) + Rows Removed by Filter: 6 + +-- start > finish: should estimate as subplan rows +:PREFIX SELECT + time_bucket_gapfill(1,time,100,-100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.23 rows=4 width=12) (actual rows=4.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", 100, '-100'::integer)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", 100, '-100'::integer) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- multiple groups: estimation should be multiplied by number of groups +:PREFIX SELECT + time_bucket_gapfill(1,time,-100,100), sensor_id, + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1, 2 +ORDER BY 1; +--- QUERY PLAN --- + Sort (cost=18.72..21.75 rows=404 width=16) (actual rows=402.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> Custom Scan (GapFill) (cost=1.22..1.23 rows=404 width=0) (actual rows=402.00 loops=1) + -> Sort (cost=1.22..1.23 rows=4 width=0) (actual rows=6.00 loops=1) + Sort Key: sensor_id, (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.12..1.18 rows=4 width=0) (actual rows=6.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", '-100'::integer, 100), sensor_id + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- Gapfill row count estimate is not done when Consts are not set at planning time +SET plan_cache_mode TO 'force_generic_plan'; +PREPARE prep_int1(int) AS +SELECT + time_bucket_gapfill($1,time,0,100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int1(1); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.23 rows=4 width=12) (actual rows=102.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill($1, "time", 0, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill($1, "time", 0, 100) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +PREPARE prep_int2(int,int) AS +SELECT + time_bucket_gapfill(1,time,$1,$2), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int2(0,100); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.23 rows=4 width=12) (actual rows=102.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", $1, $2)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", $1, $2) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +PREPARE prep_int3(int,int,int) AS +SELECT + time_bucket_gapfill($1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= $2 AND time < $3 +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int3(1,0,100); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=1 width=12) (actual rows=100.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=2.00 loops=1) + Group Key: (time_bucket_gapfill($1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.10..1.11 rows=1 width=12) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill($1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.09 rows=1 width=12) (actual rows=2.00 loops=1) + Filter: (("time" >= $2) AND ("time" < $3)) + Rows Removed by Filter: 4 + +-- If some quals have parameter boundaries we can still estimate boundaries in other quals +PREPARE prep_int4(int,int) AS +SELECT + time_bucket_gapfill(1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= $1 AND time < $2 AND time >= 1 AND time < 100 +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int4(10,30); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.13..1.66 rows=100 width=12) (actual rows=20.00 loops=1) + -> GroupAggregate (cost=1.13..1.16 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.13..1.14 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.12 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= $1) AND ("time" < $2) AND ("time" >= 1) AND ("time" < 100)) + Rows Removed by Filter: 6 + +DEALLOCATE prep_int1; +DEALLOCATE prep_int2; +DEALLOCATE prep_int3; +DEALLOCATE prep_int4; +RESET plan_cache_mode; +DROP TABLE metrics_int_loc cascade; +DROP TABLE metrics_tstz_loc cascade; diff --git a/tsl/test/shared/expected/gapfill-16.out b/tsl/test/shared/expected/gapfill-16.out index 39e9146db7b..4120b5c9770 100644 --- a/tsl/test/shared/expected/gapfill-16.out +++ b/tsl/test/shared/expected/gapfill-16.out @@ -3751,3 +3751,358 @@ HINT: Specify start and finish as arguments or in the WHERE clause. \set ON_ERROR_STOP 1 reset enable_indexscan; reset enable_bitmapscan; +-- Gapfill row count estimate should be ~((finish - start)/period + 1) instead of subpath row count, +-- otherwise Gapfill can have row count = 0 if range is outside of known data. +CREATE TABLE metrics_int_loc( + time int NOT NULL, + device_id int, + sensor_id int, + value float); +INSERT INTO metrics_int_loc VALUES + (-100,1,1,0.0), + (-100,1,2,-100.0), + (0,1,1,5.0), + (5,1,2,10.0), + (100,1,1,0.0), + (100,1,2,-100.0); +ANALYZE metrics_int_loc; +CREATE TABLE metrics_tstz_loc(time timestamptz, device_id INT, v1 float, v2 int); +INSERT INTO metrics_tstz_loc VALUES + (timestamptz '2018-01-01 05:00:00 PST', 1, 0.5, 10), + (timestamptz '2018-01-01 05:00:00 PST', 2, 0.7, 20), + (timestamptz '2018-01-01 05:00:00 PST', 3, 0.9, 30), + (timestamptz '2018-01-01 07:00:00 PST', 1, 0.0, 0), + (timestamptz '2018-01-01 07:00:00 PST', 2, 1.4, 40), + (timestamptz '2018-01-01 07:00:00 PST', 3, 0.9, 30) +; +ANALYZE metrics_tstz_loc; +\set PREFIX 'EXPLAIN (analyze, timing off, summary off, buffers off)' +-- Date/time period +-- gapfill inside known data +-- subpath row count > gapfill row count +:PREFIX SELECT + time_bucket_gapfill(interval '1h',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..1.17 rows=4 width=16) (actual rows=3.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 hour'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 1 hour'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- subpath row count < gapfill row count +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..8.36 rows=1442 width=16) (actual rows=1441.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- Estimate boundaries in WHERE +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-01 06:00:00-8' AND time < '2018-01-01 06:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..2.93 rows=361 width=16) (actual rows=360.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Mon Jan 01 06:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Mon Jan 01 06:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- gapfill outside known data +-- includes known data +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time, timestamptz '2018-01-02 06:00:00-8', timestamptz '2018-01-02 06:30:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..1.31 rows=32 width=16) (actual rows=32.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone, 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 1 min'::interval, "time", 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone, 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- excludes known data +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-02 06:00:00-8' AND time < '2018-01-02 06:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.28 rows=31 width=16) (actual rows=30.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- start > finish: should estimate as subplan rows +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-01 07:00:00-8' AND time < '2018-01-01 05:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=1 width=16) (actual rows=0.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Mon Jan 01 05:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- multiple groups: estimation should be multiplied by number of groups +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + device_id, + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1, 2 +ORDER BY 1; +--- QUERY PLAN --- + Sort (cost=262.26..294.68 rows=4323 width=20) (actual rows=4323.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> Custom Scan (GapFill) (cost=1.19..1.20 rows=4323 width=0) (actual rows=4323.00 loops=1) + -> Sort (cost=1.19..1.20 rows=3 width=0) (actual rows=6.00 loops=1) + Sort Key: device_id, (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.12..1.17 rows=3 width=0) (actual rows=6.00 loops=1) + Group Key: time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone), device_id + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=20) (actual rows=6.00 loops=1) + +-- Integer period +-- gapfill inside known data +-- subpath row count > gapfill row count +:PREFIX SELECT + time_bucket_gapfill(5,time,0,5), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.24 rows=5 width=12) (actual rows=4.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", 0, 5)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(5, "time", 0, 5) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- subpath row count < gapfill row count +:PREFIX SELECT + time_bucket_gapfill(1,time,-100,100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..2.23 rows=204 width=12) (actual rows=201.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", '-100'::integer, 100) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- Estimate boundaries in WHERE +:PREFIX SELECT + time_bucket_gapfill(1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= 0 AND time < 50 AND time >= 2*5 +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.12..1.35 rows=41 width=12) (actual rows=40.00 loops=1) + -> GroupAggregate (cost=1.12..1.14 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.12..1.12 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.11 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= 0) AND ("time" < 50) AND ("time" >= 10)) + Rows Removed by Filter: 6 + +-- gapfill outside known data +-- includes known data +:PREFIX SELECT + time_bucket_gapfill(5,time,200,300), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.33 rows=24 width=12) (actual rows=24.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", 200, 300)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(5, "time", 200, 300) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- excludes known data +:PREFIX SELECT + time_bucket_gapfill(5,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= 200 AND time < 300 +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.23 rows=21 width=12) (actual rows=20.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(5, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.10..1.11 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.09 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= 200) AND ("time" < 300)) + Rows Removed by Filter: 6 + +-- start > finish: should estimate as subplan rows +:PREFIX SELECT + time_bucket_gapfill(1,time,100,-100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.23 rows=4 width=12) (actual rows=4.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", 100, '-100'::integer)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", 100, '-100'::integer) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- multiple groups: estimation should be multiplied by number of groups +:PREFIX SELECT + time_bucket_gapfill(1,time,-100,100), sensor_id, + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1, 2 +ORDER BY 1; +--- QUERY PLAN --- + Sort (cost=18.72..21.75 rows=404 width=16) (actual rows=402.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> Custom Scan (GapFill) (cost=1.22..1.23 rows=404 width=0) (actual rows=402.00 loops=1) + -> Sort (cost=1.22..1.23 rows=4 width=0) (actual rows=6.00 loops=1) + Sort Key: sensor_id, (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.12..1.18 rows=4 width=0) (actual rows=6.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", '-100'::integer, 100), sensor_id + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- Gapfill row count estimate is not done when Consts are not set at planning time +SET plan_cache_mode TO 'force_generic_plan'; +PREPARE prep_int1(int) AS +SELECT + time_bucket_gapfill($1,time,0,100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int1(1); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.23 rows=4 width=12) (actual rows=102.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill($1, "time", 0, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill($1, "time", 0, 100) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +PREPARE prep_int2(int,int) AS +SELECT + time_bucket_gapfill(1,time,$1,$2), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int2(0,100); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.23 rows=4 width=12) (actual rows=102.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", $1, $2)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", $1, $2) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +PREPARE prep_int3(int,int,int) AS +SELECT + time_bucket_gapfill($1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= $2 AND time < $3 +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int3(1,0,100); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=1 width=12) (actual rows=100.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=2.00 loops=1) + Group Key: (time_bucket_gapfill($1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.10..1.11 rows=1 width=12) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill($1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.09 rows=1 width=12) (actual rows=2.00 loops=1) + Filter: (("time" >= $2) AND ("time" < $3)) + Rows Removed by Filter: 4 + +-- If some quals have parameter boundaries we can still estimate boundaries in other quals +PREPARE prep_int4(int,int) AS +SELECT + time_bucket_gapfill(1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= $1 AND time < $2 AND time >= 1 AND time < 100 +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int4(10,30); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.13..1.66 rows=100 width=12) (actual rows=20.00 loops=1) + -> GroupAggregate (cost=1.13..1.16 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.13..1.14 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.12 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= $1) AND ("time" < $2) AND ("time" >= 1) AND ("time" < 100)) + Rows Removed by Filter: 6 + +DEALLOCATE prep_int1; +DEALLOCATE prep_int2; +DEALLOCATE prep_int3; +DEALLOCATE prep_int4; +RESET plan_cache_mode; +DROP TABLE metrics_int_loc cascade; +DROP TABLE metrics_tstz_loc cascade; diff --git a/tsl/test/shared/expected/gapfill-17.out b/tsl/test/shared/expected/gapfill-17.out index 55d0a2aa03d..6ee0f43fd02 100644 --- a/tsl/test/shared/expected/gapfill-17.out +++ b/tsl/test/shared/expected/gapfill-17.out @@ -3751,3 +3751,358 @@ HINT: Specify start and finish as arguments or in the WHERE clause. \set ON_ERROR_STOP 1 reset enable_indexscan; reset enable_bitmapscan; +-- Gapfill row count estimate should be ~((finish - start)/period + 1) instead of subpath row count, +-- otherwise Gapfill can have row count = 0 if range is outside of known data. +CREATE TABLE metrics_int_loc( + time int NOT NULL, + device_id int, + sensor_id int, + value float); +INSERT INTO metrics_int_loc VALUES + (-100,1,1,0.0), + (-100,1,2,-100.0), + (0,1,1,5.0), + (5,1,2,10.0), + (100,1,1,0.0), + (100,1,2,-100.0); +ANALYZE metrics_int_loc; +CREATE TABLE metrics_tstz_loc(time timestamptz, device_id INT, v1 float, v2 int); +INSERT INTO metrics_tstz_loc VALUES + (timestamptz '2018-01-01 05:00:00 PST', 1, 0.5, 10), + (timestamptz '2018-01-01 05:00:00 PST', 2, 0.7, 20), + (timestamptz '2018-01-01 05:00:00 PST', 3, 0.9, 30), + (timestamptz '2018-01-01 07:00:00 PST', 1, 0.0, 0), + (timestamptz '2018-01-01 07:00:00 PST', 2, 1.4, 40), + (timestamptz '2018-01-01 07:00:00 PST', 3, 0.9, 30) +; +ANALYZE metrics_tstz_loc; +\set PREFIX 'EXPLAIN (analyze, timing off, summary off, buffers off)' +-- Date/time period +-- gapfill inside known data +-- subpath row count > gapfill row count +:PREFIX SELECT + time_bucket_gapfill(interval '1h',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..1.17 rows=4 width=16) (actual rows=3.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 hour'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 1 hour'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- subpath row count < gapfill row count +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..8.36 rows=1442 width=16) (actual rows=1441.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- Estimate boundaries in WHERE +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-01 06:00:00-8' AND time < '2018-01-01 06:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..2.93 rows=361 width=16) (actual rows=360.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Mon Jan 01 06:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Mon Jan 01 06:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- gapfill outside known data +-- includes known data +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time, timestamptz '2018-01-02 06:00:00-8', timestamptz '2018-01-02 06:30:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..1.31 rows=32 width=16) (actual rows=32.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone, 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 1 min'::interval, "time", 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone, 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- excludes known data +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-02 06:00:00-8' AND time < '2018-01-02 06:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.28 rows=31 width=16) (actual rows=30.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- start > finish: should estimate as subplan rows +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-01 07:00:00-8' AND time < '2018-01-01 05:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=1 width=16) (actual rows=0.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Mon Jan 01 05:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- multiple groups: estimation should be multiplied by number of groups +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + device_id, + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1, 2 +ORDER BY 1; +--- QUERY PLAN --- + Sort (cost=262.26..294.68 rows=4323 width=20) (actual rows=4323.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> Custom Scan (GapFill) (cost=1.19..1.20 rows=4323 width=0) (actual rows=4323.00 loops=1) + -> Sort (cost=1.19..1.20 rows=3 width=0) (actual rows=6.00 loops=1) + Sort Key: device_id, (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.12..1.17 rows=3 width=0) (actual rows=6.00 loops=1) + Group Key: time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone), device_id + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=20) (actual rows=6.00 loops=1) + +-- Integer period +-- gapfill inside known data +-- subpath row count > gapfill row count +:PREFIX SELECT + time_bucket_gapfill(5,time,0,5), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.24 rows=5 width=12) (actual rows=4.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", 0, 5)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(5, "time", 0, 5) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- subpath row count < gapfill row count +:PREFIX SELECT + time_bucket_gapfill(1,time,-100,100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..2.23 rows=204 width=12) (actual rows=201.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", '-100'::integer, 100) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- Estimate boundaries in WHERE +:PREFIX SELECT + time_bucket_gapfill(1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= 0 AND time < 50 AND time >= 2*5 +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.12..1.35 rows=41 width=12) (actual rows=40.00 loops=1) + -> GroupAggregate (cost=1.12..1.14 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.12..1.12 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.11 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= 0) AND ("time" < 50) AND ("time" >= 10)) + Rows Removed by Filter: 6 + +-- gapfill outside known data +-- includes known data +:PREFIX SELECT + time_bucket_gapfill(5,time,200,300), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.33 rows=24 width=12) (actual rows=24.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", 200, 300)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(5, "time", 200, 300) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- excludes known data +:PREFIX SELECT + time_bucket_gapfill(5,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= 200 AND time < 300 +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.23 rows=21 width=12) (actual rows=20.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(5, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.10..1.11 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.09 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= 200) AND ("time" < 300)) + Rows Removed by Filter: 6 + +-- start > finish: should estimate as subplan rows +:PREFIX SELECT + time_bucket_gapfill(1,time,100,-100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.23 rows=4 width=12) (actual rows=4.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", 100, '-100'::integer)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", 100, '-100'::integer) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- multiple groups: estimation should be multiplied by number of groups +:PREFIX SELECT + time_bucket_gapfill(1,time,-100,100), sensor_id, + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1, 2 +ORDER BY 1; +--- QUERY PLAN --- + Sort (cost=18.72..21.75 rows=404 width=16) (actual rows=402.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> Custom Scan (GapFill) (cost=1.22..1.23 rows=404 width=0) (actual rows=402.00 loops=1) + -> Sort (cost=1.22..1.23 rows=4 width=0) (actual rows=6.00 loops=1) + Sort Key: sensor_id, (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.12..1.18 rows=4 width=0) (actual rows=6.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", '-100'::integer, 100), sensor_id + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- Gapfill row count estimate is not done when Consts are not set at planning time +SET plan_cache_mode TO 'force_generic_plan'; +PREPARE prep_int1(int) AS +SELECT + time_bucket_gapfill($1,time,0,100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int1(1); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.23 rows=4 width=12) (actual rows=102.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill($1, "time", 0, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill($1, "time", 0, 100) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +PREPARE prep_int2(int,int) AS +SELECT + time_bucket_gapfill(1,time,$1,$2), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int2(0,100); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.23 rows=4 width=12) (actual rows=102.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", $1, $2)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", $1, $2) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +PREPARE prep_int3(int,int,int) AS +SELECT + time_bucket_gapfill($1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= $2 AND time < $3 +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int3(1,0,100); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=1 width=12) (actual rows=100.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=2.00 loops=1) + Group Key: (time_bucket_gapfill($1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.10..1.11 rows=1 width=12) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill($1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.09 rows=1 width=12) (actual rows=2.00 loops=1) + Filter: (("time" >= $2) AND ("time" < $3)) + Rows Removed by Filter: 4 + +-- If some quals have parameter boundaries we can still estimate boundaries in other quals +PREPARE prep_int4(int,int) AS +SELECT + time_bucket_gapfill(1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= $1 AND time < $2 AND time >= 1 AND time < 100 +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int4(10,30); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.13..1.66 rows=100 width=12) (actual rows=20.00 loops=1) + -> GroupAggregate (cost=1.13..1.16 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.13..1.14 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.12 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= $1) AND ("time" < $2) AND ("time" >= 1) AND ("time" < 100)) + Rows Removed by Filter: 6 + +DEALLOCATE prep_int1; +DEALLOCATE prep_int2; +DEALLOCATE prep_int3; +DEALLOCATE prep_int4; +RESET plan_cache_mode; +DROP TABLE metrics_int_loc cascade; +DROP TABLE metrics_tstz_loc cascade; diff --git a/tsl/test/shared/expected/gapfill-18.out b/tsl/test/shared/expected/gapfill-18.out index 9779ca1a2bb..8587565d6a2 100644 --- a/tsl/test/shared/expected/gapfill-18.out +++ b/tsl/test/shared/expected/gapfill-18.out @@ -3754,3 +3754,358 @@ HINT: Specify start and finish as arguments or in the WHERE clause. \set ON_ERROR_STOP 1 reset enable_indexscan; reset enable_bitmapscan; +-- Gapfill row count estimate should be ~((finish - start)/period + 1) instead of subpath row count, +-- otherwise Gapfill can have row count = 0 if range is outside of known data. +CREATE TABLE metrics_int_loc( + time int NOT NULL, + device_id int, + sensor_id int, + value float); +INSERT INTO metrics_int_loc VALUES + (-100,1,1,0.0), + (-100,1,2,-100.0), + (0,1,1,5.0), + (5,1,2,10.0), + (100,1,1,0.0), + (100,1,2,-100.0); +ANALYZE metrics_int_loc; +CREATE TABLE metrics_tstz_loc(time timestamptz, device_id INT, v1 float, v2 int); +INSERT INTO metrics_tstz_loc VALUES + (timestamptz '2018-01-01 05:00:00 PST', 1, 0.5, 10), + (timestamptz '2018-01-01 05:00:00 PST', 2, 0.7, 20), + (timestamptz '2018-01-01 05:00:00 PST', 3, 0.9, 30), + (timestamptz '2018-01-01 07:00:00 PST', 1, 0.0, 0), + (timestamptz '2018-01-01 07:00:00 PST', 2, 1.4, 40), + (timestamptz '2018-01-01 07:00:00 PST', 3, 0.9, 30) +; +ANALYZE metrics_tstz_loc; +\set PREFIX 'EXPLAIN (analyze, timing off, summary off, buffers off)' +-- Date/time period +-- gapfill inside known data +-- subpath row count > gapfill row count +:PREFIX SELECT + time_bucket_gapfill(interval '1h',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..1.15 rows=4 width=0) (actual rows=3.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 hour'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 1 hour'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- subpath row count < gapfill row count +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..1.15 rows=1442 width=0) (actual rows=1441.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- Estimate boundaries in WHERE +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-01 06:00:00-8' AND time < '2018-01-01 06:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=361 width=0) (actual rows=360.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Mon Jan 01 06:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Mon Jan 01 06:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- gapfill outside known data +-- includes known data +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time, timestamptz '2018-01-02 06:00:00-8', timestamptz '2018-01-02 06:30:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.14..1.15 rows=32 width=0) (actual rows=32.00 loops=1) + -> Sort (cost=1.14..1.15 rows=2 width=0) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone, 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.13 rows=2 width=0) (actual rows=2.00 loops=1) + Group Key: time_bucket_gapfill('@ 1 min'::interval, "time", 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone, 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone) + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- excludes known data +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-02 06:00:00-8' AND time < '2018-01-02 06:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=31 width=0) (actual rows=30.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Tue Jan 02 06:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Tue Jan 02 06:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- start > finish: should estimate as subplan rows +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-01 07:00:00-8' AND time < '2018-01-01 05:30:00-8' +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + -> Sort (cost=1.10..1.11 rows=1 width=16) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 1 min'::interval, "time", NULL::timestamp with time zone, NULL::timestamp with time zone)) + Sort Method: quicksort + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.09 rows=1 width=16) (actual rows=0.00 loops=1) + Filter: (("time" >= 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone) AND ("time" < 'Mon Jan 01 05:30:00 2018 PST'::timestamp with time zone)) + Rows Removed by Filter: 6 + +-- multiple groups: estimation should be multiplied by number of groups +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + device_id, + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1, 2 +ORDER BY 1; +--- QUERY PLAN --- + Sort (cost=262.26..273.07 rows=4323 width=0) (actual rows=4323.00 loops=1) + Sort Key: (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> Custom Scan (GapFill) (cost=1.19..1.20 rows=4323 width=0) (actual rows=4323.00 loops=1) + -> Sort (cost=1.19..1.20 rows=3 width=0) (actual rows=6.00 loops=1) + Sort Key: device_id, (time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone)) + Sort Method: quicksort + -> HashAggregate (cost=1.12..1.17 rows=3 width=0) (actual rows=6.00 loops=1) + Group Key: time_bucket_gapfill('@ 5 secs'::interval, "time", 'Mon Jan 01 05:00:00 2018 PST'::timestamp with time zone, 'Mon Jan 01 07:00:00 2018 PST'::timestamp with time zone), device_id + -> Seq Scan on metrics_tstz_loc (cost=0.00..1.07 rows=6 width=20) (actual rows=6.00 loops=1) + +-- Integer period +-- gapfill inside known data +-- subpath row count > gapfill row count +:PREFIX SELECT + time_bucket_gapfill(5,time,0,5), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.21 rows=5 width=0) (actual rows=4.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", 0, 5)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(5, "time", 0, 5) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- subpath row count < gapfill row count +:PREFIX SELECT + time_bucket_gapfill(1,time,-100,100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.21 rows=204 width=0) (actual rows=201.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", '-100'::integer, 100) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- Estimate boundaries in WHERE +:PREFIX SELECT + time_bucket_gapfill(1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= 0 AND time < 50 AND time >= 2*5 +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.12..1.14 rows=41 width=0) (actual rows=40.00 loops=1) + -> GroupAggregate (cost=1.12..1.14 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.12..1.12 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.11 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= 0) AND ("time" < 50) AND ("time" >= 10)) + Rows Removed by Filter: 6 + +-- gapfill outside known data +-- includes known data +:PREFIX SELECT + time_bucket_gapfill(5,time,200,300), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.21 rows=24 width=0) (actual rows=24.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", 200, 300)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(5, "time", 200, 300) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- excludes known data +:PREFIX SELECT + time_bucket_gapfill(5,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= 200 AND time < 300 +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=21 width=0) (actual rows=20.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(5, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.10..1.11 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(5, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.09 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= 200) AND ("time" < 300)) + Rows Removed by Filter: 6 + +-- start > finish: should estimate as subplan rows +:PREFIX SELECT + time_bucket_gapfill(1,time,100,-100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", 100, '-100'::integer)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", 100, '-100'::integer) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +-- multiple groups: estimation should be multiplied by number of groups +:PREFIX SELECT + time_bucket_gapfill(1,time,-100,100), sensor_id, + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1, 2 +ORDER BY 1; +--- QUERY PLAN --- + Sort (cost=18.72..19.73 rows=404 width=0) (actual rows=402.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> Custom Scan (GapFill) (cost=1.22..1.23 rows=404 width=0) (actual rows=402.00 loops=1) + -> Sort (cost=1.22..1.23 rows=4 width=0) (actual rows=6.00 loops=1) + Sort Key: sensor_id, (time_bucket_gapfill(1, "time", '-100'::integer, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.12..1.18 rows=4 width=0) (actual rows=6.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", '-100'::integer, 100), sensor_id + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=16) (actual rows=6.00 loops=1) + +-- Gapfill row count estimate is not done when Consts are not set at planning time +SET plan_cache_mode TO 'force_generic_plan'; +PREPARE prep_int1(int) AS +SELECT + time_bucket_gapfill($1,time,0,100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int1(1); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.21 rows=4 width=0) (actual rows=102.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill($1, "time", 0, 100)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill($1, "time", 0, 100) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +PREPARE prep_int2(int,int) AS +SELECT + time_bucket_gapfill(1,time,$1,$2), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int2(0,100); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.20..1.21 rows=4 width=0) (actual rows=102.00 loops=1) + -> Sort (cost=1.20..1.21 rows=4 width=0) (actual rows=4.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", $1, $2)) + Sort Method: quicksort + -> HashAggregate (cost=1.10..1.16 rows=4 width=0) (actual rows=4.00 loops=1) + Group Key: time_bucket_gapfill(1, "time", $1, $2) + -> Seq Scan on metrics_int_loc (cost=0.00..1.07 rows=6 width=12) (actual rows=6.00 loops=1) + +PREPARE prep_int3(int,int,int) AS +SELECT + time_bucket_gapfill($1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= $2 AND time < $3 +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int3(1,0,100); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.10..1.13 rows=1 width=0) (actual rows=100.00 loops=1) + -> GroupAggregate (cost=1.10..1.13 rows=1 width=0) (actual rows=2.00 loops=1) + Group Key: (time_bucket_gapfill($1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.10..1.11 rows=1 width=12) (actual rows=2.00 loops=1) + Sort Key: (time_bucket_gapfill($1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.09 rows=1 width=12) (actual rows=2.00 loops=1) + Filter: (("time" >= $2) AND ("time" < $3)) + Rows Removed by Filter: 4 + +-- If some quals have parameter boundaries we can still estimate boundaries in other quals +PREPARE prep_int4(int,int) AS +SELECT + time_bucket_gapfill(1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= $1 AND time < $2 AND time >= 1 AND time < 100 +GROUP BY 1 +ORDER BY 1; +:PREFIX EXECUTE prep_int4(10,30); +--- QUERY PLAN --- + Custom Scan (GapFill) (cost=1.13..1.16 rows=100 width=0) (actual rows=20.00 loops=1) + -> GroupAggregate (cost=1.13..1.16 rows=1 width=0) (actual rows=0.00 loops=1) + Group Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + -> Sort (cost=1.13..1.14 rows=1 width=12) (actual rows=0.00 loops=1) + Sort Key: (time_bucket_gapfill(1, "time", NULL::integer, NULL::integer)) + Sort Method: quicksort + -> Seq Scan on metrics_int_loc (cost=0.00..1.12 rows=1 width=12) (actual rows=0.00 loops=1) + Filter: (("time" >= $1) AND ("time" < $2) AND ("time" >= 1) AND ("time" < 100)) + Rows Removed by Filter: 6 + +DEALLOCATE prep_int1; +DEALLOCATE prep_int2; +DEALLOCATE prep_int3; +DEALLOCATE prep_int4; +RESET plan_cache_mode; +DROP TABLE metrics_int_loc cascade; +DROP TABLE metrics_tstz_loc cascade; diff --git a/tsl/test/shared/sql/gapfill.sql.in b/tsl/test/shared/sql/gapfill.sql.in index 975ee55891d..d05a64de48c 100644 --- a/tsl/test/shared/sql/gapfill.sql.in +++ b/tsl/test/shared/sql/gapfill.sql.in @@ -1793,3 +1793,207 @@ GROUP BY 1,2 ORDER BY 2,1; reset enable_indexscan; reset enable_bitmapscan; + +-- Gapfill row count estimate should be ~((finish - start)/period + 1) instead of subpath row count, +-- otherwise Gapfill can have row count = 0 if range is outside of known data. + +CREATE TABLE metrics_int_loc( + time int NOT NULL, + device_id int, + sensor_id int, + value float); +INSERT INTO metrics_int_loc VALUES + (-100,1,1,0.0), + (-100,1,2,-100.0), + (0,1,1,5.0), + (5,1,2,10.0), + (100,1,1,0.0), + (100,1,2,-100.0); +ANALYZE metrics_int_loc; + +CREATE TABLE metrics_tstz_loc(time timestamptz, device_id INT, v1 float, v2 int); +INSERT INTO metrics_tstz_loc VALUES + (timestamptz '2018-01-01 05:00:00 PST', 1, 0.5, 10), + (timestamptz '2018-01-01 05:00:00 PST', 2, 0.7, 20), + (timestamptz '2018-01-01 05:00:00 PST', 3, 0.9, 30), + (timestamptz '2018-01-01 07:00:00 PST', 1, 0.0, 0), + (timestamptz '2018-01-01 07:00:00 PST', 2, 1.4, 40), + (timestamptz '2018-01-01 07:00:00 PST', 3, 0.9, 30) +; +ANALYZE metrics_tstz_loc; + +\set PREFIX 'EXPLAIN (analyze, timing off, summary off, buffers off)' + +-- Date/time period + +-- gapfill inside known data +-- subpath row count > gapfill row count +:PREFIX SELECT + time_bucket_gapfill(interval '1h',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; + +-- subpath row count < gapfill row count +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; + +-- Estimate boundaries in WHERE +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-01 06:00:00-8' AND time < '2018-01-01 06:30:00-8' +GROUP BY 1 +ORDER BY 1; + +-- gapfill outside known data +-- includes known data +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time, timestamptz '2018-01-02 06:00:00-8', timestamptz '2018-01-02 06:30:00-8'), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1 +ORDER BY 1; + +-- excludes known data +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-02 06:00:00-8' AND time < '2018-01-02 06:30:00-8' +GROUP BY 1 +ORDER BY 1; + +-- start > finish: should estimate as subplan rows +:PREFIX SELECT + time_bucket_gapfill(interval '1min',time), + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc WHERE time >= '2018-01-01 07:00:00-8' AND time < '2018-01-01 05:30:00-8' +GROUP BY 1 +ORDER BY 1; + +-- multiple groups: estimation should be multiplied by number of groups +:PREFIX SELECT + time_bucket_gapfill(interval '5sec',time,timestamptz '2018-01-01 05:00:00-8', timestamptz '2018-01-01 07:00:00-8'), + device_id, + locf(avg(v1)) AS locf_v1 +FROM metrics_tstz_loc +GROUP BY 1, 2 +ORDER BY 1; + +-- Integer period + +-- gapfill inside known data +-- subpath row count > gapfill row count +:PREFIX SELECT + time_bucket_gapfill(5,time,0,5), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; + +-- subpath row count < gapfill row count +:PREFIX SELECT + time_bucket_gapfill(1,time,-100,100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; + +-- Estimate boundaries in WHERE +:PREFIX SELECT + time_bucket_gapfill(1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= 0 AND time < 50 AND time >= 2*5 +GROUP BY 1 +ORDER BY 1; + +-- gapfill outside known data +-- includes known data +:PREFIX SELECT + time_bucket_gapfill(5,time,200,300), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; + +-- excludes known data +:PREFIX SELECT + time_bucket_gapfill(5,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= 200 AND time < 300 +GROUP BY 1 +ORDER BY 1; + +-- start > finish: should estimate as subplan rows +:PREFIX SELECT + time_bucket_gapfill(1,time,100,-100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; + +-- multiple groups: estimation should be multiplied by number of groups +:PREFIX SELECT + time_bucket_gapfill(1,time,-100,100), sensor_id, + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1, 2 +ORDER BY 1; + +-- Gapfill row count estimate is not done when Consts are not set at planning time +SET plan_cache_mode TO 'force_generic_plan'; + +PREPARE prep_int1(int) AS +SELECT + time_bucket_gapfill($1,time,0,100), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; + +:PREFIX EXECUTE prep_int1(1); + +PREPARE prep_int2(int,int) AS +SELECT + time_bucket_gapfill(1,time,$1,$2), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc +GROUP BY 1 +ORDER BY 1; + +:PREFIX EXECUTE prep_int2(0,100); + +PREPARE prep_int3(int,int,int) AS +SELECT + time_bucket_gapfill($1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= $2 AND time < $3 +GROUP BY 1 +ORDER BY 1; + +:PREFIX EXECUTE prep_int3(1,0,100); + +-- If some quals have parameter boundaries we can still estimate boundaries in other quals +PREPARE prep_int4(int,int) AS +SELECT + time_bucket_gapfill(1,time), + locf(avg(value)) AS locf_v1 +FROM metrics_int_loc WHERE time >= $1 AND time < $2 AND time >= 1 AND time < 100 +GROUP BY 1 +ORDER BY 1; + +:PREFIX EXECUTE prep_int4(10,30); + +DEALLOCATE prep_int1; +DEALLOCATE prep_int2; +DEALLOCATE prep_int3; +DEALLOCATE prep_int4; +RESET plan_cache_mode; + +DROP TABLE metrics_int_loc cascade; +DROP TABLE metrics_tstz_loc cascade;