Commit 034967bd authored by Tom Lane's avatar Tom Lane

Reimplement planner's handling of MIN/MAX aggregate optimization.

Per my recent proposal, get rid of all the direct inspection of indexes
and manual generation of paths in planagg.c.  Instead, set up
EquivalenceClasses for the aggregate argument expressions, and let the
regular path generation logic deal with creating paths that can satisfy
those sort orders.  This makes planagg.c a bit more visible to the rest
of the planner than it was originally, but the approach is basically a lot
cleaner than before.  A major advantage of doing it this way is that we get
MIN/MAX optimization on inheritance trees (using MergeAppend of indexscans)
practically for free, whereas in the old way we'd have had to add a whole
lot more duplicative logic.

One small disadvantage of this approach is that MIN/MAX aggregates can no
longer exploit partial indexes having an "x IS NOT NULL" predicate, unless
that restriction or something that implies it is specified in the query.
The previous implementation was able to use the added "x IS NOT NULL"
condition as an extra predicate proof condition, but in this version we
rely entirely on indexes that are considered usable by the main planning
process.  That seems a fair tradeoff for the simplicity and functionality
gained.
parent 0abc8fdd
......@@ -1837,6 +1837,22 @@ _copyPlaceHolderInfo(PlaceHolderInfo *from)
return newnode;
}
/*
* _copyMinMaxAggInfo
*/
static MinMaxAggInfo *
_copyMinMaxAggInfo(MinMaxAggInfo *from)
{
MinMaxAggInfo *newnode = makeNode(MinMaxAggInfo);
COPY_SCALAR_FIELD(aggfnoid);
COPY_SCALAR_FIELD(aggsortop);
COPY_NODE_FIELD(target);
COPY_NODE_FIELD(pathkeys);
return newnode;
}
/* ****************************************************************
* parsenodes.h copy functions
* ****************************************************************
......@@ -3921,6 +3937,9 @@ copyObject(void *from)
case T_PlaceHolderInfo:
retval = _copyPlaceHolderInfo(from);
break;
case T_MinMaxAggInfo:
retval = _copyMinMaxAggInfo(from);
break;
/*
* VALUE NODES
......
......@@ -844,6 +844,17 @@ _equalPlaceHolderInfo(PlaceHolderInfo *a, PlaceHolderInfo *b)
return true;
}
static bool
_equalMinMaxAggInfo(MinMaxAggInfo *a, MinMaxAggInfo *b)
{
COMPARE_SCALAR_FIELD(aggfnoid);
COMPARE_SCALAR_FIELD(aggsortop);
COMPARE_NODE_FIELD(target);
COMPARE_NODE_FIELD(pathkeys);
return true;
}
/*
* Stuff from parsenodes.h
......@@ -2568,6 +2579,9 @@ equal(void *a, void *b)
case T_PlaceHolderInfo:
retval = _equalPlaceHolderInfo(a, b);
break;
case T_MinMaxAggInfo:
retval = _equalMinMaxAggInfo(a, b);
break;
case T_List:
case T_IntList:
......
......@@ -1608,6 +1608,7 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
WRITE_NODE_FIELD(window_pathkeys);
WRITE_NODE_FIELD(distinct_pathkeys);
WRITE_NODE_FIELD(sort_pathkeys);
WRITE_NODE_FIELD(minmax_aggs);
WRITE_FLOAT_FIELD(total_table_pages, "%.0f");
WRITE_FLOAT_FIELD(tuple_fraction, "%.4f");
WRITE_BOOL_FIELD(hasInheritedTarget);
......@@ -1808,6 +1809,17 @@ _outPlaceHolderInfo(StringInfo str, PlaceHolderInfo *node)
WRITE_INT_FIELD(ph_width);
}
static void
_outMinMaxAggInfo(StringInfo str, MinMaxAggInfo *node)
{
WRITE_NODE_TYPE("MINMAXAGGINFO");
WRITE_OID_FIELD(aggfnoid);
WRITE_OID_FIELD(aggsortop);
WRITE_NODE_FIELD(target);
WRITE_NODE_FIELD(pathkeys);
}
static void
_outPlannerParamItem(StringInfo str, PlannerParamItem *node)
{
......@@ -2845,6 +2857,9 @@ _outNode(StringInfo str, void *obj)
case T_PlaceHolderInfo:
_outPlaceHolderInfo(str, obj);
break;
case T_MinMaxAggInfo:
_outMinMaxAggInfo(str, obj);
break;
case T_PlannerParamItem:
_outPlannerParamItem(str, obj);
break;
......
......@@ -912,6 +912,39 @@ make_pathkeys_for_sortclauses(PlannerInfo *root,
return pathkeys;
}
/****************************************************************************
* PATHKEYS AND AGGREGATES
****************************************************************************/
/*
* make_pathkeys_for_aggregate
* Generate a pathkeys list (always a 1-item list) that represents
* the sort order needed by a MIN/MAX aggregate
*
* This is only called before EquivalenceClass merging, so we can assume
* we are not supposed to canonicalize.
*/
List *
make_pathkeys_for_aggregate(PlannerInfo *root,
Expr *aggtarget,
Oid aggsortop)
{
PathKey *pathkey;
/*
* We arbitrarily set nulls_first to false. Actually, a MIN/MAX agg can
* use either nulls ordering option, but that is dealt with elsewhere.
*/
pathkey = make_pathkey_from_sortinfo(root,
aggtarget,
aggsortop,
false, /* nulls_first */
0,
true,
false);
return list_make1(pathkey);
}
/****************************************************************************
* PATHKEYS AND MERGECLAUSES
****************************************************************************/
......@@ -1379,10 +1412,11 @@ make_inner_pathkeys_for_merge(PlannerInfo *root,
* PATHKEY USEFULNESS CHECKS
*
* We only want to remember as many of the pathkeys of a path as have some
* potential use, either for subsequent mergejoins or for meeting the query's
* requested output ordering. This ensures that add_path() won't consider
* a path to have a usefully different ordering unless it really is useful.
* These routines check for usefulness of given pathkeys.
* potential use, which can include subsequent mergejoins, meeting the query's
* requested output ordering, or implementing MIN/MAX aggregates. This
* ensures that add_path() won't consider a path to have a usefully different
* ordering unless it really is useful. These routines check for usefulness
* of given pathkeys.
****************************************************************************/
/*
......@@ -1403,7 +1437,7 @@ make_inner_pathkeys_for_merge(PlannerInfo *root,
* that direction should be preferred, in hopes of avoiding a final sort step.
* right_merge_direction() implements this heuristic.
*/
int
static int
pathkeys_useful_for_merging(PlannerInfo *root, RelOptInfo *rel, List *pathkeys)
{
int useful = 0;
......@@ -1506,7 +1540,7 @@ right_merge_direction(PlannerInfo *root, PathKey *pathkey)
* no good to order by just the first key(s) of the requested ordering.
* So the result is always either 0 or list_length(root->query_pathkeys).
*/
int
static int
pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys)
{
if (root->query_pathkeys == NIL)
......@@ -1524,6 +1558,50 @@ pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys)
return 0; /* path ordering not useful */
}
/*
* pathkeys_useful_for_minmax
* Count the number of pathkeys that are useful for implementing
* some MIN/MAX aggregate.
*
* Like pathkeys_useful_for_ordering, this is a yes-or-no affair, but
* there could be several MIN/MAX aggregates and we can match to any one.
*
* We can't use pathkeys_contained_in() because we would like to match
* pathkeys regardless of the nulls_first setting. However, we know that
* MIN/MAX aggregates will have at most one item in their pathkeys, so it's
* not too complicated to match by brute force.
*/
static int
pathkeys_useful_for_minmax(PlannerInfo *root, List *pathkeys)
{
PathKey *pathkey;
ListCell *lc;
if (pathkeys == NIL)
return 0; /* unordered path */
pathkey = (PathKey *) linitial(pathkeys);
foreach(lc, root->minmax_aggs)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
PathKey *mmpathkey;
/* Ignore minmax agg if its pathkey turned out to be redundant */
if (mminfo->pathkeys == NIL)
continue;
Assert(list_length(mminfo->pathkeys) == 1);
mmpathkey = (PathKey *) linitial(mminfo->pathkeys);
if (mmpathkey->pk_eclass == pathkey->pk_eclass &&
mmpathkey->pk_opfamily == pathkey->pk_opfamily &&
mmpathkey->pk_strategy == pathkey->pk_strategy)
return 1;
}
return 0; /* path ordering not useful */
}
/*
* truncate_useless_pathkeys
* Shorten the given pathkey list to just the useful pathkeys.
......@@ -1535,11 +1613,15 @@ truncate_useless_pathkeys(PlannerInfo *root,
{
int nuseful;
int nuseful2;
int nuseful3;
nuseful = pathkeys_useful_for_merging(root, rel, pathkeys);
nuseful2 = pathkeys_useful_for_ordering(root, pathkeys);
if (nuseful2 > nuseful)
nuseful = nuseful2;
nuseful3 = pathkeys_useful_for_minmax(root, pathkeys);
if (nuseful3 > nuseful)
nuseful = nuseful3;
/*
* Note: not safe to modify input list destructively, but we can avoid
......@@ -1565,8 +1647,8 @@ truncate_useless_pathkeys(PlannerInfo *root,
*
* We could make the test more complex, for example checking to see if any of
* the joinclauses are really mergejoinable, but that likely wouldn't win
* often enough to repay the extra cycles. Queries with neither a join nor
* a sort are reasonably common, though, so this much work seems worthwhile.
* often enough to repay the extra cycles. Queries with no join, sort, or
* aggregate at all are reasonably common, so this much work seems worthwhile.
*/
bool
has_useful_pathkeys(PlannerInfo *root, RelOptInfo *rel)
......@@ -1575,5 +1657,7 @@ has_useful_pathkeys(PlannerInfo *root, RelOptInfo *rel)
return true; /* might be able to use pathkeys for merging */
if (root->query_pathkeys != NIL)
return true; /* might be able to use them for ordering */
if (root->minmax_aggs != NIL)
return true; /* might be able to use them for MIN/MAX */
return false; /* definitely useless */
}
......@@ -81,6 +81,7 @@ static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
List *indexquals);
static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index);
static List *get_switched_clauses(List *clauses, Relids outerrelids);
static List *order_qual_clauses(PlannerInfo *root, List *clauses);
static void copy_path_costsize(Plan *dest, Path *src);
......@@ -2396,10 +2397,8 @@ fix_indexqual_references(PlannerInfo *root, IndexPath *index_path,
/*
* fix_indexqual_operand
* Convert an indexqual expression to a Var referencing the index column.
*
* This is exported because planagg.c needs it.
*/
Node *
static Node *
fix_indexqual_operand(Node *node, IndexOptInfo *index)
{
/*
......
This diff is collapsed.
......@@ -30,6 +30,10 @@
#include "utils/selfuncs.h"
/* Local functions */
static void canonicalize_all_pathkeys(PlannerInfo *root);
/*
* query_planner
* Generate a path (that is, a simplified plan) for a basic query,
......@@ -68,9 +72,9 @@
* PlannerInfo field and not a passed parameter is that the low-level routines
* in indxpath.c need to see it.)
*
* Note: the PlannerInfo node also includes group_pathkeys, window_pathkeys,
* distinct_pathkeys, and sort_pathkeys, which like query_pathkeys need to be
* canonicalized once the info is available.
* Note: the PlannerInfo node includes other pathkeys fields besides
* query_pathkeys, all of which need to be canonicalized once the info is
* available. See canonicalize_all_pathkeys.
*
* tuple_fraction is interpreted as follows:
* 0: expect all tuples to be retrieved (normal case)
......@@ -118,16 +122,7 @@ query_planner(PlannerInfo *root, List *tlist,
* something like "SELECT 2+2 ORDER BY 1".
*/
root->canon_pathkeys = NIL;
root->query_pathkeys = canonicalize_pathkeys(root,
root->query_pathkeys);
root->group_pathkeys = canonicalize_pathkeys(root,
root->group_pathkeys);
root->window_pathkeys = canonicalize_pathkeys(root,
root->window_pathkeys);
root->distinct_pathkeys = canonicalize_pathkeys(root,
root->distinct_pathkeys);
root->sort_pathkeys = canonicalize_pathkeys(root,
root->sort_pathkeys);
canonicalize_all_pathkeys(root);
return;
}
......@@ -136,7 +131,7 @@ query_planner(PlannerInfo *root, List *tlist,
* for "simple" rels.
*
* NOTE: append_rel_list was set up by subquery_planner, so do not touch
* here; eq_classes may contain data already, too.
* here; eq_classes and minmax_aggs may contain data already, too.
*/
root->simple_rel_array_size = list_length(parse->rtable) + 1;
root->simple_rel_array = (RelOptInfo **)
......@@ -212,15 +207,10 @@ query_planner(PlannerInfo *root, List *tlist,
/*
* We have completed merging equivalence sets, so it's now possible to
* convert the requested query_pathkeys to canonical form. Also
* canonicalize the groupClause, windowClause, distinctClause and
* sortClause pathkeys for use later.
* convert previously generated pathkeys (in particular, the requested
* query_pathkeys) to canonical form.
*/
root->query_pathkeys = canonicalize_pathkeys(root, root->query_pathkeys);
root->group_pathkeys = canonicalize_pathkeys(root, root->group_pathkeys);
root->window_pathkeys = canonicalize_pathkeys(root, root->window_pathkeys);
root->distinct_pathkeys = canonicalize_pathkeys(root, root->distinct_pathkeys);
root->sort_pathkeys = canonicalize_pathkeys(root, root->sort_pathkeys);
canonicalize_all_pathkeys(root);
/*
* Examine any "placeholder" expressions generated during subquery pullup.
......@@ -430,3 +420,28 @@ query_planner(PlannerInfo *root, List *tlist,
*cheapest_path = cheapestpath;
*sorted_path = sortedpath;
}
/*
* canonicalize_all_pathkeys
* Canonicalize all pathkeys that were generated before entering
* query_planner and then stashed in PlannerInfo.
*/
static void
canonicalize_all_pathkeys(PlannerInfo *root)
{
ListCell *lc;
root->query_pathkeys = canonicalize_pathkeys(root, root->query_pathkeys);
root->group_pathkeys = canonicalize_pathkeys(root, root->group_pathkeys);
root->window_pathkeys = canonicalize_pathkeys(root, root->window_pathkeys);
root->distinct_pathkeys = canonicalize_pathkeys(root, root->distinct_pathkeys);
root->sort_pathkeys = canonicalize_pathkeys(root, root->sort_pathkeys);
foreach(lc, root->minmax_aggs)
{
MinMaxAggInfo *mminfo = (MinMaxAggInfo *) lfirst(lc);
mminfo->pathkeys = canonicalize_pathkeys(root, mminfo->pathkeys);
}
}
......@@ -1010,6 +1010,30 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
sub_tlist = make_subplanTargetList(root, tlist,
&groupColIdx, &need_tlist_eval);
/*
* Do aggregate preprocessing, if the query has any aggs.
*
* Note: think not that we can turn off hasAggs if we find no aggs. It
* is possible for constant-expression simplification to remove all
* explicit references to aggs, but we still have to follow the
* aggregate semantics (eg, producing only one output row).
*/
if (parse->hasAggs)
{
/*
* Will need actual number of aggregates for estimating costs.
* Note: we do not attempt to detect duplicate aggregates here; a
* somewhat-overestimated count is okay for our present purposes.
*/
count_agg_clauses((Node *) tlist, &agg_counts);
count_agg_clauses(parse->havingQual, &agg_counts);
/*
* Preprocess MIN/MAX aggregates, if any.
*/
preprocess_minmax_aggregates(root, tlist);
}
/*
* Calculate pathkeys that represent grouping/ordering requirements.
* Stash them in PlannerInfo so that query_planner can canonicalize
......@@ -1056,23 +1080,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
tlist,
false);
/*
* Will need actual number of aggregates for estimating costs.
*
* Note: we do not attempt to detect duplicate aggregates here; a
* somewhat-overestimated count is okay for our present purposes.
*
* Note: think not that we can turn off hasAggs if we find no aggs. It
* is possible for constant-expression simplification to remove all
* explicit references to aggs, but we still have to follow the
* aggregate semantics (eg, producing only one output row).
*/
if (parse->hasAggs)
{
count_agg_clauses((Node *) tlist, &agg_counts);
count_agg_clauses(parse->havingQual, &agg_counts);
}
/*
* Figure out whether we want a sorted result from query_planner.
*
......
......@@ -1870,6 +1870,7 @@ substitute_multiple_relids_walker(Node *node,
Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, AppendRelInfo));
Assert(!IsA(node, PlaceHolderInfo));
Assert(!IsA(node, MinMaxAggInfo));
return expression_tree_walker(node, substitute_multiple_relids_walker,
(void *) context);
......
......@@ -1641,6 +1641,7 @@ adjust_appendrel_attrs_mutator(Node *node, AppendRelInfo *context)
Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, AppendRelInfo));
Assert(!IsA(node, PlaceHolderInfo));
Assert(!IsA(node, MinMaxAggInfo));
/*
* We have to process RestrictInfo nodes specially. (Note: although
......
......@@ -838,6 +838,7 @@ flatten_join_alias_vars_mutator(Node *node,
/* Shouldn't need to handle these planner auxiliary nodes here */
Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, PlaceHolderInfo));
Assert(!IsA(node, MinMaxAggInfo));
return expression_tree_mutator(node, flatten_join_alias_vars_mutator,
(void *) context);
......
......@@ -377,6 +377,7 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
/* Shouldn't need to handle other planner auxiliary nodes here */
Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, PlaceHolderInfo));
Assert(!IsA(node, MinMaxAggInfo));
if (IsA(node, Query))
{
......@@ -544,6 +545,7 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
/* Shouldn't need to handle other planner auxiliary nodes here */
Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, PlaceHolderInfo));
Assert(!IsA(node, MinMaxAggInfo));
if (IsA(node, Query))
{
......@@ -811,6 +813,7 @@ rangeTableEntry_used_walker(Node *node,
Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, AppendRelInfo));
Assert(!IsA(node, PlaceHolderInfo));
Assert(!IsA(node, MinMaxAggInfo));
if (IsA(node, Query))
{
......
......@@ -230,6 +230,7 @@ typedef enum NodeTag
T_SpecialJoinInfo,
T_AppendRelInfo,
T_PlaceHolderInfo,
T_MinMaxAggInfo,
T_PlannerParamItem,
/*
......
......@@ -189,6 +189,8 @@ typedef struct PlannerInfo
List *distinct_pathkeys; /* distinctClause pathkeys, if any */
List *sort_pathkeys; /* sortClause pathkeys, if any */
List *minmax_aggs; /* List of MinMaxAggInfos */
List *initial_rels; /* RelOptInfos we are now trying to join */
MemoryContext planner_cxt; /* context holding PlannerInfo */
......@@ -1357,6 +1359,23 @@ typedef struct PlaceHolderInfo
int32 ph_width; /* estimated attribute width */
} PlaceHolderInfo;
/*
* For each potentially index-optimizable MIN/MAX aggregate function,
* root->minmax_aggs stores a MinMaxAggInfo describing it.
*
* Note: a MIN/MAX agg doesn't really care about the nulls_first property,
* so the pathkey's nulls_first flag should be ignored.
*/
typedef struct MinMaxAggInfo
{
NodeTag type;
Oid aggfnoid; /* pg_proc Oid of the aggregate */
Oid aggsortop; /* Oid of its sort operator */
Expr *target; /* expression we are aggregating on */
List *pathkeys; /* pathkeys representing needed sort order */
} MinMaxAggInfo;
/*
* glob->paramlist keeps track of the PARAM_EXEC slots that we have decided
* we need for the query. At runtime these slots are used to pass values
......
......@@ -173,6 +173,9 @@ extern List *make_pathkeys_for_sortclauses(PlannerInfo *root,
List *sortclauses,
List *tlist,
bool canonicalize);
extern List *make_pathkeys_for_aggregate(PlannerInfo *root,
Expr *aggtarget,
Oid aggsortop);
extern void initialize_mergeclause_eclasses(PlannerInfo *root,
RestrictInfo *restrictinfo);
extern void update_mergeclause_eclasses(PlannerInfo *root,
......@@ -187,10 +190,6 @@ extern List *select_outer_pathkeys_for_merge(PlannerInfo *root,
extern List *make_inner_pathkeys_for_merge(PlannerInfo *root,
List *mergeclauses,
List *outer_pathkeys);
extern int pathkeys_useful_for_merging(PlannerInfo *root,
RelOptInfo *rel,
List *pathkeys);
extern int pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys);
extern List *truncate_useless_pathkeys(PlannerInfo *root,
RelOptInfo *rel,
List *pathkeys);
......
......@@ -32,6 +32,7 @@ extern void query_planner(PlannerInfo *root, List *tlist,
/*
* prototypes for plan/planagg.c
*/
extern void preprocess_minmax_aggregates(PlannerInfo *root, List *tlist);
extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
Path *best_path);
......@@ -39,7 +40,6 @@ extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist,
* prototypes for plan/createplan.c
*/
extern Plan *create_plan(PlannerInfo *root, Path *best_path);
extern Node *fix_indexqual_operand(Node *node, IndexOptInfo *index);
extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
Index scanrelid, Plan *subplan,
List *subrtable, List *subrowmark);
......
......@@ -442,29 +442,90 @@ FROM bool_test;
(1 row)
--
-- Test several cases that should be optimized into indexscans instead of
-- the generic aggregate implementation. We can't actually verify that they
-- are done as indexscans, but we can check that the results are correct.
-- Test cases that should be optimized into indexscans instead of
-- the generic aggregate implementation.
--
analyze tenk1; -- ensure we get consistent plans here
-- Basic cases
explain (costs off)
select min(unique1) from tenk1;
QUERY PLAN
-------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Index Scan using tenk1_unique1 on tenk1
Index Cond: (unique1 IS NOT NULL)
(5 rows)
select min(unique1) from tenk1;
min
-----
0
(1 row)
explain (costs off)
select max(unique1) from tenk1;
QUERY PLAN
----------------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Index Scan Backward using tenk1_unique1 on tenk1
Index Cond: (unique1 IS NOT NULL)
(5 rows)
select max(unique1) from tenk1;
max
------
9999
(1 row)
explain (costs off)
select max(unique1) from tenk1 where unique1 < 42;
QUERY PLAN
------------------------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Index Scan Backward using tenk1_unique1 on tenk1
Index Cond: ((unique1 IS NOT NULL) AND (unique1 < 42))
(5 rows)
select max(unique1) from tenk1 where unique1 < 42;
max
-----
41
(1 row)
explain (costs off)
select max(unique1) from tenk1 where unique1 > 42;
QUERY PLAN
------------------------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Index Scan Backward using tenk1_unique1 on tenk1
Index Cond: ((unique1 IS NOT NULL) AND (unique1 > 42))
(5 rows)
select max(unique1) from tenk1 where unique1 > 42;
max
------
9999
(1 row)
explain (costs off)
select max(unique1) from tenk1 where unique1 > 42000;
QUERY PLAN
---------------------------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Index Scan Backward using tenk1_unique1 on tenk1
Index Cond: ((unique1 IS NOT NULL) AND (unique1 > 42000))
(5 rows)
select max(unique1) from tenk1 where unique1 > 42000;
max
-----
......@@ -472,12 +533,34 @@ select max(unique1) from tenk1 where unique1 > 42000;
(1 row)
-- multi-column index (uses tenk1_thous_tenthous)
explain (costs off)
select max(tenthous) from tenk1 where thousand = 33;
QUERY PLAN
--------------------------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Index Scan Backward using tenk1_thous_tenthous on tenk1
Index Cond: ((thousand = 33) AND (tenthous IS NOT NULL))
(5 rows)
select max(tenthous) from tenk1 where thousand = 33;
max
------
9033
(1 row)
explain (costs off)
select min(tenthous) from tenk1 where thousand = 33;
QUERY PLAN
--------------------------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Index Scan using tenk1_thous_tenthous on tenk1
Index Cond: ((thousand = 33) AND (tenthous IS NOT NULL))
(5 rows)
select min(tenthous) from tenk1 where thousand = 33;
min
-----
......@@ -485,8 +568,22 @@ select min(tenthous) from tenk1 where thousand = 33;
(1 row)
-- check parameter propagation into an indexscan subquery
explain (costs off)
select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt
from int4_tbl;
QUERY PLAN
-----------------------------------------------------------------------------------------
Seq Scan on int4_tbl
SubPlan 2
-> Result
InitPlan 1 (returns $1)
-> Limit
-> Index Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 IS NOT NULL) AND (unique1 > int4_tbl.f1))
(7 rows)
select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt
from int4_tbl;
from int4_tbl;
f1 | gt
-------------+----
0 | 1
......@@ -497,30 +594,94 @@ from int4_tbl;
(5 rows)
-- check some cases that were handled incorrectly in 8.3.0
explain (costs off)
select distinct max(unique2) from tenk1;
QUERY PLAN
----------------------------------------------------------------
HashAggregate
InitPlan 1 (returns $0)
-> Limit
-> Index Scan Backward using tenk1_unique2 on tenk1
Index Cond: (unique2 IS NOT NULL)
-> Result
(6 rows)
select distinct max(unique2) from tenk1;
max
------
9999
(1 row)
explain (costs off)
select max(unique2) from tenk1 order by 1;
QUERY PLAN
----------------------------------------------------------------
Sort
Sort Key: ($0)
InitPlan 1 (returns $0)
-> Limit
-> Index Scan Backward using tenk1_unique2 on tenk1
Index Cond: (unique2 IS NOT NULL)
-> Result
(7 rows)
select max(unique2) from tenk1 order by 1;
max
------
9999
(1 row)
explain (costs off)
select max(unique2) from tenk1 order by max(unique2);
QUERY PLAN
----------------------------------------------------------------
Sort
Sort Key: ($0)
InitPlan 1 (returns $0)
-> Limit
-> Index Scan Backward using tenk1_unique2 on tenk1
Index Cond: (unique2 IS NOT NULL)
-> Result
(7 rows)
select max(unique2) from tenk1 order by max(unique2);
max
------
9999
(1 row)
explain (costs off)
select max(unique2) from tenk1 order by max(unique2)+1;
QUERY PLAN
----------------------------------------------------------------
Sort
Sort Key: (($0 + 1))
InitPlan 1 (returns $0)
-> Limit
-> Index Scan Backward using tenk1_unique2 on tenk1
Index Cond: (unique2 IS NOT NULL)
-> Result
(7 rows)
select max(unique2) from tenk1 order by max(unique2)+1;
max
------
9999
(1 row)
explain (costs off)
select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
QUERY PLAN
----------------------------------------------------------------
Sort
Sort Key: (generate_series(1, 3))
InitPlan 1 (returns $0)
-> Limit
-> Index Scan Backward using tenk1_unique2 on tenk1
Index Cond: (unique2 IS NOT NULL)
-> Result
(7 rows)
select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
max | g
------+---
......@@ -529,6 +690,69 @@ select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
9999 | 1
(3 rows)
-- this is an interesting special case as of 9.1
explain (costs off)
select min(unique2) from tenk1 where unique2 = 42;
QUERY PLAN
-----------------------------------------------
Aggregate
-> Index Scan using tenk1_unique2 on tenk1
Index Cond: (unique2 = 42)
(3 rows)
select min(unique2) from tenk1 where unique2 = 42;
min
-----
42
(1 row)
-- try it on an inheritance tree
create table minmaxtest(f1 int);
create table minmaxtest1() inherits (minmaxtest);
create table minmaxtest2() inherits (minmaxtest);
create index minmaxtesti on minmaxtest(f1);
create index minmaxtest1i on minmaxtest1(f1);
create index minmaxtest2i on minmaxtest2(f1 desc);
insert into minmaxtest values(11), (12);
insert into minmaxtest1 values(13), (14);
insert into minmaxtest2 values(15), (16);
explain (costs off)
select min(f1), max(f1) from minmaxtest;
QUERY PLAN
--------------------------------------------------------------------------------------
Result
InitPlan 1 (returns $0)
-> Limit
-> Merge Append
Sort Key: public.minmaxtest.f1
-> Index Scan using minmaxtesti on minmaxtest
Index Cond: (f1 IS NOT NULL)
-> Index Scan using minmaxtest1i on minmaxtest1 minmaxtest
Index Cond: (f1 IS NOT NULL)
-> Index Scan Backward using minmaxtest2i on minmaxtest2 minmaxtest
Index Cond: (f1 IS NOT NULL)
InitPlan 2 (returns $1)
-> Limit
-> Merge Append
Sort Key: public.minmaxtest.f1
-> Index Scan Backward using minmaxtesti on minmaxtest
Index Cond: (f1 IS NOT NULL)
-> Index Scan Backward using minmaxtest1i on minmaxtest1 minmaxtest
Index Cond: (f1 IS NOT NULL)
-> Index Scan using minmaxtest2i on minmaxtest2 minmaxtest
Index Cond: (f1 IS NOT NULL)
(21 rows)
select min(f1), max(f1) from minmaxtest;
min | max
-----+-----
11 | 16
(1 row)
drop table minmaxtest cascade;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table minmaxtest1
drop cascades to table minmaxtest2
--
-- Test combinations of DISTINCT and/or ORDER BY
--
......
......@@ -205,31 +205,81 @@ SELECT
FROM bool_test;
--
-- Test several cases that should be optimized into indexscans instead of
-- the generic aggregate implementation. We can't actually verify that they
-- are done as indexscans, but we can check that the results are correct.
-- Test cases that should be optimized into indexscans instead of
-- the generic aggregate implementation.
--
analyze tenk1; -- ensure we get consistent plans here
-- Basic cases
explain (costs off)
select min(unique1) from tenk1;
select min(unique1) from tenk1;
explain (costs off)
select max(unique1) from tenk1;
select max(unique1) from tenk1;
explain (costs off)
select max(unique1) from tenk1 where unique1 < 42;
select max(unique1) from tenk1 where unique1 < 42;
explain (costs off)
select max(unique1) from tenk1 where unique1 > 42;
select max(unique1) from tenk1 where unique1 > 42;
explain (costs off)
select max(unique1) from tenk1 where unique1 > 42000;
select max(unique1) from tenk1 where unique1 > 42000;
-- multi-column index (uses tenk1_thous_tenthous)
explain (costs off)
select max(tenthous) from tenk1 where thousand = 33;
select max(tenthous) from tenk1 where thousand = 33;
explain (costs off)
select min(tenthous) from tenk1 where thousand = 33;
select min(tenthous) from tenk1 where thousand = 33;
-- check parameter propagation into an indexscan subquery
explain (costs off)
select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt
from int4_tbl;
select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt
from int4_tbl;
from int4_tbl;
-- check some cases that were handled incorrectly in 8.3.0
explain (costs off)
select distinct max(unique2) from tenk1;
select distinct max(unique2) from tenk1;
explain (costs off)
select max(unique2) from tenk1 order by 1;
select max(unique2) from tenk1 order by 1;
explain (costs off)
select max(unique2) from tenk1 order by max(unique2);
select max(unique2) from tenk1 order by max(unique2);
explain (costs off)
select max(unique2) from tenk1 order by max(unique2)+1;
select max(unique2) from tenk1 order by max(unique2)+1;
explain (costs off)
select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
select max(unique2), generate_series(1,3) as g from tenk1 order by g desc;
-- this is an interesting special case as of 9.1
explain (costs off)
select min(unique2) from tenk1 where unique2 = 42;
select min(unique2) from tenk1 where unique2 = 42;
-- try it on an inheritance tree
create table minmaxtest(f1 int);
create table minmaxtest1() inherits (minmaxtest);
create table minmaxtest2() inherits (minmaxtest);
create index minmaxtesti on minmaxtest(f1);
create index minmaxtest1i on minmaxtest1(f1);
create index minmaxtest2i on minmaxtest2(f1 desc);
insert into minmaxtest values(11), (12);
insert into minmaxtest1 values(13), (14);
insert into minmaxtest2 values(15), (16);
explain (costs off)
select min(f1), max(f1) from minmaxtest;
select min(f1), max(f1) from minmaxtest;
drop table minmaxtest cascade;
--
-- Test combinations of DISTINCT and/or ORDER BY
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment