Fix mergejoin cost estimation so that we consider the statistical ranges of
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 8 Dec 2007 21:05:11 +0000 (21:05 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 8 Dec 2007 21:05:11 +0000 (21:05 +0000)
the two join variables at both ends: not only trailing rows that need not be
scanned because there cannot be a match on the other side, but initial rows
that will be scanned without possibly having a match.  This allows a more
realistic estimate of startup cost to be made, per recent pgsql-performance
discussion.  In passing, fix a couple of bugs that had crept into
mergejoinscansel: it was not quite up to speed for the task of estimating
descending-order scans, which is a new requirement in 8.3.

src/backend/optimizer/path/costsize.c
src/backend/utils/adt/selfuncs.c
src/include/nodes/relation.h
src/include/utils/selfuncs.h

index fc95399b396ff410d8de9f8b4ee695f04a93a214..c1e1651c7989afce8ad7ac0242cf87bc8570cbdb 100644 (file)
@@ -54,7 +54,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.189 2007/11/15 22:25:15 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.190 2007/12/08 21:05:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1372,12 +1372,16 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
    double      outer_path_rows = PATH_ROWS(outer_path);
    double      inner_path_rows = PATH_ROWS(inner_path);
    double      outer_rows,
-               inner_rows;
+               inner_rows,
+               outer_skip_rows,
+               inner_skip_rows;
    double      mergejointuples,
                rescannedtuples;
    double      rescanratio;
-   Selectivity outerscansel,
-               innerscansel;
+   Selectivity outerstartsel,
+               outerendsel,
+               innerstartsel,
+               innerendsel;
    Selectivity joininfactor;
    Path        sort_path;      /* dummy for result of cost_sort */
 
@@ -1444,10 +1448,12 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
     * A merge join will stop as soon as it exhausts either input stream
     * (unless it's an outer join, in which case the outer side has to be
     * scanned all the way anyway).  Estimate fraction of the left and right
-    * inputs that will actually need to be scanned. We use only the first
-    * (most significant) merge clause for this purpose.  Since
-    * mergejoinscansel() is a fairly expensive computation, we cache the
-    * results in the merge clause RestrictInfo.
+    * inputs that will actually need to be scanned.  Likewise, we can
+    * estimate the number of rows that will be skipped before the first
+    * join pair is found, which should be factored into startup cost.
+    * We use only the first (most significant) merge clause for this purpose.
+    * Since mergejoinscansel() is a fairly expensive computation, we cache
+    * the results in the merge clause RestrictInfo.
     */
    if (mergeclauses && path->jpath.jointype != JOIN_FULL)
    {
@@ -1478,37 +1484,61 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
                          outer_path->parent->relids))
        {
            /* left side of clause is outer */
-           outerscansel = cache->leftscansel;
-           innerscansel = cache->rightscansel;
+           outerstartsel = cache->leftstartsel;
+           outerendsel = cache->leftendsel;
+           innerstartsel = cache->rightstartsel;
+           innerendsel = cache->rightendsel;
        }
        else
        {
            /* left side of clause is inner */
-           outerscansel = cache->rightscansel;
-           innerscansel = cache->leftscansel;
+           outerstartsel = cache->rightstartsel;
+           outerendsel = cache->rightendsel;
+           innerstartsel = cache->leftstartsel;
+           innerendsel = cache->leftendsel;
        }
        if (path->jpath.jointype == JOIN_LEFT)
-           outerscansel = 1.0;
+       {
+           outerstartsel = 0.0;
+           outerendsel = 1.0;
+       }
        else if (path->jpath.jointype == JOIN_RIGHT)
-           innerscansel = 1.0;
+       {
+           innerstartsel = 0.0;
+           innerendsel = 1.0;
+       }
    }
    else
    {
        /* cope with clauseless or full mergejoin */
-       outerscansel = innerscansel = 1.0;
+       outerstartsel = innerstartsel = 0.0;
+       outerendsel = innerendsel = 1.0;
    }
 
-   /* convert selectivity to row count; must scan at least one row */
-   outer_rows = clamp_row_est(outer_path_rows * outerscansel);
-   inner_rows = clamp_row_est(inner_path_rows * innerscansel);
+   /*
+    * Convert selectivities to row counts.  We force outer_rows and
+    * inner_rows to be at least 1, but the skip_rows estimates can be zero.
+    */
+   outer_skip_rows = rint(outer_path_rows * outerstartsel);
+   inner_skip_rows = rint(inner_path_rows * innerstartsel);
+   outer_rows = clamp_row_est(outer_path_rows * outerendsel);
+   inner_rows = clamp_row_est(inner_path_rows * innerendsel);
+
+   Assert(outer_skip_rows <= outer_rows);
+   Assert(inner_skip_rows <= inner_rows);
 
    /*
     * Readjust scan selectivities to account for above rounding.  This is
     * normally an insignificant effect, but when there are only a few rows in
     * the inputs, failing to do this makes for a large percentage error.
     */
-   outerscansel = outer_rows / outer_path_rows;
-   innerscansel = inner_rows / inner_path_rows;
+   outerstartsel = outer_skip_rows / outer_path_rows;
+   innerstartsel = inner_skip_rows / inner_path_rows;
+   outerendsel = outer_rows / outer_path_rows;
+   innerendsel = inner_rows / inner_path_rows;
+
+   Assert(outerstartsel <= outerendsel);
+   Assert(innerstartsel <= innerendsel);
 
    /* cost of source data */
 
@@ -1522,14 +1552,18 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
                  outer_path->parent->width,
                  -1.0);
        startup_cost += sort_path.startup_cost;
+       startup_cost += (sort_path.total_cost - sort_path.startup_cost)
+           * outerstartsel;
        run_cost += (sort_path.total_cost - sort_path.startup_cost)
-           * outerscansel;
+           * (outerendsel - outerstartsel);
    }
    else
    {
        startup_cost += outer_path->startup_cost;
+       startup_cost += (outer_path->total_cost - outer_path->startup_cost)
+           * outerstartsel;
        run_cost += (outer_path->total_cost - outer_path->startup_cost)
-           * outerscansel;
+           * (outerendsel - outerstartsel);
    }
 
    if (innersortkeys)          /* do we need to sort inner? */
@@ -1542,14 +1576,18 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
                  inner_path->parent->width,
                  -1.0);
        startup_cost += sort_path.startup_cost;
+       startup_cost += (sort_path.total_cost - sort_path.startup_cost)
+           * innerstartsel * rescanratio;
        run_cost += (sort_path.total_cost - sort_path.startup_cost)
-           * innerscansel * rescanratio;
+           * (innerendsel - innerstartsel) * rescanratio;
    }
    else
    {
        startup_cost += inner_path->startup_cost;
+       startup_cost += (inner_path->total_cost - inner_path->startup_cost)
+           * innerstartsel * rescanratio;
        run_cost += (inner_path->total_cost - inner_path->startup_cost)
-           * innerscansel * rescanratio;
+           * (innerendsel - innerstartsel) * rescanratio;
    }
 
    /* CPU costs */
@@ -1571,8 +1609,11 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
     * joininfactor.
     */
    startup_cost += merge_qual_cost.startup;
+   startup_cost += merge_qual_cost.per_tuple *
+       (outer_skip_rows + inner_skip_rows * rescanratio);
    run_cost += merge_qual_cost.per_tuple *
-       (outer_rows + inner_rows * rescanratio);
+       ((outer_rows - outer_skip_rows) +
+        (inner_rows - inner_skip_rows) * rescanratio);
 
    /*
     * For each tuple that gets through the mergejoin proper, we charge
@@ -1597,8 +1638,10 @@ cached_scansel(PlannerInfo *root, RestrictInfo *rinfo, PathKey *pathkey)
 {
    MergeScanSelCache *cache;
    ListCell   *lc;
-   Selectivity leftscansel,
-               rightscansel;
+   Selectivity leftstartsel,
+               leftendsel,
+               rightstartsel,
+               rightendsel;
    MemoryContext oldcontext;
 
    /* Do we have this result already? */
@@ -1617,8 +1660,10 @@ cached_scansel(PlannerInfo *root, RestrictInfo *rinfo, PathKey *pathkey)
                     pathkey->pk_opfamily,
                     pathkey->pk_strategy,
                     pathkey->pk_nulls_first,
-                    &leftscansel,
-                    &rightscansel);
+                    &leftstartsel,
+                    &leftendsel,
+                    &rightstartsel,
+                    &rightendsel);
 
    /* Cache the result in suitably long-lived workspace */
    oldcontext = MemoryContextSwitchTo(root->planner_cxt);
@@ -1627,8 +1672,10 @@ cached_scansel(PlannerInfo *root, RestrictInfo *rinfo, PathKey *pathkey)
    cache->opfamily = pathkey->pk_opfamily;
    cache->strategy = pathkey->pk_strategy;
    cache->nulls_first = pathkey->pk_nulls_first;
-   cache->leftscansel = leftscansel;
-   cache->rightscansel = rightscansel;
+   cache->leftstartsel = leftstartsel;
+   cache->leftendsel = leftendsel;
+   cache->rightstartsel = rightstartsel;
+   cache->rightendsel = rightendsel;
 
    rinfo->scansel_cache = lappend(rinfo->scansel_cache, cache);
 
index 103f4dc9d7620b8cd3ca62d712e0829ccf4ffb52..0b0d992a3b19d2133c07a747bc5df46304ce17e4 100644 (file)
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.241 2007/11/15 22:25:16 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.242 2007/12/08 21:05:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -128,8 +128,8 @@ static double convert_one_bytea_to_scalar(unsigned char *value, int valuelen,
                            int rangelo, int rangehi);
 static char *convert_string_datum(Datum value, Oid typid);
 static double convert_timevalue_to_scalar(Datum value, Oid typid);
-static bool get_variable_maximum(PlannerInfo *root, VariableStatData *vardata,
-                    Oid sortop, Datum *max);
+static bool get_variable_range(PlannerInfo *root, VariableStatData *vardata,
+                    Oid sortop, Datum *min, Datum *max);
 static Selectivity prefix_selectivity(VariableStatData *vardata,
                   Oid vartype, Oid opfamily, Const *prefixcon);
 static Selectivity pattern_selectivity(Const *patt, Pattern_Type ptype);
@@ -2172,18 +2172,24 @@ icnlikejoinsel(PG_FUNCTION_ARGS)
  * we can estimate how much of the input will actually be read.  This
  * can have a considerable impact on the cost when using indexscans.
  *
+ * Also, we can estimate how much of each input has to be read before the
+ * first join pair is found, which will affect the join's startup time.
+ *
  * clause should be a clause already known to be mergejoinable.  opfamily,
  * strategy, and nulls_first specify the sort ordering being used.
  *
- * *leftscan is set to the fraction of the left-hand variable expected
- * to be scanned (0 to 1), and similarly *rightscan for the right-hand
- * variable.
+ * The outputs are:
+ *     *leftstart is set to the fraction of the left-hand variable expected
+ *      to be scanned before the first join pair is found (0 to 1).
+ *     *leftend is set to the fraction of the left-hand variable expected
+ *      to be scanned before the join terminates (0 to 1).
+ *     *rightstart, *rightend similarly for the right-hand variable.
  */
 void
 mergejoinscansel(PlannerInfo *root, Node *clause,
                 Oid opfamily, int strategy, bool nulls_first,
-                Selectivity *leftscan,
-                Selectivity *rightscan)
+                Selectivity *leftstart, Selectivity *leftend,
+                Selectivity *rightstart, Selectivity *rightend)
 {
    Node       *left,
               *right;
@@ -2196,14 +2202,23 @@ mergejoinscansel(PlannerInfo *root, Node *clause,
    Oid         opno,
                lsortop,
                rsortop,
+               lstatop,
+               rstatop,
+               ltop,
                leop,
+               revltop,
                revleop;
-   Datum       leftmax,
+   bool        isgt;
+   Datum       leftmin,
+               leftmax,
+               rightmin,
                rightmax;
    double      selec;
 
    /* Set default results if we can't figure anything out. */
-   *leftscan = *rightscan = 1.0;
+   /* XXX should default "start" fraction be a bit more than 0? */
+   *leftstart = *rightstart = 0.0;
+   *leftend = *rightend = 1.0;
 
    /* Deconstruct the merge clause */
    if (!is_opclause(clause))
@@ -2229,30 +2244,103 @@ mergejoinscansel(PlannerInfo *root, Node *clause,
 
    /*
     * Look up the various operators we need.  If we don't find them all, it
-    * probably means the opfamily is broken, but we cope anyway.
+    * probably means the opfamily is broken, but we just fail silently.
+    *
+    * Note: we expect that pg_statistic histograms will be sorted by the
+    * '<' operator, regardless of which sort direction we are considering.
     */
    switch (strategy)
    {
        case BTLessStrategyNumber:
-           lsortop = get_opfamily_member(opfamily, op_lefttype, op_lefttype,
-                                         BTLessStrategyNumber);
-           rsortop = get_opfamily_member(opfamily, op_righttype, op_righttype,
-                                         BTLessStrategyNumber);
-           leop = get_opfamily_member(opfamily, op_lefttype, op_righttype,
-                                      BTLessEqualStrategyNumber);
-           revleop = get_opfamily_member(opfamily, op_righttype, op_lefttype,
-                                         BTLessEqualStrategyNumber);
+           isgt = false;
+           if (op_lefttype == op_righttype)
+           {
+               /* easy case */
+               ltop = get_opfamily_member(opfamily,
+                                          op_lefttype, op_righttype,
+                                          BTLessStrategyNumber);
+               leop = get_opfamily_member(opfamily,
+                                          op_lefttype, op_righttype,
+                                          BTLessEqualStrategyNumber);
+               lsortop = ltop;
+               rsortop = ltop;
+               lstatop = lsortop;
+               rstatop = rsortop;
+               revltop = ltop;
+               revleop = leop;
+           }
+           else
+           {
+               ltop = get_opfamily_member(opfamily,
+                                          op_lefttype, op_righttype,
+                                          BTLessStrategyNumber);
+               leop = get_opfamily_member(opfamily,
+                                          op_lefttype, op_righttype,
+                                          BTLessEqualStrategyNumber);
+               lsortop = get_opfamily_member(opfamily,
+                                             op_lefttype, op_lefttype,
+                                             BTLessStrategyNumber);
+               rsortop = get_opfamily_member(opfamily,
+                                             op_righttype, op_righttype,
+                                             BTLessStrategyNumber);
+               lstatop = lsortop;
+               rstatop = rsortop;
+               revltop = get_opfamily_member(opfamily,
+                                             op_righttype, op_lefttype,
+                                             BTLessStrategyNumber);
+               revleop = get_opfamily_member(opfamily,
+                                             op_righttype, op_lefttype,
+                                             BTLessEqualStrategyNumber);
+           }
            break;
        case BTGreaterStrategyNumber:
            /* descending-order case */
-           lsortop = get_opfamily_member(opfamily, op_lefttype, op_lefttype,
-                                         BTGreaterStrategyNumber);
-           rsortop = get_opfamily_member(opfamily, op_righttype, op_righttype,
-                                         BTGreaterStrategyNumber);
-           leop = get_opfamily_member(opfamily, op_lefttype, op_righttype,
-                                      BTGreaterEqualStrategyNumber);
-           revleop = get_opfamily_member(opfamily, op_righttype, op_lefttype,
-                                         BTGreaterEqualStrategyNumber);
+           isgt = true;
+           if (op_lefttype == op_righttype)
+           {
+               /* easy case */
+               ltop = get_opfamily_member(opfamily,
+                                          op_lefttype, op_righttype,
+                                          BTGreaterStrategyNumber);
+               leop = get_opfamily_member(opfamily,
+                                          op_lefttype, op_righttype,
+                                          BTGreaterEqualStrategyNumber);
+               lsortop = ltop;
+               rsortop = ltop;
+               lstatop = get_opfamily_member(opfamily,
+                                             op_lefttype, op_lefttype,
+                                             BTLessStrategyNumber);
+               rstatop = lstatop;
+               revltop = ltop;
+               revleop = leop;
+           }
+           else
+           {
+               ltop = get_opfamily_member(opfamily,
+                                          op_lefttype, op_righttype,
+                                          BTGreaterStrategyNumber);
+               leop = get_opfamily_member(opfamily,
+                                          op_lefttype, op_righttype,
+                                          BTGreaterEqualStrategyNumber);
+               lsortop = get_opfamily_member(opfamily,
+                                             op_lefttype, op_lefttype,
+                                             BTGreaterStrategyNumber);
+               rsortop = get_opfamily_member(opfamily,
+                                             op_righttype, op_righttype,
+                                             BTGreaterStrategyNumber);
+               lstatop = get_opfamily_member(opfamily,
+                                             op_lefttype, op_lefttype,
+                                             BTLessStrategyNumber);
+               rstatop = get_opfamily_member(opfamily,
+                                             op_righttype, op_righttype,
+                                             BTLessStrategyNumber);
+               revltop = get_opfamily_member(opfamily,
+                                             op_righttype, op_lefttype,
+                                             BTGreaterStrategyNumber);
+               revleop = get_opfamily_member(opfamily,
+                                             op_righttype, op_lefttype,
+                                             BTGreaterEqualStrategyNumber);
+           }
            break;
        default:
            goto fail;          /* shouldn't get here */
@@ -2260,66 +2348,133 @@ mergejoinscansel(PlannerInfo *root, Node *clause,
 
    if (!OidIsValid(lsortop) ||
        !OidIsValid(rsortop) ||
+       !OidIsValid(lstatop) ||
+       !OidIsValid(rstatop) ||
+       !OidIsValid(ltop) ||
        !OidIsValid(leop) ||
+       !OidIsValid(revltop) ||
        !OidIsValid(revleop))
        goto fail;              /* insufficient info in catalogs */
 
-   /* Try to get maximum values of both inputs */
-   if (!get_variable_maximum(root, &leftvar, lsortop, &leftmax))
-       goto fail;              /* no max available from stats */
-
-   if (!get_variable_maximum(root, &rightvar, rsortop, &rightmax))
-       goto fail;              /* no max available from stats */
+   /* Try to get ranges of both inputs */
+   if (!isgt)
+   {
+       if (!get_variable_range(root, &leftvar, lstatop,
+                               &leftmin, &leftmax))
+           goto fail;          /* no range available from stats */
+       if (!get_variable_range(root, &rightvar, rstatop,
+                               &rightmin, &rightmax))
+           goto fail;          /* no range available from stats */
+   }
+   else
+   {
+       /* need to swap the max and min */
+       if (!get_variable_range(root, &leftvar, lstatop,
+                               &leftmax, &leftmin))
+           goto fail;          /* no range available from stats */
+       if (!get_variable_range(root, &rightvar, rstatop,
+                               &rightmax, &rightmin))
+           goto fail;          /* no range available from stats */
+   }
 
    /*
     * Now, the fraction of the left variable that will be scanned is the
     * fraction that's <= the right-side maximum value.  But only believe
-    * non-default estimates, else stick with our 1.0.  Also, if the sort
-    * order is nulls-first, we're going to have to read over any nulls too.
+    * non-default estimates, else stick with our 1.0.
     */
-   selec = scalarineqsel(root, leop, false, &leftvar,
+   selec = scalarineqsel(root, leop, isgt, &leftvar,
                          rightmax, op_righttype);
    if (selec != DEFAULT_INEQ_SEL)
-   {
-       if (nulls_first && HeapTupleIsValid(leftvar.statsTuple))
-       {
-           Form_pg_statistic stats;
-
-           stats = (Form_pg_statistic) GETSTRUCT(leftvar.statsTuple);
-           selec += stats->stanullfrac;
-           CLAMP_PROBABILITY(selec);
-       }
-       *leftscan = selec;
-   }
+       *leftend = selec;
 
    /* And similarly for the right variable. */
-   selec = scalarineqsel(root, revleop, false, &rightvar,
+   selec = scalarineqsel(root, revleop, isgt, &rightvar,
                          leftmax, op_lefttype);
    if (selec != DEFAULT_INEQ_SEL)
+       *rightend = selec;
+
+   /*
+    * Only one of the two "end" fractions can really be less than 1.0;
+    * believe the smaller estimate and reset the other one to exactly 1.0.
+    * If we get exactly equal estimates (as can easily happen with
+    * self-joins), believe neither.
+    */
+   if (*leftend > *rightend)
+       *leftend = 1.0;
+   else if (*leftend < *rightend)
+       *rightend = 1.0;
+   else
+       *leftend = *rightend = 1.0;
+
+   /*
+    * Also, the fraction of the left variable that will be scanned before
+    * the first join pair is found is the fraction that's < the right-side
+    * minimum value.  But only believe non-default estimates, else stick with
+    * our own default.
+    */
+   selec = scalarineqsel(root, ltop, isgt, &leftvar,
+                         rightmin, op_righttype);
+   if (selec != DEFAULT_INEQ_SEL)
+       *leftstart = selec;
+
+   /* And similarly for the right variable. */
+   selec = scalarineqsel(root, revltop, isgt, &rightvar,
+                         leftmin, op_lefttype);
+   if (selec != DEFAULT_INEQ_SEL)
+       *rightstart = selec;
+
+   /*
+    * Only one of the two "start" fractions can really be more than zero;
+    * believe the larger estimate and reset the other one to exactly 0.0.
+    * If we get exactly equal estimates (as can easily happen with
+    * self-joins), believe neither.
+    */
+   if (*leftstart < *rightstart)
+       *leftstart = 0.0;
+   else if (*leftstart > *rightstart)
+       *rightstart = 0.0;
+   else
+       *leftstart = *rightstart = 0.0;
+
+   /*
+    * If the sort order is nulls-first, we're going to have to skip over any
+    * nulls too.  These would not have been counted by scalarineqsel, and
+    * we can safely add in this fraction regardless of whether we believe
+    * scalarineqsel's results or not.  But be sure to clamp the sum to 1.0!
+    */
+   if (nulls_first)
    {
-       if (nulls_first && HeapTupleIsValid(rightvar.statsTuple))
-       {
-           Form_pg_statistic stats;
+       Form_pg_statistic stats;
 
+       if (HeapTupleIsValid(leftvar.statsTuple))
+       {
+           stats = (Form_pg_statistic) GETSTRUCT(leftvar.statsTuple);
+           *leftstart += stats->stanullfrac;
+           CLAMP_PROBABILITY(*leftstart);
+           *leftend += stats->stanullfrac;
+           CLAMP_PROBABILITY(*leftend);
+       }
+       if (HeapTupleIsValid(rightvar.statsTuple))
+       {
            stats = (Form_pg_statistic) GETSTRUCT(rightvar.statsTuple);
-           selec += stats->stanullfrac;
-           CLAMP_PROBABILITY(selec);
+           *rightstart += stats->stanullfrac;
+           CLAMP_PROBABILITY(*rightstart);
+           *rightend += stats->stanullfrac;
+           CLAMP_PROBABILITY(*rightend);
        }
-       *rightscan = selec;
    }
 
-   /*
-    * Only one of the two fractions can really be less than 1.0; believe the
-    * smaller estimate and reset the other one to exactly 1.0.  If we get
-    * exactly equal estimates (as can easily happen with self-joins), believe
-    * neither.
-    */
-   if (*leftscan > *rightscan)
-       *leftscan = 1.0;
-   else if (*leftscan < *rightscan)
-       *rightscan = 1.0;
-   else
-       *leftscan = *rightscan = 1.0;
+   /* Disbelieve start >= end, just in case that can happen */
+   if (*leftstart >= *leftend)
+   {
+       *leftstart = 0.0;
+       *leftend = 1.0;
+   }
+   if (*rightstart >= *rightend)
+   {
+       *rightstart = 0.0;
+       *rightend = 1.0;
+   }
 
 fail:
    ReleaseVariableStats(leftvar);
@@ -3778,20 +3933,21 @@ get_variable_numdistinct(VariableStatData *vardata)
 }
 
 /*
- * get_variable_maximum
- *     Estimate the maximum value of the specified variable.
- *     If successful, store value in *max and return TRUE.
+ * get_variable_range
+ *     Estimate the minimum and maximum value of the specified variable.
+ *     If successful, store values in *min and *max, and return TRUE.
  *     If no data available, return FALSE.
  *
- * sortop is the "<" comparison operator to use.  (To extract the
- * minimum instead of the maximum, just pass the ">" operator instead.)
+ * sortop is the "<" comparison operator to use.  This should generally
+ * be "<" not ">", as only the former is likely to be found in pg_statistic.
  */
 static bool
-get_variable_maximum(PlannerInfo *root, VariableStatData *vardata,
-                    Oid sortop, Datum *max)
+get_variable_range(PlannerInfo *root, VariableStatData *vardata, Oid sortop,
+                  Datum *min, Datum *max)
 {
+   Datum       tmin = 0;
    Datum       tmax = 0;
-   bool        have_max = false;
+   bool        have_data = false;
    Form_pg_statistic stats;
    int16       typLen;
    bool        typByVal;
@@ -3809,7 +3965,7 @@ get_variable_maximum(PlannerInfo *root, VariableStatData *vardata,
    get_typlenbyval(vardata->atttype, &typLen, &typByVal);
 
    /*
-    * If there is a histogram, grab the last or first value as appropriate.
+    * If there is a histogram, grab the first and last values.
     *
     * If there is a histogram that is sorted with some other operator than
     * the one we want, fail --- this suggests that there is data we can't
@@ -3823,42 +3979,24 @@ get_variable_maximum(PlannerInfo *root, VariableStatData *vardata,
    {
        if (nvalues > 0)
        {
+           tmin = datumCopy(values[0], typByVal, typLen);
            tmax = datumCopy(values[nvalues - 1], typByVal, typLen);
-           have_max = true;
+           have_data = true;
        }
        free_attstatsslot(vardata->atttype, values, nvalues, NULL, 0);
    }
-   else
+   else if (get_attstatsslot(vardata->statsTuple,
+                             vardata->atttype, vardata->atttypmod,
+                             STATISTIC_KIND_HISTOGRAM, InvalidOid,
+                             &values, &nvalues,
+                             NULL, NULL))
    {
-       Oid         rsortop = get_commutator(sortop);
-
-       if (OidIsValid(rsortop) &&
-           get_attstatsslot(vardata->statsTuple,
-                            vardata->atttype, vardata->atttypmod,
-                            STATISTIC_KIND_HISTOGRAM, rsortop,
-                            &values, &nvalues,
-                            NULL, NULL))
-       {
-           if (nvalues > 0)
-           {
-               tmax = datumCopy(values[0], typByVal, typLen);
-               have_max = true;
-           }
-           free_attstatsslot(vardata->atttype, values, nvalues, NULL, 0);
-       }
-       else if (get_attstatsslot(vardata->statsTuple,
-                                 vardata->atttype, vardata->atttypmod,
-                                 STATISTIC_KIND_HISTOGRAM, InvalidOid,
-                                 &values, &nvalues,
-                                 NULL, NULL))
-       {
-           free_attstatsslot(vardata->atttype, values, nvalues, NULL, 0);
-           return false;
-       }
+       free_attstatsslot(vardata->atttype, values, nvalues, NULL, 0);
+       return false;
    }
 
    /*
-    * If we have most-common-values info, look for a large MCV.  This is
+    * If we have most-common-values info, look for extreme MCVs.  This is
     * needed even if we also have a histogram, since the histogram excludes
     * the MCVs.  However, usually the MCVs will not be the extreme values, so
     * avoid unnecessary data copying.
@@ -3869,31 +4007,41 @@ get_variable_maximum(PlannerInfo *root, VariableStatData *vardata,
                         &values, &nvalues,
                         NULL, NULL))
    {
-       bool        large_mcv = false;
+       bool        tmin_is_mcv = false;
+       bool        tmax_is_mcv = false;
        FmgrInfo    opproc;
 
        fmgr_info(get_opcode(sortop), &opproc);
 
        for (i = 0; i < nvalues; i++)
        {
-           if (!have_max)
+           if (!have_data)
            {
-               tmax = values[i];
-               large_mcv = have_max = true;
+               tmin = tmax = values[i];
+               tmin_is_mcv = tmax_is_mcv = have_data = true;
+               continue;
+           }
+           if (DatumGetBool(FunctionCall2(&opproc, values[i], tmin)))
+           {
+               tmin = values[i];
+               tmin_is_mcv = true;
            }
-           else if (DatumGetBool(FunctionCall2(&opproc, tmax, values[i])))
+           if (DatumGetBool(FunctionCall2(&opproc, tmax, values[i])))
            {
                tmax = values[i];
-               large_mcv = true;
+               tmax_is_mcv = true;
            }
        }
-       if (large_mcv)
+       if (tmin_is_mcv)
+           tmin = datumCopy(tmin, typByVal, typLen);
+       if (tmax_is_mcv)
            tmax = datumCopy(tmax, typByVal, typLen);
        free_attstatsslot(vardata->atttype, values, nvalues, NULL, 0);
    }
 
+   *min = tmin;
    *max = tmax;
-   return have_max;
+   return have_data;
 }
 
 
index 2dce223d3a7a2b162fa64c488d6bfdd79d5bc9e4..e82b00cdd46649a7cde81663b9abdb305d00d32e 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.150 2007/11/15 22:25:17 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/relation.h,v 1.151 2007/12/08 21:05:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -993,8 +993,10 @@ typedef struct MergeScanSelCache
    int         strategy;       /* sort direction (ASC or DESC) */
    bool        nulls_first;    /* do NULLs come before normal values? */
    /* Results */
-   Selectivity leftscansel;    /* scan fraction for clause left side */
-   Selectivity rightscansel;   /* scan fraction for clause right side */
+   Selectivity leftstartsel;   /* first-join fraction for clause left side */
+   Selectivity leftendsel;     /* last-join fraction for clause left side */
+   Selectivity rightstartsel;  /* first-join fraction for clause right side */
+   Selectivity rightendsel;    /* last-join fraction for clause right side */
 } MergeScanSelCache;
 
 /*
index f92bb16d07981fab39ffd5477af6284da122657f..ededfdf3c6293455db92ad5c4d23d89b3f81393f 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/selfuncs.h,v 1.41 2007/11/07 22:37:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/selfuncs.h,v 1.42 2007/12/08 21:05:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -161,8 +161,8 @@ extern Selectivity rowcomparesel(PlannerInfo *root,
 
 extern void mergejoinscansel(PlannerInfo *root, Node *clause,
                 Oid opfamily, int strategy, bool nulls_first,
-                Selectivity *leftscan,
-                Selectivity *rightscan);
+                Selectivity *leftstart, Selectivity *leftend,
+                Selectivity *rightstart, Selectivity *rightend);
 
 extern double estimate_num_groups(PlannerInfo *root, List *groupExprs,
                    double input_rows);