mirror of
https://github.com/postgres/postgres.git
synced 2025-08-31 17:02:12 +03:00
Extended statistics on expressions
Allow defining extended statistics on expressions, not just just on
simple column references. With this commit, expressions are supported
by all existing extended statistics kinds, improving the same types of
estimates. A simple example may look like this:
CREATE TABLE t (a int);
CREATE STATISTICS s ON mod(a,10), mod(a,20) FROM t;
ANALYZE t;
The collected statistics are useful e.g. to estimate queries with those
expressions in WHERE or GROUP BY clauses:
SELECT * FROM t WHERE mod(a,10) = 0 AND mod(a,20) = 0;
SELECT 1 FROM t GROUP BY mod(a,10), mod(a,20);
This introduces new internal statistics kind 'e' (expressions) which is
built automatically when the statistics object definition includes any
expressions. This represents single-expression statistics, as if there
was an expression index (but without the index maintenance overhead).
The statistics is stored in pg_statistics_ext_data as an array of
composite types, which is possible thanks to 79f6a942bd
.
CREATE STATISTICS allows building statistics on a single expression, in
which case in which case it's not possible to specify statistics kinds.
A new system view pg_stats_ext_exprs can be used to display expression
statistics, similarly to pg_stats and pg_stats_ext views.
ALTER TABLE ... ALTER COLUMN ... TYPE now treats indexes the same way it
treats indexes, i.e. it drops and recreates the statistics. This means
all statistics are reset, and we no longer try to preserve at least the
functional dependencies. This should not be a major issue in practice,
as the functional dependencies actually rely on per-column statistics,
which were always reset anyway.
Author: Tomas Vondra
Reviewed-by: Justin Pryzby, Dean Rasheed, Zhihong Yu
Discussion: https://postgr.es/m/ad7891d2-e90c-b446-9fe2-7419143847d7%40enterprisedb.com
This commit is contained in:
@@ -239,6 +239,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
WindowDef *windef;
|
||||
JoinExpr *jexpr;
|
||||
IndexElem *ielem;
|
||||
StatsElem *selem;
|
||||
Alias *alias;
|
||||
RangeVar *range;
|
||||
IntoClause *into;
|
||||
@@ -405,7 +406,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
old_aggr_definition old_aggr_list
|
||||
oper_argtypes RuleActionList RuleActionMulti
|
||||
opt_column_list columnList opt_name_list
|
||||
sort_clause opt_sort_clause sortby_list index_params
|
||||
sort_clause opt_sort_clause sortby_list index_params stats_params
|
||||
opt_include opt_c_include index_including_params
|
||||
name_list role_list from_clause from_list opt_array_bounds
|
||||
qualified_name_list any_name any_name_list type_name_list
|
||||
@@ -512,6 +513,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
%type <list> func_alias_clause
|
||||
%type <sortby> sortby
|
||||
%type <ielem> index_elem index_elem_options
|
||||
%type <selem> stats_param
|
||||
%type <node> table_ref
|
||||
%type <jexpr> joined_table
|
||||
%type <range> relation_expr
|
||||
@@ -4097,7 +4099,7 @@ ExistingIndex: USING INDEX name { $$ = $3; }
|
||||
|
||||
CreateStatsStmt:
|
||||
CREATE STATISTICS any_name
|
||||
opt_name_list ON expr_list FROM from_list
|
||||
opt_name_list ON stats_params FROM from_list
|
||||
{
|
||||
CreateStatsStmt *n = makeNode(CreateStatsStmt);
|
||||
n->defnames = $3;
|
||||
@@ -4109,7 +4111,7 @@ CreateStatsStmt:
|
||||
$$ = (Node *)n;
|
||||
}
|
||||
| CREATE STATISTICS IF_P NOT EXISTS any_name
|
||||
opt_name_list ON expr_list FROM from_list
|
||||
opt_name_list ON stats_params FROM from_list
|
||||
{
|
||||
CreateStatsStmt *n = makeNode(CreateStatsStmt);
|
||||
n->defnames = $6;
|
||||
@@ -4122,6 +4124,36 @@ CreateStatsStmt:
|
||||
}
|
||||
;
|
||||
|
||||
/*
|
||||
* Statistics attributes can be either simple column references, or arbitrary
|
||||
* expressions in parens. For compatibility with index attributes permitted
|
||||
* in CREATE INDEX, we allow an expression that's just a function call to be
|
||||
* written without parens.
|
||||
*/
|
||||
|
||||
stats_params: stats_param { $$ = list_make1($1); }
|
||||
| stats_params ',' stats_param { $$ = lappend($1, $3); }
|
||||
;
|
||||
|
||||
stats_param: ColId
|
||||
{
|
||||
$$ = makeNode(StatsElem);
|
||||
$$->name = $1;
|
||||
$$->expr = NULL;
|
||||
}
|
||||
| func_expr_windowless
|
||||
{
|
||||
$$ = makeNode(StatsElem);
|
||||
$$->name = NULL;
|
||||
$$->expr = $1;
|
||||
}
|
||||
| '(' a_expr ')'
|
||||
{
|
||||
$$ = makeNode(StatsElem);
|
||||
$$->name = NULL;
|
||||
$$->expr = $2;
|
||||
}
|
||||
;
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
|
@@ -484,6 +484,13 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
|
||||
else
|
||||
err = _("grouping operations are not allowed in index predicates");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_STATS_EXPRESSION:
|
||||
if (isAgg)
|
||||
err = _("aggregate functions are not allowed in statistics expressions");
|
||||
else
|
||||
err = _("grouping operations are not allowed in statistics expressions");
|
||||
|
||||
break;
|
||||
case EXPR_KIND_ALTER_COL_TRANSFORM:
|
||||
if (isAgg)
|
||||
@@ -910,6 +917,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
|
||||
case EXPR_KIND_INDEX_EXPRESSION:
|
||||
err = _("window functions are not allowed in index expressions");
|
||||
break;
|
||||
case EXPR_KIND_STATS_EXPRESSION:
|
||||
err = _("window functions are not allowed in statistics expressions");
|
||||
break;
|
||||
case EXPR_KIND_INDEX_PREDICATE:
|
||||
err = _("window functions are not allowed in index predicates");
|
||||
break;
|
||||
|
@@ -500,6 +500,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
|
||||
case EXPR_KIND_FUNCTION_DEFAULT:
|
||||
case EXPR_KIND_INDEX_EXPRESSION:
|
||||
case EXPR_KIND_INDEX_PREDICATE:
|
||||
case EXPR_KIND_STATS_EXPRESSION:
|
||||
case EXPR_KIND_ALTER_COL_TRANSFORM:
|
||||
case EXPR_KIND_EXECUTE_PARAMETER:
|
||||
case EXPR_KIND_TRIGGER_WHEN:
|
||||
@@ -1741,6 +1742,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
|
||||
case EXPR_KIND_INDEX_PREDICATE:
|
||||
err = _("cannot use subquery in index predicate");
|
||||
break;
|
||||
case EXPR_KIND_STATS_EXPRESSION:
|
||||
err = _("cannot use subquery in statistics expression");
|
||||
break;
|
||||
case EXPR_KIND_ALTER_COL_TRANSFORM:
|
||||
err = _("cannot use subquery in transform expression");
|
||||
break;
|
||||
@@ -3030,6 +3034,8 @@ ParseExprKindName(ParseExprKind exprKind)
|
||||
return "index expression";
|
||||
case EXPR_KIND_INDEX_PREDICATE:
|
||||
return "index predicate";
|
||||
case EXPR_KIND_STATS_EXPRESSION:
|
||||
return "statistics expression";
|
||||
case EXPR_KIND_ALTER_COL_TRANSFORM:
|
||||
return "USING";
|
||||
case EXPR_KIND_EXECUTE_PARAMETER:
|
||||
|
@@ -2503,6 +2503,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
|
||||
case EXPR_KIND_INDEX_PREDICATE:
|
||||
err = _("set-returning functions are not allowed in index predicates");
|
||||
break;
|
||||
case EXPR_KIND_STATS_EXPRESSION:
|
||||
err = _("set-returning functions are not allowed in statistics expressions");
|
||||
break;
|
||||
case EXPR_KIND_ALTER_COL_TRANSFORM:
|
||||
err = _("set-returning functions are not allowed in transform expressions");
|
||||
break;
|
||||
|
@@ -1917,6 +1917,9 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
|
||||
stat_types = lappend(stat_types, makeString("dependencies"));
|
||||
else if (enabled[i] == STATS_EXT_MCV)
|
||||
stat_types = lappend(stat_types, makeString("mcv"));
|
||||
else if (enabled[i] == STATS_EXT_EXPRESSIONS)
|
||||
/* expression stats are not exposed to users */
|
||||
continue;
|
||||
else
|
||||
elog(ERROR, "unrecognized statistics kind %c", enabled[i]);
|
||||
}
|
||||
@@ -1924,14 +1927,47 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
|
||||
/* Determine which columns the statistics are on */
|
||||
for (i = 0; i < statsrec->stxkeys.dim1; i++)
|
||||
{
|
||||
ColumnRef *cref = makeNode(ColumnRef);
|
||||
StatsElem *selem = makeNode(StatsElem);
|
||||
AttrNumber attnum = statsrec->stxkeys.values[i];
|
||||
|
||||
cref->fields = list_make1(makeString(get_attname(heapRelid,
|
||||
attnum, false)));
|
||||
cref->location = -1;
|
||||
selem->name = get_attname(heapRelid, attnum, false);
|
||||
selem->expr = NULL;
|
||||
|
||||
def_names = lappend(def_names, cref);
|
||||
def_names = lappend(def_names, selem);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now handle expressions, if there are any. The order (with respect to
|
||||
* regular attributes) does not really matter for extended stats, so we
|
||||
* simply append them after simple column references.
|
||||
*
|
||||
* XXX Some places during build/estimation treat expressions as if they
|
||||
* are before atttibutes, but for the CREATE command that's entirely
|
||||
* irrelevant.
|
||||
*/
|
||||
datum = SysCacheGetAttr(STATEXTOID, ht_stats,
|
||||
Anum_pg_statistic_ext_stxexprs, &isnull);
|
||||
|
||||
if (!isnull)
|
||||
{
|
||||
ListCell *lc;
|
||||
List *exprs = NIL;
|
||||
char *exprsString;
|
||||
|
||||
exprsString = TextDatumGetCString(datum);
|
||||
exprs = (List *) stringToNode(exprsString);
|
||||
|
||||
foreach(lc, exprs)
|
||||
{
|
||||
StatsElem *selem = makeNode(StatsElem);
|
||||
|
||||
selem->name = NULL;
|
||||
selem->expr = (Node *) lfirst(lc);
|
||||
|
||||
def_names = lappend(def_names, selem);
|
||||
}
|
||||
|
||||
pfree(exprsString);
|
||||
}
|
||||
|
||||
/* finally, build the output node */
|
||||
@@ -1942,6 +1978,7 @@ generateClonedExtStatsStmt(RangeVar *heapRel, Oid heapRelid,
|
||||
stats->relations = list_make1(heapRel);
|
||||
stats->stxcomment = NULL;
|
||||
stats->if_not_exists = false;
|
||||
stats->transformed = true; /* don't need transformStatsStmt again */
|
||||
|
||||
/* Clean up */
|
||||
ReleaseSysCache(ht_stats);
|
||||
@@ -2866,6 +2903,84 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
|
||||
return stmt;
|
||||
}
|
||||
|
||||
/*
|
||||
* transformStatsStmt - parse analysis for CREATE STATISTICS
|
||||
*
|
||||
* To avoid race conditions, it's important that this function rely only on
|
||||
* the passed-in relid (and not on stmt->relation) to determine the target
|
||||
* relation.
|
||||
*/
|
||||
CreateStatsStmt *
|
||||
transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
|
||||
{
|
||||
ParseState *pstate;
|
||||
ParseNamespaceItem *nsitem;
|
||||
ListCell *l;
|
||||
Relation rel;
|
||||
|
||||
/* Nothing to do if statement already transformed. */
|
||||
if (stmt->transformed)
|
||||
return stmt;
|
||||
|
||||
/*
|
||||
* We must not scribble on the passed-in CreateStatsStmt, so copy it.
|
||||
* (This is overkill, but easy.)
|
||||
*/
|
||||
stmt = copyObject(stmt);
|
||||
|
||||
/* Set up pstate */
|
||||
pstate = make_parsestate(NULL);
|
||||
pstate->p_sourcetext = queryString;
|
||||
|
||||
/*
|
||||
* Put the parent table into the rtable so that the expressions can refer
|
||||
* to its fields without qualification. Caller is responsible for locking
|
||||
* relation, but we still need to open it.
|
||||
*/
|
||||
rel = relation_open(relid, NoLock);
|
||||
nsitem = addRangeTableEntryForRelation(pstate, rel,
|
||||
AccessShareLock,
|
||||
NULL, false, true);
|
||||
|
||||
/* no to join list, yes to namespaces */
|
||||
addNSItemToQuery(pstate, nsitem, false, true, true);
|
||||
|
||||
/* take care of any expressions */
|
||||
foreach(l, stmt->exprs)
|
||||
{
|
||||
StatsElem *selem = (StatsElem *) lfirst(l);
|
||||
|
||||
if (selem->expr)
|
||||
{
|
||||
/* Now do parse transformation of the expression */
|
||||
selem->expr = transformExpr(pstate, selem->expr,
|
||||
EXPR_KIND_STATS_EXPRESSION);
|
||||
|
||||
/* We have to fix its collations too */
|
||||
assign_expr_collations(pstate, selem->expr);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that only the base rel is mentioned. (This should be dead code
|
||||
* now that add_missing_from is history.)
|
||||
*/
|
||||
if (list_length(pstate->p_rtable) != 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
|
||||
errmsg("statistics expressions can refer only to the table being indexed")));
|
||||
|
||||
free_parsestate(pstate);
|
||||
|
||||
/* Close relation */
|
||||
table_close(rel, NoLock);
|
||||
|
||||
/* Mark statement as successfully transformed */
|
||||
stmt->transformed = true;
|
||||
|
||||
return stmt;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* transformRuleStmt -
|
||||
|
Reference in New Issue
Block a user