Re: PoC/WIP: Extended statistics on expressions
Dean Rasheed <dean.a.rasheed@gmail.com>
From: Dean Rasheed <dean.a.rasheed@gmail.com>
To: Tomas Vondra <tomas.vondra@enterprisedb.com>
Cc: Justin Pryzby <pryzby@telsasoft.com>,
PostgreSQL Hackers <pgsql-hackers@postgresql.org>
Date: 2021-03-17T15:55:41Z
Lists: pgsql-hackers
On Sun, 7 Mar 2021 at 21:10, Tomas Vondra <tomas.vondra@enterprisedb.com> wrote:
>
> 2) ndistinct
>
> There's one thing that's bugging me, in how we handle "partial" matches.
> For each expression we track both the original expression and the Vars
> we extract from it. If we can't find a statistics matching the whole
> expression, we try to match those individual Vars, and we remove the
> matching ones from the list. And in the end we multiply the estimates
> for the remaining Vars.
>
> This works fine with one matching ndistinct statistics. Consider for example
>
> GROUP BY (a+b), (c+d)
>
> with statistics on [(a+b),c] - that is, expression and one column.
I've just been going over this example, and I think it actually works
slightly differently from how you described, though I haven't worked
out the full general implications of that.
> We parse the expressions into two GroupExprInfo
>
> {expr: (a+b), vars: [a, b]}
> {expr: (c+d), vars: [c, d]}
>
Here, I think what you actually get, in the presence of stats on
[(a+b),c] is actually the following two GroupExprInfos:
{expr: (a+b), vars: []}
{expr: (c+d), vars: [c, d]}
because of the code immediately after this comment in estimate_num_groups():
/*
* If examine_variable is able to deduce anything about the GROUP BY
* expression, treat it as a single variable even if it's really more
* complicated.
*/
As it happens, that makes no difference in this case, where there is
just this one stats object, but it does change things when there are
two stats objects.
> and the statistics matches the first item exactly (the expression). The
> second expression is not in the statistics, but we match "c". So we end
> up with an estimate for "(a+b), c" and have one remaining GroupExprInfo:
>
> {expr: (c+d), vars: [d]}
Right.
> Without any other statistics we estimate that as ndistinct for "d", so
> we end up with
>
> ndistinct((a+b), c) * ndistinct(d)
>
> which mostly makes sense. It assumes ndistinct(c+d) is product of the
> ndistinct estimates, but that's kinda what we've been always doing.
Yes, that appears to be what happens, and it's probably the best that
can be done with the available stats.
> But now consider we have another statistics on just (c+d). In the second
> loop we end up matching this expression exactly, so we end up with
>
> ndistinct((a+b), c) * ndistinct((c+d))
In this case, with stats on (c+d) as well, the two GroupExprInfos
built at the start change to:
{expr: (a+b), vars: []}
{expr: (c+d), vars: []}
so it actually ends up not using any multivariate stats, but instead
uses the two univariate expression stats, giving
ndistinct((a+b)) * ndistinct((c+d))
which actually seems pretty good, and gave very good estimates in the
simple test case I tried.
> i.e. we kinda use the "c" twice. Which is a bit unfortunate. I think
> what we should do after the first loop is just discarding the whole
> expression and "expand" into per-variable GroupExprInfo, so in the
> second step we would not match the (c+d) statistics.
Not using the (c+d) stats would give either
ndistinct((a+b)) * ndistinct(c) * ndistinct(d)
or
ndistinct((a+b), c) * ndistinct(d)
depending on exactly how the algorithm was changed. In my test, these
both gave worse estimates, but there are probably other datasets for
which they might do better. It all depends on how much correlation
there is between (a+b) and c.
I suspect that there is no optimal strategy for handling overlapping
stats that works best for all datasets, but the current algorithm
seems to do a pretty decent job.
> Of course, maybe there's a better way to pick the statistics, but I
> think our conclusion so far was that people should just create
> statistics covering all the columns in the query, to not have to match
> multiple statistics like this.
Yes, I think that's always likely to work better, especially for
ndistinct stats, where all possible permutations of subsets of the
columns are included, so a single ndistinct stat can work well for a
range of different queries.
Regards,
Dean
Commits
-
Disallow extended statistics on system columns
- 66061077155d 14.0 landed
- c9eeef2a15c0 15.0 landed
-
Identify simple column references in extended statistics
- 50ba70a957f9 14.0 landed
- 537ca68dbb24 15.0 landed
-
Don't print extra parens around expressions in extended stats
- 4d1816ec26e8 14.0 landed
- 13380e147649 15.0 landed
-
Change position of field "transformed" in struct CreateStatsStmt.
- 13a1ca160dcf 14.0 landed
-
Add transformed flag to nodes/*funcs.c for CREATE STATISTICS
- d57ecebd128c 14.0 landed
-
Stabilize stats_ext test with other collations
- 2a058e938c73 14.0 landed
-
Extended statistics on expressions
- a4d75c86bf15 14.0 landed
-
Reduce duration of stats_ext regression tests
- 98376c18f12e 14.0 landed
-
Allow composite types in catalog bootstrap
- 79f6a942bdb9 14.0 landed
-
Convert Typ from array to list in bootstrap
- e1a5e65703ce 14.0 landed
-
Disallow CREATE STATISTICS on system catalogs
- c22539756ee5 10.16 landed
- f52db969440b 11.11 landed
- 943a113bcb6f 12.6 landed
- d26d4c717dbf 13.2 landed
- c9a0dc34865f 14.0 landed