]> granicus.if.org Git - postgresql/commitdiff
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;