Hackers,

Currently, if we have a query such as:

SELECT a,b, COUNT(*)
FROM a
INNER JOIN b on a.a = b.x
GROUP BY a,b
ORDER BY a DESC, b;

With enable_hashagg = off, we get the following plan:

              QUERY PLAN
---------------------------------------
 GroupAggregate
   Group Key: a.a, a.b
   ->  Sort
         Sort Key: a.a DESC, a.b
         ->  Merge Join
               Merge Cond: (a.a = b.x)
               ->  Sort
                     Sort Key: a.a
                     ->  Seq Scan on a
               ->  Sort
                     Sort Key: b.x
                     ->  Seq Scan on b

We can see that the merge join picked to sort the input on a.a rather
than a.a DESC.  This is due to the way
select_outer_pathkeys_for_merge() only picks the query_pathkeys as a
prefix of the join pathkeys if we can find all of the join pathkeys in
the query_pathkeys.

I think we can relax this now that we have incremental sort.  I think
a better way to limit this is to allow a prefix of the query_pathkeys
providing that covers *all* of the join pathkeys.  That way, for the
above query, it leaves it open for the planner to do the Merge Join by
sorting by a.a DESC then just do an Incremental Sort to get the
GroupAggregate input sorted by a.b.

I've attached a patch for this and it changes the plan for the above query to:

               QUERY PLAN
----------------------------------------
 GroupAggregate
   Group Key: a.a, a.b
   ->  Incremental Sort
         Sort Key: a.a DESC, a.b
         Presorted Key: a.a
         ->  Merge Join
               Merge Cond: (a.a = b.x)
               ->  Sort
                     Sort Key: a.a DESC
                     ->  Seq Scan on a
               ->  Sort
                     Sort Key: b.x DESC
                     ->  Seq Scan on b

The current behaviour is causing me a bit of trouble in plan
regression for the ORDER BY / DISTINCT aggregate improvement patch
that I have pending.

Is there any reason that we shouldn't do this?

David
diff --git a/src/backend/optimizer/path/pathkeys.c 
b/src/backend/optimizer/path/pathkeys.c
index 11de286e60..cf08fcac51 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -1924,11 +1924,13 @@ find_mergeclauses_for_outer_pathkeys(PlannerInfo *root,
  * Since we assume here that a sort is required, there is no particular use
  * in matching any available ordering of the outerrel.  (joinpath.c has an
  * entirely separate code path for considering sort-free mergejoins.)  Rather,
- * it's interesting to try to match the requested query_pathkeys so that a
- * second output sort may be avoided; and failing that, we try to list "more
- * popular" keys (those with the most unmatched EquivalenceClass peers)
- * earlier, in hopes of making the resulting ordering useful for as many
- * higher-level mergejoins as possible.
+ * it's interesting to try to match, or match a prefix of the requested
+ * query_pathkeys so that a second output sort may be avoided.  We can get
+ * away with just a prefix of the query_pathkeys when that prefix covers the
+ * entire join condition.  Failing that, we try to list "more popular" keys
+ *  (those with the most unmatched EquivalenceClass peers) earlier, in hopes
+ * of making the resulting ordering useful for as many higher-level mergejoins
+ * as possible.
  */
 List *
 select_outer_pathkeys_for_merge(PlannerInfo *root,
@@ -1998,11 +2000,16 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
 
        /*
         * Find out if we have all the ECs mentioned in query_pathkeys; if so we
-        * can generate a sort order that's also useful for final output. There 
is
-        * no percentage in a partial match, though, so we have to have 'em all.
+        * can generate a sort order that's also useful for final output. If we
+        * only have a prefix of the query_pathkeys, and that prefix is the 
entire
+        * join condition, then it's useful to use the prefix as the pathkeys as
+        * this increases the chances that an incremental sort will be able to 
be
+        * used by the upper planner.
         */
        if (root->query_pathkeys)
        {
+               int             matches = 0;
+
                foreach(lc, root->query_pathkeys)
                {
                        PathKey    *query_pathkey = (PathKey *) lfirst(lc);
@@ -2015,6 +2022,8 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
                        }
                        if (j >= necs)
                                break;                  /* didn't find match */
+
+                       matches++;
                }
                /* if we got to the end of the list, we have them all */
                if (lc == NULL)
@@ -2037,6 +2046,22 @@ select_outer_pathkeys_for_merge(PlannerInfo *root,
                                }
                        }
                }
+
+               /*
+                * If we didn't find a full match, but matched all of the join 
clauses
+                * then we'll make use of these as presorted input is better 
than
+                * nothing for the upper planner.
+                */
+               else if (matches == nClauses)
+               {
+                       pathkeys = list_copy_head(root->query_pathkeys, 
matches);
+
+                       /* we have all of the join pathkeys, so nothing more to 
do */
+                       pfree(ecs);
+                       pfree(scores);
+
+                       return pathkeys;
+               }
        }
 
        /*
diff --git a/src/test/regress/expected/tuplesort.out 
b/src/test/regress/expected/tuplesort.out
index ef79574ecf..a2efa179fc 100644
--- a/src/test/regress/expected/tuplesort.out
+++ b/src/test/regress/expected/tuplesort.out
@@ -622,17 +622,18 @@ EXPLAIN (COSTS OFF) :qry;
          ->  GroupAggregate
                Group Key: a.col12
                Filter: (count(*) > 1)
-               ->  Sort
+               ->  Incremental Sort
                      Sort Key: a.col12 DESC, a.col1
+                     Presorted Key: a.col12
                      ->  Merge Join
                            Merge Cond: (a.col12 = b.col12)
                            ->  Sort
-                                 Sort Key: a.col12
+                                 Sort Key: a.col12 DESC
                                  ->  Seq Scan on test_mark_restore a
                            ->  Sort
-                                 Sort Key: b.col12
+                                 Sort Key: b.col12 DESC
                                  ->  Seq Scan on test_mark_restore b
-(16 rows)
+(17 rows)
 
 :qry;
  col12 | count | count | count | count | count 
@@ -660,17 +661,18 @@ EXPLAIN (COSTS OFF) :qry;
          ->  GroupAggregate
                Group Key: a.col12
                Filter: (count(*) > 1)
-               ->  Sort
+               ->  Incremental Sort
                      Sort Key: a.col12 DESC, a.col1
+                     Presorted Key: a.col12
                      ->  Merge Join
                            Merge Cond: (a.col12 = b.col12)
                            ->  Sort
-                                 Sort Key: a.col12
+                                 Sort Key: a.col12 DESC
                                  ->  Seq Scan on test_mark_restore a
                            ->  Sort
-                                 Sort Key: b.col12
+                                 Sort Key: b.col12 DESC
                                  ->  Seq Scan on test_mark_restore b
-(16 rows)
+(17 rows)
 
 :qry;
  col12 | count | count | count | count | count 

Reply via email to