Thread
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Re-allow using statistics for bool-valued functions in WHERE.
- 1eccb9315070 19 (unreleased) landed
-
Build out the planner support function infrastructure.
- a391ff3c3d41 12.0 cited
-
Allow planner to use expression-index stats for function calls in WHERE.
- 39df0f150ca6 9.6.0 cited
-
Indexes on expressions with multiple columns and operators
Frédéric Yhuel <frederic.yhuel@dalibo.com> — 2025-09-17T13:55:29Z
Hello, in the following, I don't understand why: 1) the expression index isn't used in the first EXPLAIN 2) the number of estimated rows is completely off in the second EXPLAIN, whereas the planner could easily use the statistics of foo_f_idx. (SQL script attached, tested with master and v17) DROP TABLE IF EXISTS foo; CREATE UNLOGGED TABLE foo (id bigint, ackid int, crit text); ALTER TABLE foo ALTER COLUMN crit SET statistics 400; INSERT INTO foo SELECT i, NULL, CASE WHEN i%100=1 THEN 'WARNING' ELSE 'INFO' END FROM generate_series(1,100000) AS T(i); UPDATE foo SET ackid = random()*10000 WHERE id%100=1 AND id > 500 ; CREATE INDEX foo_expr_idx ON foo ((ackid IS NULL AND crit = 'WARNING')); ANALYZE foo ; EXPLAIN (ANALYZE, SUMMARY OFF, BUFFERS OFF) SELECT * FROM foo WHERE ackid IS NULL AND crit = 'WARNING'; QUERY PLAN ------------------------------------------------------------------------------------------------------- Seq Scan on foo (cost=0.00..1797.00 rows=990 width=17) (actual time=0.012..23.932 rows=5.00 loops=1) Filter: ((ackid IS NULL) AND (crit = 'WARNING'::text)) Rows Removed by Filter: 99995 (3 rows) CREATE OR REPLACE FUNCTION f(crit text, ackid int) RETURNS bool LANGUAGE plpgsql IMMUTABLE AS $$ BEGIN RETURN crit = 'WARNING' AND ackid IS NULL; END $$; CREATE INDEX foo_f_idx ON foo (f(crit, ackid)); ANALYZE foo ; EXPLAIN (ANALYZE, SUMMARY OFF, BUFFERS OFF) SELECT * FROM foo WHERE f(crit, ackid); QUERY PLAN ----------------------------------------------------------------------------------------------------------------------- Index Scan using foo_f_idx on foo (cost=0.29..8.39 rows=33333 width=17) (actual time=0.021..0.028 rows=5.00 loops=1) Index Cond: (f(crit, ackid) = true) Index Searches: 1 (3 rows) SELECT tablename, most_common_vals, most_common_freqs FROM pg_stats WHERE tablename like 'foo_%'; tablename | most_common_vals | most_common_freqs --------------+------------------+------------------- foo_expr_idx | {f,t} | {0.99995,5e-05} foo_f_idx | {f,t} | {0.99995,5e-05} SELECT 100000 * 5e-05 AS the_row_estimate_that_the_planner_should_use; the_row_estimate_that_the_planner_should_use --------------------------------------------- 5.00000 Best regards, Frédéric -
Re: Indexes on expressions with multiple columns and operators
Laurenz Albe <laurenz.albe@cybertec.at> — 2025-09-17T14:22:04Z
On Wed, 2025-09-17 at 15:55 +0200, Frédéric Yhuel wrote: > Hello, in the following, I don't understand why: > > 1) the expression index isn't used in the first EXPLAIN > > 2) the number of estimated rows is completely off in the second EXPLAIN, > whereas the planner could easily use the statistics of foo_f_idx. > > (SQL script attached, tested with master and v17) > > DROP TABLE IF EXISTS foo; > > CREATE UNLOGGED TABLE foo (id bigint, ackid int, crit text); > > ALTER TABLE foo ALTER COLUMN crit SET statistics 400; > > INSERT INTO foo SELECT i, NULL, CASE WHEN i%100=1 THEN 'WARNING' ELSE > 'INFO' END FROM generate_series(1,100000) AS T(i); > > UPDATE foo SET ackid = random()*10000 WHERE id%100=1 AND id > 500 ; > > CREATE INDEX foo_expr_idx ON foo ((ackid IS NULL AND crit = 'WARNING')); > > ANALYZE foo ; > > EXPLAIN (ANALYZE, SUMMARY OFF, BUFFERS OFF) SELECT * FROM foo WHERE > ackid IS NULL AND crit = 'WARNING'; > QUERY PLAN > > ------------------------------------------------------------------------------------------------------- > Seq Scan on foo (cost=0.00..1797.00 rows=990 width=17) (actual > time=0.012..23.932 rows=5.00 loops=1) > Filter: ((ackid IS NULL) AND (crit = 'WARNING'::text)) > Rows Removed by Filter: 99995 > (3 rows) As far as I know, PostgreSQL considers "ackid IS NULL" and "crit = 'WARNING'" as two different RestrictInfos. See the comment: /* * Restriction clause info. * * We create one of these for each AND sub-clause of a restriction condition * (WHERE or JOIN/ON clause). Since the restriction clauses are logically * ANDed, we can use any one of them or any subset of them to filter out * tuples, without having to evaluate the rest. The RestrictInfo node itself * stores data used by the optimizer while choosing the best query plan. PostgreSQL doesn't consider the (exotic) case what someone creates an index on an expression that contains an AND. You could do this: EXPLAIN (ANALYZE, SUMMARY OFF, BUFFERS OFF) SELECT * FROM foo WHERE (ackid IS NULL AND crit = 'WARNING') IS TRUE; QUERY PLAN ══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ Index Scan using foo_expr_idx on foo (cost=0.29..8.39 rows=5 width=17) (actual time=0.011..0.018 rows=5.00 loops=1) Index Cond: (((ackid IS NULL) AND (crit = 'WARNING'::text)) = true) Index Searches: 1 (3 rows) But really, you should create a better index, perhaps CREATE INDEX ON foo (crit) WHERE ackid IS NULL; Yours, Laurenz Albe -
Re: Indexes on expressions with multiple columns and operators
Tom Lane <tgl@sss.pgh.pa.us> — 2025-09-17T14:41:23Z
=?UTF-8?Q?Fr=C3=A9d=C3=A9ric_Yhuel?= <frederic.yhuel@dalibo.com> writes: > Hello, in the following, I don't understand why: > 1) the expression index isn't used in the first EXPLAIN The planner doesn't look for multi-clause matches of that sort. You could apply a little ju-jitsu perhaps: regression=# EXPLAIN (ANALYZE, SUMMARY OFF, BUFFERS OFF) SELECT * FROM foo WHERE (ackid IS NULL AND crit = 'WARNING') is true; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------- Index Scan using foo_expr_idx on foo (cost=0.29..8.39 rows=5 width=17) (actual time=0.013..0.016 rows=5.00 loops=1) Index Cond: (((ackid IS NULL) AND (crit = 'WARNING'::text)) = true) Index Searches: 1 (3 rows) but my own tendency would be to use a partial index rather than a boolean-valued index: regression=# CREATE INDEX foo_partial_idx ON foo (id) WHERE ackid IS NULL AND crit = 'WARNING'; CREATE INDEX regression=# EXPLAIN (ANALYZE, SUMMARY OFF, BUFFERS OFF) SELECT * FROM foo WHERE ackid IS NULL AND crit = 'WARNING'; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- Index Scan using foo_partial_idx on foo (cost=0.13..107.18 rows=990 width=17) (actual time=0.010..0.014 rows=5.00 loops=1) Index Searches: 1 (2 rows) The advantage of a partial index is you might be able to have the index entries themselves carry some other column(s), allowing more queries to be made into index-only scans. I put "id" here, which might or might not be of any use in this specific toy example. > 2) the number of estimated rows is completely off in the second EXPLAIN, > whereas the planner could easily use the statistics of foo_f_idx. Hmm, not sure about that. Again, boolean-valued indexes aren't something we've worked on too hard, but I don't see why that would affect this case. regards, tom lane -
Re: Indexes on expressions with multiple columns and operators
Frédéric Yhuel <frederic.yhuel@dalibo.com> — 2025-09-17T14:57:26Z
Thank you Laurenz and Tom! I'm going to quote Tom's email here: On 9/17/25 16:41, Tom Lane wrote: > =?UTF-8?Q?Fr=C3=A9d=C3=A9ric_Yhuel?= <frederic.yhuel@dalibo.com> writes: >> Hello, in the following, I don't understand why: >> 1) the expression index isn't used in the first EXPLAIN > > The planner doesn't look for multi-clause matches of that sort. > You could apply a little ju-jitsu perhaps: > > regression=# EXPLAIN (ANALYZE, SUMMARY OFF, BUFFERS OFF) SELECT * FROM foo WHERE (ackid IS NULL AND crit = 'WARNING') is true; > QUERY PLAN > ---------------------------------------------------------------------------------------------------------------------- > Index Scan using foo_expr_idx on foo (cost=0.29..8.39 rows=5 width=17) (actual time=0.013..0.016 rows=5.00 loops=1) > Index Cond: (((ackid IS NULL) AND (crit = 'WARNING'::text)) = true) > Index Searches: 1 > (3 rows) > Thanks, it works well indeed. > but my own tendency would be to use a partial index rather than a > boolean-valued index: > > regression=# CREATE INDEX foo_partial_idx ON foo (id) WHERE ackid IS NULL AND crit = 'WARNING'; > CREATE INDEX > regression=# EXPLAIN (ANALYZE, SUMMARY OFF, BUFFERS OFF) SELECT * FROM foo WHERE ackid IS NULL AND crit = 'WARNING'; > QUERY PLAN > ----------------------------------------------------------------------------------------------------------------------------- > Index Scan using foo_partial_idx on foo (cost=0.13..107.18 rows=990 width=17) (actual time=0.010..0.014 rows=5.00 loops=1) > Index Searches: 1 > (2 rows) > > The advantage of a partial index is you might be able to have the > index entries themselves carry some other column(s), allowing > more queries to be made into index-only scans. I put "id" here, > which might or might not be of any use in this specific toy example. > Yes, Laurenz made a similar suggestion, but the problem is that I'm mostly interested in the estimated number of output rows... because in the real query, there's a very bad Hash Join above (the Nested Loop is *much* faster). >> 2) the number of estimated rows is completely off in the second EXPLAIN, >> whereas the planner could easily use the statistics of foo_f_idx. > > Hmm, not sure about that. Again, boolean-valued indexes aren't > something we've worked on too hard, but I don't see why that > would affect this case. > OK, thanks anyway, I think the ju-jitsu mentioned above will do, even though the application code will have to be patched.
-
Re: Indexes on expressions with multiple columns and operators
Frédéric Yhuel <frederic.yhuel@dalibo.com> — 2025-09-17T15:33:35Z
On 9/17/25 16:57, Frédéric Yhuel wrote: > Yes, Laurenz made a similar suggestion, but the problem is that I'm > mostly interested in the estimated number of output rows... because in > the real query, there's a very bad Hash Join above (the Nested Loop is > *much* faster). BTW, I've also tested another solution: partitioning on 'criticity', with one partition for 'INFO' (99% of the rows), and another one for the other values ('WARNING' and 'ALARM' in the real case). The statistics are much better... however an expression index would be an easier fix. Multivariate MCV statistics don't work well in the real case (100M lines in the table). -
Re: Indexes on expressions with multiple columns and operators
Jehan-Guillaume de Rorthais <jgdr@dalibo.com> — 2025-09-18T15:26:28Z
Hi there, I think this discussion has a nice solution, thank you! However, while poking around this issue yesterday, we also found something surprising between estimated rows and costs when using a function. Bellow the scenario to apply on top of Frederic's one to quickly expose the weirdness: CREATE OR REPLACE FUNCTION s(crit text, ackid int) RETURNS bool LANGUAGE plpgsql IMMUTABLE AS $$ BEGIN RETURN crit = 'WARNING' AND ackid IS NULL; END $$; CREATE INDEX foo_s_idx ON foo (s(crit, ackid)); ANALYZE foo ; EXPLAIN SELECT * FROM foo WHERE s(crit, ackid); EXPLAIN SELECT * FROM foo WHERE (ackid IS NULL AND crit = 'WARNING') is true; SELECT most_common_vals, most_common_freqs FROM pg_stats WHERE tablename = 'foo_s_idx'; On a fresh instance from HEAD with its default configuration, it shows: Index Scan using foo_s_idx on foo (cost=0.29..8.39 rows=33333 width=13) Index Cond: (s(crit, ackid) = true) Index Scan using foo_expr_idx on foo (cost=0.29..8.39 rows=5 width=13) Index Cond: (((ackid IS NULL) AND (crit = 'WARNING'::text)) = true) most_common_vals | most_common_freqs ------------------+------------------- {f,t} | {0.99995,5e-05} It seems statistics shown in "pg_stats" view for function "s()" are good. The query itself even have the same costs than the query using the syntax tips you provide before. However, the estimated row number seems wrong in regard with the costs shown and statistics. It looks like a default hardcoded 33% has been applied. As Frederic said earlier, this bad row estimation drives the upper join to the wrong method. Is this a known planer behavior? (again, the ju-jitsu syntax provided by Laurenz and you is definitely on point here, I just hijack the thread to discuss this weirdness.) Regards, -
Re: Indexes on expressions with multiple columns and operators
Tom Lane <tgl@sss.pgh.pa.us> — 2025-09-18T15:32:29Z
=?UTF-8?Q?Fr=C3=A9d=C3=A9ric_Yhuel?= <frederic.yhuel@dalibo.com> writes: > On 9/17/25 16:41, Tom Lane wrote: >> =?UTF-8?Q?Fr=C3=A9d=C3=A9ric_Yhuel?= <frederic.yhuel@dalibo.com> writes: >>> 2) the number of estimated rows is completely off in the second EXPLAIN, >>> whereas the planner could easily use the statistics of foo_f_idx. >> Hmm, not sure about that. Again, boolean-valued indexes aren't >> something we've worked on too hard, but I don't see why that >> would affect this case. > OK, thanks anyway, I think the ju-jitsu mentioned above will do, even > though the application code will have to be patched. Sigh ... so the answer is this used to work (since commit 39df0f150) and then I carelessly broke it in commit a391ff3c3. If you try this test case in versions 9.5..11 you get a spot-on rowcount estimate. Serves me right for not having a test case I guess, but I'm astonished that nobody complained sooner. regards, tom lane
-
Re: Indexes on expressions with multiple columns and operators
Tom Lane <tgl@sss.pgh.pa.us> — 2025-09-18T16:40:39Z
I wrote: > Sigh ... so the answer is this used to work (since commit 39df0f150) > and then I carelessly broke it in commit a391ff3c3. If you try this > test case in versions 9.5..11 you get a spot-on rowcount estimate. > Serves me right for not having a test case I guess, but I'm astonished > that nobody complained sooner. The attached fixes things so it works like it did pre-a391ff3c3. I spent some time trying to devise a test case, and was reminded of why I didn't have one before: it's hard to make a case that will be robust enough to not show diffs in the buildfarm. I'll keep thinking about that though. regards, tom lane
-
Re: Indexes on expressions with multiple columns and operators
Ranier Vilela <ranier.vf@gmail.com> — 2025-09-18T16:48:08Z
Em qui., 18 de set. de 2025 às 13:40, Tom Lane <tgl@sss.pgh.pa.us> escreveu: > I wrote: > > Sigh ... so the answer is this used to work (since commit 39df0f150) > > and then I carelessly broke it in commit a391ff3c3. If you try this > > test case in versions 9.5..11 you get a spot-on rowcount estimate. > > Serves me right for not having a test case I guess, but I'm astonished > > that nobody complained sooner. > > The attached fixes things so it works like it did pre-a391ff3c3. > > I spent some time trying to devise a test case, and was reminded > of why I didn't have one before: it's hard to make a case that > will be robust enough to not show diffs in the buildfarm. > I'll keep thinking about that though. > One question, make difference return -1.0 (float point notation)? If not, static analyzers will have less work. best regards, Ranier Vilela
-
Re: Indexes on expressions with multiple columns and operators
Tom Lane <tgl@sss.pgh.pa.us> — 2025-09-18T16:59:11Z
Jehan-Guillaume de Rorthais <jgdr@dalibo.com> writes: > On a fresh instance from HEAD with its default configuration, it shows: > Index Scan using foo_s_idx on foo (cost=0.29..8.39 rows=33333 width=13) > Index Cond: (s(crit, ackid) = true) > It seems statistics shown in "pg_stats" view for function "s()" are good. The > query itself even have the same costs than the query using the syntax tips you > provide before. > However, the estimated row number seems wrong in regard with the costs shown > and statistics. Yeah. The problem is that clause_selectivity_ext fails to consider use of statistics if the clause looks like "bool_valued_function(...)". If it looks like "bool_valued_function(...) = true", that goes down a different code path that does the right thing. Additional factors: * If you just write "WHERE bool_valued_function(...) = true", that gets stripped down to "WHERE bool_valued_function(...)" in the name of making equivalent expressions look equivalent. (IS TRUE doesn't get stripped, which is why you have to use that wording to avoid that.) * Index condition building puts back the "= true" in order to construct something that satisfies the index AM API. And then it uses that form to get a selectivity estimate for costing purposes --- so the right number goes into the indexscan cost estimate. * But the rowcount estimate is made on the form without "= true". That's the number shown in EXPLAIN and used when considering joins. regards, tom lane
-
Re: Indexes on expressions with multiple columns and operators
Frédéric Yhuel <frederic.yhuel@dalibo.com> — 2025-09-19T07:50:26Z
On 9/18/25 18:40, Tom Lane wrote: > The attached fixes things so it works like it did pre-a391ff3c3. > Indeed, it works well! > I spent some time trying to devise a test case, and was reminded > of why I didn't have one before: it's hard to make a case that > will be robust enough to not show diffs in the buildfarm. > I'll keep thinking about that though. It looks like an interesting homework for an aspiring hacker :-) So I'll try to devise one, too.
-
Re: Indexes on expressions with multiple columns and operators
Jehan-Guillaume de Rorthais <jgdr@dalibo.com> — 2025-09-19T14:37:45Z
On Thu, 18 Sep 2025 12:59:11 -0400 Tom Lane <tgl@sss.pgh.pa.us> wrote: > Jehan-Guillaume de Rorthais <jgdr@dalibo.com> writes: > > On a fresh instance from HEAD with its default configuration, it shows: > > > Index Scan using foo_s_idx on foo (cost=0.29..8.39 rows=33333 width=13) > > Index Cond: (s(crit, ackid) = true) > > > It seems statistics shown in "pg_stats" view for function "s()" are good. > > The query itself even have the same costs than the query using the syntax > > tips you provide before. > > > However, the estimated row number seems wrong in regard with the costs shown > > and statistics. > > Yeah. The problem is that clause_selectivity_ext fails to consider > use of statistics if the clause looks like "bool_valued_function(...)". > If it looks like "bool_valued_function(...) = true", that goes down > a different code path that does the right thing. Oh, OK, I understand. Thanks for your explanations! Regards,
-
Re: Indexes on expressions with multiple columns and operators
Tom Lane <tgl@sss.pgh.pa.us> — 2025-09-20T16:51:50Z
=?UTF-8?Q?Fr=C3=A9d=C3=A9ric_Yhuel?= <frederic.yhuel@dalibo.com> writes: > On 9/18/25 18:40, Tom Lane wrote: >> The attached fixes things so it works like it did pre-a391ff3c3. > Indeed, it works well! Thanks for testing! >> I spent some time trying to devise a test case, and was reminded >> of why I didn't have one before: it's hard to make a case that >> will be robust enough to not show diffs in the buildfarm. >> I'll keep thinking about that though. > It looks like an interesting homework for an aspiring hacker :-) > So I'll try to devise one, too. I concluded that maybe I was overthinking this part. We only really need to check the rowcount estimate, since the indexscan cost estimate is already okay. And stats_ext.sql seems to have gotten away with assuming that rowcount estimates are reliably reproducible on not-too-large tables. This bug affects use of extended statistics too, so a test using those is good enough to show it's fixed; we don't really need to use an expression index for the purpose. So, I added a test case in stats_ext.sql and pushed it. I wish we could back-patch this, since it's so obviously a bug fix. But we avoid changing plans in released branches, and at this point it's even too late for v18. Thank you again for the report. regards, tom lane
-
Re: Indexes on expressions with multiple columns and operators
Frédéric Yhuel <frederic.yhuel@dalibo.com> — 2025-09-22T13:37:23Z
On 9/20/25 18:51, Tom Lane wrote: > I concluded that maybe I was overthinking this part. We only really > need to check the rowcount estimate, since the indexscan cost estimate > is already okay. And stats_ext.sql seems to have gotten away with > assuming that rowcount estimates are reliably reproducible on > not-too-large tables. This bug affects use of extended statistics > too, so a test using those is good enough to show it's fixed; we don't > really need to use an expression index for the purpose. So, I added a > test case in stats_ext.sql and pushed it. OK, great! Thanks for the detailed explanation. Regarding extended statistics, it's unfortunate that they cannot be used in this case. Multivariate MCV statistics work as long as the number of rows in the table is reasonably small (100K) and the STATISTICS value for a column is high enough (it doesn't matter which column, because only the sample size matters). I wonder if this is an argument in favour of decoupling the sample size and the precision of the statistics. Here, we basically want the sample size to be as big as the table in order to include the few (NULL, WARNING) values. Or maybe we need a different kind of extended statistics?
-
Re: Indexes on expressions with multiple columns and operators
Andrei Lepikhov <lepihov@gmail.com> — 2025-09-22T13:57:43Z
On 22/9/2025 15:37, Frédéric Yhuel wrote: > I wonder if this is an argument in favour of decoupling the sample size > and the precision of the statistics. Here, we basically want the sample > size to be as big as the table in order to include the few (NULL, > WARNING) values. I also have seen how repeating ANALYZE on the same database drastically changes query plans ;(. It seems to me that with massive samples, many of the ANALYZE algorithms should be rewritten. In principle, statistical hooks exist. So, it is possible to invent an independent table analyser which will scan the whole table to get precise statistics. -- regards, Andrei Lepikhov
-
Re: Indexes on expressions with multiple columns and operators
Frédéric Yhuel <frederic.yhuel@dalibo.com> — 2025-09-22T16:09:05Z
On 9/22/25 15:57, Andrei Lepikhov wrote: > On 22/9/2025 15:37, Frédéric Yhuel wrote: >> I wonder if this is an argument in favour of decoupling the sample >> size and the precision of the statistics. Here, we basically want the >> sample size to be as big as the table in order to include the few >> (NULL, WARNING) values. > I also have seen how repeating ANALYZE on the same database drastically > changes query plans ;(. > It seems to me that with massive samples, many of the ANALYZE algorithms > should be rewritten. In principle, statistical hooks exist. So, it is > possible to invent an independent table analyser which will scan the > whole table to get precise statistics. > Interesting! I wonder how difficult it would be. However, in this specific case, I realised that it wouldn't solve the issue of ANALYZE being triggered when there are zero rows with (ackid, crit) = (NULL, WARNING). Partitioning would still work in this case, though, because ackid's null_frac would be zero for the partition containing the 'WARNING' value. I wonder if we could devise another kind of extended statistic that would provide these "partitioned statistics" without actually partitioning.
-
Re: Indexes on expressions with multiple columns and operators
Andrei Lepikhov <lepihov@gmail.com> — 2025-09-22T21:15:52Z
On 22/9/2025 18:09, Frédéric Yhuel wrote: > On 9/22/25 15:57, Andrei Lepikhov wrote: > I wonder if we could devise another kind of extended statistic that > would provide these "partitioned statistics" without actually partitioning.I'm not sure I fully understand your case, but SQL Server demonstrates an interesting approach: they have a WHERE clause attached to statistics. So, having implemented this, you may separate the whole range of values inside the table into 'partitions' by such a WHERE condition. It may solve at least one issue with the 'dependencies' statistics: a single number describing the dependency between any two values in the columns often leads to incorrect estimations, as I see. -- regards, Andrei Lepikhov
-
Re: Indexes on expressions with multiple columns and operators
Frédéric Yhuel <frederic.yhuel@dalibo.com> — 2025-09-23T10:20:44Z
On 9/22/25 23:15, Andrei Lepikhov wrote: > I'm not sure I fully understand your case, but SQL Server demonstrates > an interesting approach: they have a WHERE clause attached to > statistics. So, having implemented this, you may separate the whole > range of values inside the table into 'partitions' by such a WHERE > condition. Yes, from what I understood of the documentation [1], this is exactly what I would like! > It may solve at least one issue with the 'dependencies' statistics: a > single number describing the dependency between any two values in the > columns often leads to incorrect estimations, as I see. For what it's worth, I've never encountered a case in my life as a PostgreSQL support engineer where the 'dependency' kind could be useful. I only successfully used the 'mcv' kind once (and that was only partially successful, as it fixed the estimates but not the plan). [1] https://learn.microsoft.com/en-us/sql/t-sql/statements/create-statistics-transact-sql?view=sql-server-ver17#c-use-create-statistics-to-create-filtered-statistics
-
Re: Indexes on expressions with multiple columns and operators
Andrei Lepikhov <lepihov@gmail.com> — 2025-09-23T10:43:32Z
On 23/9/2025 12:20, Frédéric Yhuel wrote: > On 9/22/25 23:15, Andrei Lepikhov wrote: >> It may solve at least one issue with the 'dependencies' statistics: a >> single number describing the dependency between any two values in the >> columns often leads to incorrect estimations, as I see. > > For what it's worth, I've never encountered a case in my life as a > PostgreSQL support engineer where the 'dependency' kind could be useful. > I only successfully used the 'mcv' kind once (and that was only > partially successful, as it fixed the estimates but not the plan).Thanks for your feedback! I also don't think the 'dependencies' statistics are very useful now, especially considering how many computational resources it is needed in case of multiple columns involved. But is it the same for the 'distinct' statistics? It seems you should love it - the number of groups in GROUP-BY, DISTINCT, and even HashJoin should be estimated more precisely, no? -- regards, Andrei Lepikhov
-
Re: Indexes on expressions with multiple columns and operators
Frédéric Yhuel <frederic.yhuel@dalibo.com> — 2025-09-23T13:31:18Z
On 9/23/25 12:43, Andrei Lepikhov wrote: > But is it the same for the 'distinct' statistics? It seems you should > love it - the number of groups in GROUP-BY, DISTINCT, and even HashJoin > should be estimated more precisely, no? I think it has more potential, and I would love to use this weapon, but I haven't had the opportunity yet. It would be interesting to know how much it is used in real life. To get back to the topic of partitioned statistics, do you know if SQL Server is smart enough to handle this case [1] that we discussed last year? (with filtered statistics) [1] https://www.postgresql.org/message-id/flat/b860c71a-7cab-4d88-ad87-8c1f2eea9ae8%40dalibo.com
-
Re: Indexes on expressions with multiple columns and operators
Frédéric Yhuel <frederic.yhuel@dalibo.com> — 2025-09-25T10:41:18Z
On 9/23/25 12:20, Frédéric Yhuel wrote: > On 9/22/25 23:15, Andrei Lepikhov wrote: >> I'm not sure I fully understand your case, but SQL Server demonstrates >> an interesting approach: they have a WHERE clause attached to >> statistics. So, having implemented this, you may separate the whole >> range of values inside the table into 'partitions' by such a WHERE >> condition. > > Yes, from what I understood of the documentation [1], this is exactly > what I would like! I've tested it and I can confirm that it works very well. So, on SQL Server, you can do this: CREATE STATISTICS FooStats ON foo (ackid, crit) WHERE crit = 'WARNING'; It would be great to have a similar feature in PostgreSQL.
-
Re: Indexes on expressions with multiple columns and operators
Frédéric Yhuel <frederic.yhuel@dalibo.com> — 2025-09-25T10:43:18Z
On 9/23/25 15:31, Frédéric Yhuel wrote: > To get back to the topic of partitioned statistics, do you know if SQL > Server is smart enough to handle this case [1] that we discussed last > year? (with filtered statistics) > > [1] https://www.postgresql.org/message-id/flat/b860c71a-7cab-4d88- > ad87-8c1f2eea9ae8%40dalibo.com > Sorry, it doesn't make any sense. First of all, it's not possible to do something like this with SQL Sever: CREATE STATISTICS OrdersStats ON orders (id, product_id) WHERE product_id IN (SELECT id FROM products WHERE name = 'babar'); this is because you need to use simple scalar expressions in the filter clause. An even if it were possible... it would be completely useless in this case. Sorry for the noise. -
Re: Indexes on expressions with multiple columns and operators
Andrei Lepikhov <lepihov@gmail.com> — 2025-10-13T13:56:59Z
On 23/9/2025 15:31, Frédéric Yhuel wrote: > To get back to the topic of partitioned statistics, do you know if SQL > Server is smart enough to handle this case [1] that we discussed last > year? (with filtered statistics) > > [1] https://www.postgresql.org/message-id/flat/b860c71a-7cab-4d88- > ad87-8c1f2eea9ae8%40dalibo.com Not sure. After changing employers recently, I've lost access to the SQL Server instance :( and can't check how smart it is. However, as I recall, they design many features, and sometimes their optimiser finds a good solution/estimation through an unexpected approach. -- regards, Andrei Lepikhov, pgEdge
-
Re: Indexes on expressions with multiple columns and operators
Andrei Lepikhov <lepihov@gmail.com> — 2025-10-13T14:00:35Z
On 25/9/2025 12:41, Frédéric Yhuel wrote: > So, on SQL Server, you can do this: > CREATE STATISTICS FooStats ON foo (ackid, crit) WHERE crit = 'WARNING'; > > It would be great to have a similar feature in PostgreSQL.Nice! Thanks for the report. I think the only reason why Postgres doesn't have it yet is the computational cost. SQL Server utilises a separate background worker to manage this vast amount of statistical data. Not sure that Postgres core wants it. Maybe one more contrib extension can be a solution? -- regards, Andrei Lepikhov, pgEdge
-
Re: Indexes on expressions with multiple columns and operators
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-13T14:55:07Z
Andrei Lepikhov <lepihov@gmail.com> writes: > On 25/9/2025 12:41, Frédéric Yhuel wrote: >> So, on SQL Server, you can do this: >> CREATE STATISTICS FooStats ON foo (ackid, crit) WHERE crit = 'WARNING'; > Nice! Thanks for the report. I think the only reason why Postgres > doesn't have it yet is the computational cost. I think it's more lack of round tuits. If we had such an option for statistics objects, presumably we'd determine the applicability of a particular statistics object to a query the same way we do for partial indexes, namely try to prove the statistics' restriction condition from the query WHERE clauses. I've not heard complaints about that being unduly expensive. In the meantime, I believe the old-fashioned approach of creating a partial expression index and letting ANALYZE collect stats on that will serve, at least for simple statistics. regards, tom lane
-
Re: Indexes on expressions with multiple columns and operators
Andrei Lepikhov <lepihov@gmail.com> — 2025-10-14T09:29:27Z
On 13/10/2025 16:55, Tom Lane wrote: > Andrei Lepikhov <lepihov@gmail.com> writes: >> On 25/9/2025 12:41, Frédéric Yhuel wrote: >>> So, on SQL Server, you can do this: >>> CREATE STATISTICS FooStats ON foo (ackid, crit) WHERE crit = 'WARNING'; > >> Nice! Thanks for the report. I think the only reason why Postgres >> doesn't have it yet is the computational cost. > > I think it's more lack of round tuits. If we had such an option for > statistics objects, presumably we'd determine the applicability of a > particular statistics object to a query the same way we do for partial > indexes, namely try to prove the statistics' restriction condition > from the query WHERE clauses. I've not heard complaints about that > being unduly expensive. > > In the meantime, I believe the old-fashioned approach of creating > a partial expression index and letting ANALYZE collect stats on that > will serve, at least for simple statistics.I know at least two extensions (one of which is mine) that attempt to analyse query post-execution state, identify unsuccessful predictions on cardinality, number of groups, and work_mem, and fix these issues by creating MCV and distinct extended statistics. Of course, without extended statistics on join clauses, their effect is highly limited, but we are preparing ;). Many combinations of clauses may occur. Partial indexes can affect the whole system's performance in automatic mode. Additionally, I would personally like to play the same game as SQL Server already does - compute statistics in an efficient manner - during a Scan. The filter of such a scan may serve as a WHERE condition in the extended statistics. -- regards, Andrei Lepikhov, pgEdge