Push limit through subqueries to underlying sort, where possible.
authorRobert Haas <rhaas@postgresql.org>
Mon, 21 Aug 2017 18:43:01 +0000 (14:43 -0400)
committerRobert Haas <rhaas@postgresql.org>
Mon, 21 Aug 2017 18:19:44 +0000 (14:19 -0400)
Douglas Doole, reviewed by Ashutosh Bapat and by me.  Minor formatting
change by me.

Discussion: http://postgr.es/m/CADE5jYLuugnEEUsyW6Q_4mZFYTxHxaVCQmGAsF0yiY8ZDggi-w@mail.gmail.com

src/backend/executor/nodeLimit.c
src/test/regress/expected/subselect.out
src/test/regress/sql/subselect.sql

index ac5a2ff0e601274a1f2868b93527660caef79321..09af1a5d8b32619bf1de07e09965fa10c7c15263 100644 (file)
@@ -308,6 +308,9 @@ recompute_limits(LimitState *node)
  * since the MergeAppend surely need read no more than that many tuples from
  * any one input.  We also have to be prepared to look through a Result,
  * since the planner might stick one atop MergeAppend for projection purposes.
+ * We can also accept one or more levels of subqueries that have no quals or
+ * SRFs (that is, each subquery is just projecting columns) between the LIMIT
+ * and any of the above.
  *
  * This is a bit of a kluge, but we don't have any more-abstract way of
  * communicating between the two nodes; and it doesn't seem worth trying
@@ -320,6 +323,29 @@ recompute_limits(LimitState *node)
 static void
 pass_down_bound(LimitState *node, PlanState *child_node)
 {
+   /*
+    * If the child is a subquery that does no filtering (no predicates)
+    * and does not have any SRFs in the target list then we can potentially
+    * push the limit through the subquery. It is possible that we could have
+    * multiple subqueries, so tunnel through them all.
+    */
+   while (IsA(child_node, SubqueryScanState))
+   {
+       SubqueryScanState *subqueryScanState;
+
+       subqueryScanState = (SubqueryScanState *) child_node;
+
+       /*
+        * Non-empty predicates or an SRF means we cannot push down the limit.
+        */
+       if (subqueryScanState->ss.ps.qual != NULL ||
+           expression_returns_set((Node *) child_node->plan->targetlist))
+           return;
+
+       /* Use the child in the following checks */
+       child_node = subqueryScanState->subplan;
+   }
+
    if (IsA(child_node, SortState))
    {
        SortState  *sortState = (SortState *) child_node;
index ed7d6d8034e302362d12c0d9d00926ba0d3ba94f..8419dea08e30d092501c3389b5ce0a9f30526c02 100644 (file)
@@ -1041,3 +1041,55 @@ NOTICE:  x = 9, y = 13
 (3 rows)
 
 drop function tattle(x int, y int);
+--
+-- Test that LIMIT can be pushed to SORT through a subquery that just
+-- projects columns
+--
+create table sq_limit (pk int primary key, c1 int, c2 int);
+insert into sq_limit values
+   (1, 1, 1),
+   (2, 2, 2),
+   (3, 3, 3),
+   (4, 4, 4),
+   (5, 1, 1),
+   (6, 2, 2),
+   (7, 3, 3),
+   (8, 4, 4);
+-- The explain contains data that may not be invariant, so
+-- filter for just the interesting bits.  The goal here is that
+-- we should see three notices, in order:
+--   NOTICE: Limit
+--   NOTICE: Subquery
+--   NOTICE: Top-N Sort
+-- A missing step, or steps out of order means we have a problem.
+do $$
+   declare x text;
+   begin
+       for x in
+           explain (analyze, summary off, timing off, costs off)
+           select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3
+       loop
+           if (left(ltrim(x), 5) = 'Limit') then
+               raise notice 'Limit';
+           end if;
+           if (left(ltrim(x), 12) = '->  Subquery') then
+               raise notice 'Subquery';
+           end if;
+           if (left(ltrim(x), 18) = 'Sort Method: top-N') then
+               raise notice 'Top-N Sort';
+           end if;
+       end loop;
+   end;
+$$;
+NOTICE:  Limit
+NOTICE:  Subquery
+NOTICE:  Top-N Sort
+select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3;
+ pk | c2 
+----+----
+  1 |  1
+  5 |  1
+  2 |  2
+(3 rows)
+
+drop table sq_limit;
index 2fc0e26ca066a97c67aea259231b8f5bf7d7cb19..7087ee27cd405d51debc870bc94a865a961ac8cb 100644 (file)
@@ -540,3 +540,49 @@ select * from
   where tattle(x, u);
 
 drop function tattle(x int, y int);
+
+--
+-- Test that LIMIT can be pushed to SORT through a subquery that just
+-- projects columns
+--
+create table sq_limit (pk int primary key, c1 int, c2 int);
+insert into sq_limit values
+   (1, 1, 1),
+   (2, 2, 2),
+   (3, 3, 3),
+   (4, 4, 4),
+   (5, 1, 1),
+   (6, 2, 2),
+   (7, 3, 3),
+   (8, 4, 4);
+
+-- The explain contains data that may not be invariant, so
+-- filter for just the interesting bits.  The goal here is that
+-- we should see three notices, in order:
+--   NOTICE: Limit
+--   NOTICE: Subquery
+--   NOTICE: Top-N Sort
+-- A missing step, or steps out of order means we have a problem.
+do $$
+   declare x text;
+   begin
+       for x in
+           explain (analyze, summary off, timing off, costs off)
+           select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3
+       loop
+           if (left(ltrim(x), 5) = 'Limit') then
+               raise notice 'Limit';
+           end if;
+           if (left(ltrim(x), 12) = '->  Subquery') then
+               raise notice 'Subquery';
+           end if;
+           if (left(ltrim(x), 18) = 'Sort Method: top-N') then
+               raise notice 'Top-N Sort';
+           end if;
+       end loop;
+   end;
+$$;
+
+select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3;
+
+drop table sq_limit;