From b06fbc7ad2cb2467a51432c01dd0d0b881667cb9 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Thu, 18 Jan 2001 07:12:37 +0000 Subject: [PATCH] Fix performance issue with qualifications on VIEWs: outer query should try to push restrictions on the view down into the view subquery, so that they can become indexscan quals or what-have-you rather than being applied at the top level of the subquery. 7.0 and before were able to do this, though in a much klugier way, and I'd hate to have anyone complaining that 7.1 is stupider than 7.0 ... --- src/backend/optimizer/path/allpaths.c | 63 +++++++++++++++++++++++++-- src/backend/optimizer/plan/planner.c | 59 ++++++++++++++++--------- 2 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 4f7a0e570f..5e461cb3c1 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -8,13 +8,14 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.68 2000/12/14 22:30:43 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.69 2001/01/18 07:12:37 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/geqo.h" #include "optimizer/pathnode.h" @@ -23,6 +24,7 @@ #include "optimizer/planner.h" #include "optimizer/prep.h" #include "parser/parsetree.h" +#include "rewrite/rewriteManip.h" bool enable_geqo = true; @@ -99,12 +101,65 @@ set_base_rel_pathlists(Query *root) if (rel->issubquery) { /* Subquery --- generate a separate plan for it */ + List *upperrestrictlist; + List *lst; /* - * XXX for now, we just apply any restrict clauses that came - * from the outer query as qpquals of the SubqueryScan node. - * Later, think about pushing them down into the subquery itself. + * If there are any restriction clauses that have been attached + * to the subquery relation, consider pushing them down to become + * HAVING quals of the subquery itself. (Not WHERE clauses, since + * they may refer to subquery outputs that are aggregate results. + * But planner.c will transfer them into the subquery's WHERE if + * they do not.) This transformation is useful because it may + * allow us to generate a better plan for the subquery than + * evaluating all the subquery output rows and then filtering + * them. + * + * Currently, we do not push down clauses that contain subselects, + * mainly because I'm not sure it will work correctly (the + * subplan hasn't yet transformed sublinks to subselects). + * Non-pushed-down clauses will get evaluated as qpquals of + * the SubqueryScan node. + * + * XXX Are there any cases where we want to make a policy + * decision not to push down, because it'd result in a worse + * plan? */ + upperrestrictlist = NIL; + foreach(lst, rel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lst); + Node *clause = (Node *) rinfo->clause; + + if (contain_subplans(clause)) + { + /* Keep it in the upper query */ + upperrestrictlist = lappend(upperrestrictlist, rinfo); + } + else + { + /* + * We need to replace Vars in the clause (which must + * refer to outputs of the subquery) with copies of the + * subquery's targetlist expressions. Note that at this + * point, any uplevel Vars in the clause should have been + * replaced with Params, so they need no work. + */ + clause = ResolveNew(clause, rti, 0, + rte->subquery->targetList, + CMD_SELECT, 0); + rte->subquery->havingQual = + make_and_qual(rte->subquery->havingQual, + clause); + /* + * We need not change the subquery's hasAggs or + * hasSublinks flags, since we can't be pushing down + * any aggregates that weren't there before, and we + * don't push down subselects at all. + */ + } + } + rel->baserestrictinfo = upperrestrictlist; /* Generate the plan for the subquery */ rel->subplan = subquery_planner(rte->subquery, diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 5d845e9842..d2ec1b8a75 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.98 2000/12/14 22:30:43 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.99 2001/01/18 07:12:37 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -132,6 +132,7 @@ subquery_planner(Query *parse, double tuple_fraction) List *saved_initplan = PlannerInitPlan; int saved_planid = PlannerPlanId; Plan *plan; + List *newHaving; List *lst; /* Set up for a new level of subquery */ @@ -155,20 +156,6 @@ subquery_planner(Query *parse, double tuple_fraction) parse->jointree = (FromExpr *) preprocess_jointree(parse, (Node *) parse->jointree); - /* - * A HAVING clause without aggregates is equivalent to a WHERE clause - * (except it can only refer to grouped fields). If there are no aggs - * anywhere in the query, then we don't want to create an Agg plan - * node, so merge the HAVING condition into WHERE. (We used to - * consider this an error condition, but it seems to be legal SQL.) - */ - if (parse->havingQual != NULL && !parse->hasAggs) - { - parse->jointree->quals = make_and_qual(parse->jointree->quals, - parse->havingQual); - parse->havingQual = NULL; - } - /* * Do expression preprocessing on targetlist and quals. */ @@ -181,6 +168,37 @@ subquery_planner(Query *parse, double tuple_fraction) parse->havingQual = preprocess_expression(parse, parse->havingQual, EXPRKIND_HAVING); + /* + * A HAVING clause without aggregates is equivalent to a WHERE clause + * (except it can only refer to grouped fields). Transfer any agg-free + * clauses of the HAVING qual into WHERE. This may seem like wasting + * cycles to cater to stupidly-written queries, but there are other + * reasons for doing it. Firstly, if the query contains no aggs at all, + * then we aren't going to generate an Agg plan node, and so there'll be + * no place to execute HAVING conditions; without this transfer, we'd + * lose the HAVING condition entirely, which is wrong. Secondly, when + * we push down a qual condition into a sub-query, it's easiest to push + * the qual into HAVING always, in case it contains aggs, and then let + * this code sort it out. + * + * Note that both havingQual and parse->jointree->quals are in + * implicitly-ANDed-list form at this point, even though they are + * declared as Node *. Also note that contain_agg_clause does not + * recurse into sub-selects, which is exactly what we need here. + */ + newHaving = NIL; + foreach(lst, (List *) parse->havingQual) + { + Node *havingclause = (Node *) lfirst(lst); + + if (contain_agg_clause(havingclause)) + newHaving = lappend(newHaving, havingclause); + else + parse->jointree->quals = (Node *) + lappend((List *) parse->jointree->quals, havingclause); + } + parse->havingQual = (Node *) newHaving; + /* * Do the main planning. If we have an inherited target relation, * that needs special processing, else go straight to grouping_planner. @@ -554,12 +572,6 @@ preprocess_expression(Query *parse, Node *expr, int kind) * Check for ungrouped variables passed to subplans. Note we * do NOT do this for subplans in WHERE (or JOIN/ON); it's legal * there because WHERE is evaluated pre-GROUP. - * - * An interesting fine point: if subquery_planner reassigned a - * HAVING qual into WHERE, then we will accept references to - * ungrouped vars from subplans in the HAVING qual. This is not - * entirely consistent, but it doesn't seem particularly - * harmful... */ check_subplans_for_ungrouped_vars(expr, parse); } @@ -1049,6 +1061,11 @@ grouping_planner(Query *parse, double tuple_fraction) result_plan); /* Note: Agg does not affect any existing sort order of the tuples */ } + else + { + /* If there are no Aggs, we shouldn't have any HAVING qual anymore */ + Assert(parse->havingQual == NULL); + } /* * If we were not able to make the plan come out in the right order, -- 2.24.1