From cfe0851bc00880ef244938ee5cfdc241d8951049 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Sun, 4 Oct 2026 23:30:36 -0400 Subject: [PATCH v1 1/2] Count NULL pairs in IS [NOT] DISTINCT FROM selectivity clause_selectivity() estimated IS NOT DISTINCT FROM the same as "=", so rows with both inputs NULL were not counted. Add the product of the two null fractions before negating. Semijoins and antijoins are left as they were. Discussion: https://postgr.es/m/17545-a0ca4de888953169@postgresql.org --- src/backend/optimizer/path/clausesel.c | 37 ++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c index 25c4d177ad9..53de0018bd6 100644 --- a/src/backend/optimizer/path/clausesel.c +++ b/src/backend/optimizer/path/clausesel.c @@ -14,6 +14,8 @@ */ #include "postgres.h" +#include "access/htup_details.h" +#include "catalog/pg_statistic.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/optimizer.h" @@ -42,6 +44,7 @@ static void addRangeClause(RangeQueryClause **rqlist, Node *clause, bool varonleft, bool isLTsel, Selectivity s2); static RelOptInfo *find_single_rel_for_clauses(PlannerInfo *root, List *clauses); +static double expr_nullfrac(PlannerInfo *root, Node *expr); static Selectivity clauselist_selectivity_or(PlannerInfo *root, List *clauses, int varRelid, @@ -577,6 +580,25 @@ find_single_rel_for_clauses(PlannerInfo *root, List *clauses) return NULL; /* no clauses */ } +/* + * expr_nullfrac - + * Return the null fraction of an expression, or zero if we have no + * statistics for it. + */ +static double +expr_nullfrac(PlannerInfo *root, Node *expr) +{ + VariableStatData vardata; + double nullfrac = 0.0; + + examine_variable(root, expr, 0, &vardata); + if (HeapTupleIsValid(vardata.statsTuple)) + nullfrac = ((Form_pg_statistic) GETSTRUCT(vardata.statsTuple))->stanullfrac; + ReleaseVariableStats(vardata); + + return nullfrac; +} + /* * treat_as_join_clause - * Decide whether an operator clause is to be handled by the @@ -854,11 +876,22 @@ clause_selectivity_ext(PlannerInfo *root, /* * DistinctExpr has the same representation as OpExpr, but the * contained operator is "=" not "<>", so we must negate the result. - * This estimation method doesn't give the right behavior for nulls, - * but it's better than doing nothing. + * First add the fraction of rows having both inputs null, which "=" + * does not count. We don't do that for semijoins and antijoins, + * where the "=" estimate is not a fraction of row pairs. */ if (IsA(clause, DistinctExpr)) + { + if (sjinfo == NULL || + (sjinfo->jointype != JOIN_SEMI && + sjinfo->jointype != JOIN_ANTI)) + { + s1 += expr_nullfrac(root, linitial(opclause->args)) * + expr_nullfrac(root, lsecond(opclause->args)); + CLAMP_PROBABILITY(s1); + } s1 = 1.0 - s1; + } } else if (is_funcclause(clause)) { -- 2.37.1 (Apple Git-137.1)