v1-0004-Extend-multi-column-MCV-cap-to-AND-clauses-inside-OR.patch
text/x-patch
Filename: v1-0004-Extend-multi-column-MCV-cap-to-AND-clauses-inside-OR.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v1-0004
Subject: Extend multi-column MCV cap to AND clauses inside OR expressions
| File | + | − |
|---|---|---|
| src/backend/statistics/extended_stats.c | 10 | 2 |
| src/backend/statistics/mcv.c | 56 | 12 |
| src/include/statistics/extended_stats_internal.h | 2 | 1 |
| src/test/regress/expected/stats_ext.out | 15 | 1 |
| src/test/regress/sql/stats_ext.sql | 7 | 1 |
From 145c56ff65785eb559e720f3c04b908a57c61940 Mon Sep 17 00:00:00 2001
From: Enrique Sanchez Cardoso <enriqueesanchz@gmail.com>
Date: Sun, 24 May 2026 19:09:26 +0200
Subject: [PATCH 4/4] Extend multi-column MCV cap to AND clauses inside OR
expressions
---
src/backend/statistics/extended_stats.c | 12 +++-
src/backend/statistics/mcv.c | 68 +++++++++++++++----
.../statistics/extended_stats_internal.h | 3 +-
src/test/regress/expected/stats_ext.out | 16 ++++-
src/test/regress/sql/stats_ext.sql | 8 ++-
5 files changed, 90 insertions(+), 17 deletions(-)
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index da6f6315698..fc6f7905bb3 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -1915,7 +1915,8 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
overlap_basesel,
mcv_totalsel,
clause_sel,
- overlap_sel;
+ overlap_sel,
+ clause_cap;
/*
* "Simple" selectivity of the next clause and its overlap
@@ -1945,7 +1946,8 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
&mcv_basesel,
&overlap_mcvsel,
&overlap_basesel,
- &mcv_totalsel);
+ &mcv_totalsel,
+ &clause_cap);
/*
* Combine the simple and multi-column estimates.
@@ -1959,11 +1961,17 @@ statext_mcv_clauselist_selectivity(PlannerInfo *root, List *clauses, int varReli
if (bms_is_member(listidx, simple_clauses))
clause_sel = simple_sel;
else
+ {
clause_sel = mcv_combine_selectivities(simple_sel,
mcv_sel,
mcv_basesel,
mcv_totalsel);
+ /* Cap the contribution of values not found in the MCV. */
+ if (clause_sel > clause_cap)
+ clause_sel = clause_cap;
+ }
+
overlap_sel = mcv_combine_selectivities(overlap_simple_sel,
overlap_mcvsel,
overlap_basesel,
diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c
index 62761c58e33..df75087391c 100644
--- a/src/backend/statistics/mcv.c
+++ b/src/backend/statistics/mcv.c
@@ -1577,6 +1577,40 @@ mcv_cap_multiplier(List *clauses)
return multiplier;
}
+/*
+ * mcv_compute_cap
+ * Compute a selectivity cap based on the least common MCV frequency.
+ *
+ * When one equality/IN clause covers each MCV dimension, value combinations
+ * not found in the MCV can't be more frequent than the least common tracked
+ * combination. The cap is: matched MCV frequency plus the number of
+ * non-MCV combinations times the least common MCV frequency.
+ *
+ * Returns 1.0 (no cap) when the clauses don't fully cover all dimensions
+ * or contain unsupported clause types.
+ */
+static Selectivity
+mcv_compute_cap(MCVList *mcv, List *clauses, Selectivity mcv_sel,
+ int64 matched_count)
+{
+ int64 cap_mult;
+ int64 non_mcv_mult;
+ Selectivity cap;
+
+ if (list_length(clauses) != mcv->ndimensions)
+ return 1.0;
+
+ cap_mult = mcv_cap_multiplier(clauses);
+ non_mcv_mult = cap_mult - matched_count;
+
+ if (non_mcv_mult <= 0)
+ return 1.0;
+
+ cap = mcv_sel + non_mcv_mult * mcv->items[mcv->nitems - 1].frequency;
+ CLAMP_PROBABILITY(cap);
+ return cap;
+}
+
/*
* match the attribute/expression to a dimension of the statistic
*
@@ -2144,17 +2178,7 @@ mcv_clauselist_selectivity(PlannerInfo *root, StatisticExtInfo *stat,
* combination is not among the most common, so it can't be more frequent
* than the least common tracked combination.
*/
- if (mcv->ndimensions == list_length(clauses))
- {
- int64 cap_mult = mcv_cap_multiplier(clauses);
- int64 non_mcv_mult = cap_mult - matched_count;
-
- if (non_mcv_mult > 0)
- {
- *cap = s + non_mcv_mult * mcv->items[mcv->nitems - 1].frequency;
- CLAMP_PROBABILITY(*cap);
- }
- }
+ *cap = mcv_compute_cap(mcv, clauses, s, matched_count);
return s;
}
@@ -2202,11 +2226,16 @@ Selectivity
mcv_clause_selectivity_or(PlannerInfo *root, StatisticExtInfo *stat,
MCVList *mcv, Node *clause, bool **or_matches,
Selectivity *basesel, Selectivity *overlap_mcvsel,
- Selectivity *overlap_basesel, Selectivity *totalsel)
+ Selectivity *overlap_basesel, Selectivity *totalsel,
+ Selectivity *clause_cap)
{
Selectivity s = 0.0;
bool *new_matches;
int i;
+ int64 matched_count = 0;
+
+ /* default: no cap on clause selectivity */
+ *clause_cap = 1.0;
/* build the OR-matches bitmap, if not built already */
if (*or_matches == NULL)
@@ -2233,6 +2262,7 @@ mcv_clause_selectivity_or(PlannerInfo *root, StatisticExtInfo *stat,
{
s += mcv->items[i].frequency;
*basesel += mcv->items[i].base_frequency;
+ matched_count++;
if ((*or_matches)[i])
{
@@ -2247,6 +2277,20 @@ mcv_clause_selectivity_or(PlannerInfo *root, StatisticExtInfo *stat,
pfree(new_matches);
+ /*
+ * When there is one equality/IN clause per MCV dimension, cap the
+ * contribution of value combinations not found in the MCV. Each such
+ * combination is not among the most common, so it can't be more frequent
+ * than the least common tracked combination.
+ */
+ if (is_andclause(clause))
+ {
+ BoolExpr *bexpr = (BoolExpr *) clause;
+
+ *clause_cap = mcv_compute_cap(mcv, bexpr->args, s,
+ matched_count);
+ }
+
return s;
}
diff --git a/src/include/statistics/extended_stats_internal.h b/src/include/statistics/extended_stats_internal.h
index 01b5f67b843..10f41f87564 100644
--- a/src/include/statistics/extended_stats_internal.h
+++ b/src/include/statistics/extended_stats_internal.h
@@ -140,6 +140,7 @@ extern Selectivity mcv_clause_selectivity_or(PlannerInfo *root,
Selectivity *basesel,
Selectivity *overlap_mcvsel,
Selectivity *overlap_basesel,
- Selectivity *totalsel);
+ Selectivity *totalsel,
+ Selectivity *clause_cap);
#endif /* EXTENDED_STATS_INTERNAL_H */
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 7ea244f7851..c712679b573 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -2931,7 +2931,7 @@ DROP TABLE mcv_lists_partial;
-- P(a=0)=0.5 and P(b=0)=0.5, so the independence estimate is 0.25 * N.
-- After building MCV statistics the cap limits the combined estimate to the
-- least-common MCV frequency, eliminating most of the over-estimation.
-CREATE TABLE mcv_cap (a INT, b INT) WITH (autovacuum_enabled = off);
+CREATE TABLE mcv_cap (a INT, b INT, c INT DEFAULT 0) WITH (autovacuum_enabled = off);
INSERT INTO mcv_cap
SELECT 0, b FROM generate_series(1, 99) b, generate_series(1, 100) r;
INSERT INTO mcv_cap
@@ -2979,6 +2979,20 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_cap WHERE a = 0 AND b IN (
200 | 0
(1 row)
+-- partial MCV match inside OR (a=0, b=99)
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_cap WHERE c = 1 OR (a = 0 AND b IN (0, 99))');
+ estimated | actual
+-----------+--------
+ 200 | 100
+(1 row)
+
+-- no MCV match inside OR
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_cap WHERE c = 1 OR (a = 0 AND b = 0)');
+ estimated | actual
+-----------+--------
+ 100 | 0
+(1 row)
+
DROP TABLE mcv_cap;
-- check the ability to use multiple MCV lists
CREATE TABLE mcv_lists_multi (
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 8e0b8c0eb5c..926dfaa4e6d 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -1468,7 +1468,7 @@ DROP TABLE mcv_lists_partial;
-- P(a=0)=0.5 and P(b=0)=0.5, so the independence estimate is 0.25 * N.
-- After building MCV statistics the cap limits the combined estimate to the
-- least-common MCV frequency, eliminating most of the over-estimation.
-CREATE TABLE mcv_cap (a INT, b INT) WITH (autovacuum_enabled = off);
+CREATE TABLE mcv_cap (a INT, b INT, c INT DEFAULT 0) WITH (autovacuum_enabled = off);
INSERT INTO mcv_cap
SELECT 0, b FROM generate_series(1, 99) b, generate_series(1, 100) r;
@@ -1497,6 +1497,12 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_cap WHERE a = 0 AND b IN (
-- no MCV match
SELECT * FROM check_estimated_rows('SELECT * FROM mcv_cap WHERE a = 0 AND b IN (0, 100)');
+-- partial MCV match inside OR (a=0, b=99)
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_cap WHERE c = 1 OR (a = 0 AND b IN (0, 99))');
+
+-- no MCV match inside OR
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_cap WHERE c = 1 OR (a = 0 AND b = 0)');
+
DROP TABLE mcv_cap;
-- check the ability to use multiple MCV lists
--
2.43.0