diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 8c9f8a6aeb..99b7b2e6b0 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -3401,13 +3401,17 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, scratch.resnull = &state->resnull; } argno++; + + Assert(pertrans->numInputs == argno); } - else if (pertrans->numSortCols == 0) + else if (!pertrans->aggsortrequired) { ListCell *arg; /* - * Normal transition function without ORDER BY / DISTINCT. + * Normal transition function without ORDER BY / DISTINCT or with + * ORDER BY / DISTINCT but the planner has given us pre-sorted + * input. */ strictargs = trans_fcinfo->args + 1; @@ -3415,6 +3419,13 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, { TargetEntry *source_tle = (TargetEntry *) lfirst(arg); + /* + * Don't initialize args for any ORDER BY clause that might + * exist in a presorted aggregate. + */ + if (argno == pertrans->numTransInputs) + break; + /* * Start from 1, since the 0th arg will be the transition * value @@ -3424,11 +3435,13 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, &trans_fcinfo->args[argno + 1].isnull); argno++; } + Assert(pertrans->numTransInputs == argno); } else if (pertrans->numInputs == 1) { /* - * DISTINCT and/or ORDER BY case, with a single column sorted on. + * Non-presorted DISTINCT and/or ORDER BY case, with a single + * column sorted on. */ TargetEntry *source_tle = (TargetEntry *) linitial(pertrans->aggref->args); @@ -3440,11 +3453,14 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, &state->resnull); strictnulls = &state->resnull; argno++; + + Assert(pertrans->numInputs == argno); } else { /* - * DISTINCT and/or ORDER BY case, with multiple columns sorted on. + * Non-presorted DISTINCT and/or ORDER BY case, with multiple + * columns sorted on. */ Datum *values = pertrans->sortslot->tts_values; bool *nulls = pertrans->sortslot->tts_isnull; @@ -3460,8 +3476,8 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, &values[argno], &nulls[argno]); argno++; } + Assert(pertrans->numInputs == argno); } - Assert(pertrans->numInputs == argno); /* * For a strict transfn, nothing happens when there's a NULL input; we @@ -3483,6 +3499,21 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, state->steps_len - 1); } + /* Handle DISTINCT aggregates which have pre-sorted input */ + if (pertrans->numDistinctCols > 0 && !pertrans->aggsortrequired) + { + if (pertrans->numDistinctCols > 1) + scratch.opcode = EEOP_AGG_PRESORTED_DISTINCT_MULTI; + else + scratch.opcode = EEOP_AGG_PRESORTED_DISTINCT_SINGLE; + + scratch.d.agg_presorted_distinctcheck.pertrans = pertrans; + scratch.d.agg_presorted_distinctcheck.jumpdistinct = -1; /* adjust later */ + ExprEvalPushStep(state, &scratch); + adjust_bailout = lappend_int(adjust_bailout, + state->steps_len - 1); + } + /* * Call transition function (once for each concurrently evaluated * grouping set). Do so for both sort and hash based computations, as @@ -3543,6 +3574,12 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase, Assert(as->d.agg_deserialize.jumpnull == -1); as->d.agg_deserialize.jumpnull = state->steps_len; } + else if (as->opcode == EEOP_AGG_PRESORTED_DISTINCT_SINGLE || + as->opcode == EEOP_AGG_PRESORTED_DISTINCT_MULTI) + { + Assert(as->d.agg_presorted_distinctcheck.jumpdistinct == -1); + as->d.agg_presorted_distinctcheck.jumpdistinct = state->steps_len; + } else Assert(false); } @@ -3622,7 +3659,7 @@ ExecBuildAggTransCall(ExprState *state, AggState *aggstate, * process_ordered_aggregate_{single, multi} and * advance_transition_function. */ - if (pertrans->numSortCols == 0) + if (!pertrans->aggsortrequired) { if (pertrans->transtypeByVal) { diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 5483dee650..91319ba806 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -488,6 +488,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYREF, &&CASE_EEOP_AGG_PLAIN_TRANS_STRICT_BYREF, &&CASE_EEOP_AGG_PLAIN_TRANS_BYREF, + &&CASE_EEOP_AGG_PRESORTED_DISTINCT_SINGLE, + &&CASE_EEOP_AGG_PRESORTED_DISTINCT_MULTI, &&CASE_EEOP_AGG_ORDERED_TRANS_DATUM, &&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE, &&CASE_EEOP_LAST @@ -1772,6 +1774,84 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_AGG_PRESORTED_DISTINCT_SINGLE) + { + AggState *aggstate = castNode(AggState, state->parent); + AggStatePerTrans pertrans = op->d.agg_presorted_distinctcheck.pertrans; + Datum value = pertrans->transfn_fcinfo->args[1].value; + bool isnull = pertrans->transfn_fcinfo->args[1].isnull; + + if (!pertrans->haslast || + pertrans->lastisnull != isnull || + !DatumGetBool(FunctionCall2Coll(&pertrans->equalfnOne, + pertrans->aggCollation, + pertrans->lastdatum, value))) + { + if (pertrans->haslast && !pertrans->inputtypeByVal) + pfree(DatumGetPointer(pertrans->lastdatum)); + + pertrans->haslast = true; + if (!isnull) + { + /* + * XXX is it worth having a dedicted ByVal version of this + * operation so that we can skip switching memory contexts + * and do a simple assign rather than datumCopy below? + */ + MemoryContext oldContext; + oldContext = MemoryContextSwitchTo(aggstate->curaggcontext->ecxt_per_tuple_memory); + + pertrans->lastdatum = datumCopy(value, pertrans->inputtypeByVal, pertrans->inputtypeLen); + + MemoryContextSwitchTo(oldContext); + } + else + pertrans->lastdatum = (Datum) 0; + pertrans->lastisnull = isnull; + EEO_NEXT(); + } + EEO_JUMP(op->d.agg_presorted_distinctcheck.jumpdistinct); + } + + EEO_CASE(EEOP_AGG_PRESORTED_DISTINCT_MULTI) + { + AggState *aggstate = castNode(AggState, state->parent); + AggStatePerTrans pertrans = op->d.agg_presorted_distinctcheck.pertrans; + ExprContext *tmpcontext = aggstate->tmpcontext; + int i; + + /* + * XXX or should we have had these values copied directly into + * the sortslot? If we did then we'd still need to copy them + * into the transfn_fcinfo->args here if we detect the tuple is + * distinct from the previous tuple. + */ + for (i = 0; i < pertrans->numTransInputs; i++) + { + pertrans->sortslot->tts_values[i] = pertrans->transfn_fcinfo->args[i + 1].value; + pertrans->sortslot->tts_isnull[i] = pertrans->transfn_fcinfo->args[i + 1].isnull; + } + + ExecClearTuple(pertrans->sortslot); + pertrans->sortslot->tts_nvalid = pertrans->numInputs; + ExecStoreVirtualTuple(pertrans->sortslot); + + tmpcontext->ecxt_outertuple = pertrans->sortslot; + tmpcontext->ecxt_innertuple = pertrans->uniqslot; + + if (!pertrans->haslast || + !ExecQual(pertrans->equalfnMulti, tmpcontext)) + { + if (pertrans->haslast) + ExecClearTuple(pertrans->uniqslot); + + pertrans->haslast = true; + ExecCopySlot(pertrans->uniqslot, pertrans->sortslot); + EEO_NEXT(); + } + EEO_JUMP(op->d.agg_presorted_distinctcheck.jumpdistinct); + } + /* process single-column ordered aggregate datum */ EEO_CASE(EEOP_AGG_ORDERED_TRANS_DATUM) { diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 8440a76fbd..bcf25a63b4 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -601,7 +601,7 @@ initialize_aggregate(AggState *aggstate, AggStatePerTrans pertrans, /* * Start a fresh sort operation for each DISTINCT/ORDER BY aggregate. */ - if (pertrans->numSortCols > 0) + if (pertrans->aggsortrequired) { /* * In case of rescan, maybe there could be an uncompleted sort @@ -1327,7 +1327,7 @@ finalize_aggregates(AggState *aggstate, pergroupstate = &pergroup[transno]; - if (pertrans->numSortCols > 0) + if (pertrans->aggsortrequired) { Assert(aggstate->aggstrategy != AGG_HASHED && aggstate->aggstrategy != AGG_MIXED); @@ -1341,6 +1341,21 @@ finalize_aggregates(AggState *aggstate, pertrans, pergroupstate); } + else if (pertrans->numDistinctCols > 0 && pertrans->haslast) + { + pertrans->haslast = false; + + if (pertrans->numDistinctCols == 1) + { + if (!pertrans->inputtypeByVal && !pertrans->lastisnull) + pfree(DatumGetPointer(pertrans->lastdatum)); + + pertrans->lastisnull = false; + pertrans->lastdatum = (Datum) 0; + } + else + ExecClearTuple(pertrans->uniqslot); + } } /* @@ -4231,6 +4246,11 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans, * stick them into arrays. We ignore ORDER BY for an ordered-set agg, * however; the agg's transfn and finalfn are responsible for that. * + * When the planner has set the aggpresorted flag, the input to the + * aggregate is already correctly sorted. For ORDER BY aggregates we can + * simply treat these as normal aggregates. For DISTINCT aggregates we + * must still handle de-duplication of consecutive non-distinct values. + * * Note that by construction, if there is a DISTINCT clause then the ORDER * BY clause is a prefix of it (see transformDistinctClause). */ @@ -4238,18 +4258,27 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans, { sortlist = NIL; numSortCols = numDistinctCols = 0; + pertrans->aggsortrequired = false; + } + else if (aggref->aggpresorted && aggref->aggdistinct == NIL) + { + sortlist = NIL; + numSortCols = numDistinctCols = 0; + pertrans->aggsortrequired = false; } else if (aggref->aggdistinct) { sortlist = aggref->aggdistinct; numSortCols = numDistinctCols = list_length(sortlist); Assert(numSortCols >= list_length(aggref->aggorder)); + pertrans->aggsortrequired = !aggref->aggpresorted; } else { sortlist = aggref->aggorder; numSortCols = list_length(sortlist); numDistinctCols = 0; + pertrans->aggsortrequired = (numSortCols > 0); } pertrans->numSortCols = numSortCols; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index bd87f23784..161c9fd2fe 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1541,6 +1541,7 @@ _copyAggref(const Aggref *from) COPY_SCALAR_FIELD(aggstar); COPY_SCALAR_FIELD(aggvariadic); COPY_SCALAR_FIELD(aggkind); + COPY_SCALAR_FIELD(aggpresorted); COPY_SCALAR_FIELD(agglevelsup); COPY_SCALAR_FIELD(aggsplit); COPY_SCALAR_FIELD(aggno); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index dba3e6b31e..f1420fcf92 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -230,6 +230,7 @@ _equalAggref(const Aggref *a, const Aggref *b) COMPARE_SCALAR_FIELD(aggstar); COMPARE_SCALAR_FIELD(aggvariadic); COMPARE_SCALAR_FIELD(aggkind); + COMPARE_SCALAR_FIELD(aggpresorted); COMPARE_SCALAR_FIELD(agglevelsup); COMPARE_SCALAR_FIELD(aggsplit); COMPARE_SCALAR_FIELD(aggno); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index e32b92e299..e8247f3e30 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1179,6 +1179,7 @@ _outAggref(StringInfo str, const Aggref *node) WRITE_BOOL_FIELD(aggstar); WRITE_BOOL_FIELD(aggvariadic); WRITE_CHAR_FIELD(aggkind); + WRITE_BOOL_FIELD(aggpresorted); WRITE_UINT_FIELD(agglevelsup); WRITE_ENUM_FIELD(aggsplit, AggSplit); WRITE_INT_FIELD(aggno); diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index f0b34ecfac..cfda9aa0cb 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -655,6 +655,7 @@ _readAggref(void) READ_BOOL_FIELD(aggstar); READ_BOOL_FIELD(aggvariadic); READ_CHAR_FIELD(aggkind); + READ_BOOL_FIELD(aggpresorted); READ_UINT_FIELD(agglevelsup); READ_ENUM_FIELD(aggsplit, AggSplit); READ_INT_FIELD(aggno); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 1868c4eff4..c9dcabaa97 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -24,6 +24,7 @@ #include "access/sysattr.h" #include "access/table.h" #include "access/xact.h" +#include "catalog/pg_aggregate.h" #include "catalog/pg_constraint.h" #include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" @@ -3075,11 +3076,69 @@ standard_qp_callback(PlannerInfo *root, void *extra) grouping_is_sortable(qp_extra->groupClause)) root->group_pathkeys = make_pathkeys_for_sortclauses(root, - qp_extra->groupClause, - tlist); + qp_extra->groupClause, + tlist); else root->group_pathkeys = NIL; + /* Determine pathkeys for aggregate functions with DISTINCT/ORDER BY */ + if (parse->groupingSets == NIL && root->numOrderedAggs > 0 && + (qp_extra->groupClause == NIL || root->group_pathkeys)) + { + ListCell *lc; + List *pathkeys = NIL; + List *sortlist; + + foreach(lc, root->agginfos) + { + AggInfo *agginfo = (AggInfo *) lfirst(lc); + Aggref *aggref = agginfo->representative_aggref; + + if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) + continue; + + if (aggref->aggdistinct != NIL) + sortlist = aggref->aggdistinct; + else if (aggref->aggorder != NIL) + sortlist = aggref->aggorder; + else + continue; + + Assert(sortlist != NIL); + + /* + * Find the pathkeys with the most sorted derivative of the first + * Aggref. For example, if we determine the pathkeys for the first + * Aggref to be {a}, and we find another with {a,b}, then we use + * {a,b} since it's useful for more Aggrefs than just {a}. We + * currently ignore anything that might have a longer list of + * pathkeys than the first Aggref if it is not contained in the + * pathkeys for the first agg. We can't practically plan for all + * orders of each Aggref, so this seems like the best compromise. + */ + if (pathkeys == NIL) + { + pathkeys = make_pathkeys_for_sortclauses(root, sortlist, + aggref->args); + aggref->aggpresorted = true; + } + else + { + List *pathkeys2 = make_pathkeys_for_sortclauses(root, + sortlist, + aggref->args); + + if (pathkeys_contained_in(pathkeys, pathkeys2)) + { + pathkeys = pathkeys2; + aggref->aggpresorted = true; + } + } + } + + root->group_pathkeys = list_concat(root->group_pathkeys, pathkeys); + } + /* We consider only the first (bottom) window in pathkeys logic */ if (activeWindows != NIL) { diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 785600d04d..3c30f9b9b7 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -252,6 +252,8 @@ typedef enum ExprEvalOp EEOP_AGG_PLAIN_TRANS_INIT_STRICT_BYREF, EEOP_AGG_PLAIN_TRANS_STRICT_BYREF, EEOP_AGG_PLAIN_TRANS_BYREF, + EEOP_AGG_PRESORTED_DISTINCT_SINGLE, + EEOP_AGG_PRESORTED_DISTINCT_MULTI, EEOP_AGG_ORDERED_TRANS_DATUM, EEOP_AGG_ORDERED_TRANS_TUPLE, @@ -657,6 +659,17 @@ typedef struct ExprEvalStep int jumpnull; } agg_plain_pergroup_nullcheck; + /* for EEOP_AGG_PRESORTED_DISTINCT_{SINGLE,MULTI} */ + struct + { + AggStatePerTrans pertrans; + ExprContext *aggcontext; + int setno; + int transno; + int setoff; + int jumpdistinct; + } agg_presorted_distinctcheck; + /* for EEOP_AGG_PLAIN_TRANS_[INIT_][STRICT_]{BYVAL,BYREF} */ /* for EEOP_AGG_ORDERED_TRANS_{DATUM,TUPLE} */ struct diff --git a/src/include/executor/nodeAgg.h b/src/include/executor/nodeAgg.h index 398446d11f..22976655e1 100644 --- a/src/include/executor/nodeAgg.h +++ b/src/include/executor/nodeAgg.h @@ -48,6 +48,12 @@ typedef struct AggStatePerTransData */ bool aggshared; + /* + * True for ORDER BY / DISTINCT aggregates that are not + * Aggref->aggpresorted + */ + bool aggsortrequired; + /* * Number of aggregated input columns. This includes ORDER BY expressions * in both the plain-agg and ordered-set cases. Ordered-set direct args @@ -136,6 +142,9 @@ typedef struct AggStatePerTransData TupleTableSlot *sortslot; /* current input tuple */ TupleTableSlot *uniqslot; /* used for multi-column DISTINCT */ TupleDesc sortdesc; /* descriptor of input tuples */ + Datum lastdatum; /* used for single-column DISTINCT */ + bool lastisnull; /* used for single-column DISTINCT */ + bool haslast; /* got a last value for DISTINCT check */ /* * These values are working state that is initialized at the start of an diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 9ae851d847..9557df0b9e 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -304,6 +304,9 @@ typedef struct Param * replaced with a single argument representing the partial-aggregate * transition values. * + * aggpresorted is set by the query planner for ORDER BY / DISTINCT aggregates + * where the query plan chosen provides presorted input for the executor. + * * aggsplit indicates the expected partial-aggregation mode for the Aggref's * parent plan node. It's always set to AGGSPLIT_SIMPLE in the parser, but * the planner might change it to something else. We use this mainly as @@ -335,6 +338,7 @@ typedef struct Aggref bool aggvariadic; /* true if variadic arguments have been * combined into an array last argument */ char aggkind; /* aggregate kind (see pg_aggregate.h) */ + bool aggpresorted; /* Agg input already sorted */ Index agglevelsup; /* > 0 if agg belongs to outer query */ AggSplit aggsplit; /* expected agg-splitting mode of parent Agg */ int aggno; /* unique ID within the Agg node */ diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index ca06d41dd0..db45ba0aba 100644 --- a/src/test/regress/expected/aggregates.out +++ b/src/test/regress/expected/aggregates.out @@ -2224,8 +2224,8 @@ NOTICE: avg_transfn called with 3 -- shouldn't share states due to the distinctness not matching. select my_avg(distinct one),my_sum(one) from (values(1),(3)) t(one); NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 3 NOTICE: avg_transfn called with 1 +NOTICE: avg_transfn called with 3 NOTICE: avg_transfn called with 3 my_avg | my_sum --------+-------- diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out index dfa4b036b5..72c240c9f7 100644 --- a/src/test/regress/expected/partition_aggregate.out +++ b/src/test/regress/expected/partition_aggregate.out @@ -367,17 +367,17 @@ SELECT c, sum(b order by a) FROM pagg_tab GROUP BY c ORDER BY 1, 2; -> GroupAggregate Group Key: pagg_tab.c -> Sort - Sort Key: pagg_tab.c + Sort Key: pagg_tab.c, pagg_tab.a -> Seq Scan on pagg_tab_p1 pagg_tab -> GroupAggregate Group Key: pagg_tab_1.c -> Sort - Sort Key: pagg_tab_1.c + Sort Key: pagg_tab_1.c, pagg_tab_1.a -> Seq Scan on pagg_tab_p2 pagg_tab_1 -> GroupAggregate Group Key: pagg_tab_2.c -> Sort - Sort Key: pagg_tab_2.c + Sort Key: pagg_tab_2.c, pagg_tab_2.a -> Seq Scan on pagg_tab_p3 pagg_tab_2 (18 rows) @@ -393,7 +393,7 @@ SELECT a, sum(b order by a) FROM pagg_tab GROUP BY a ORDER BY 1, 2; -> GroupAggregate Group Key: pagg_tab.a -> Sort - Sort Key: pagg_tab.a + Sort Key: pagg_tab.a, pagg_tab.a -> Append -> Seq Scan on pagg_tab_p1 pagg_tab_1 -> Seq Scan on pagg_tab_p2 pagg_tab_2 @@ -959,13 +959,13 @@ SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HA Group Key: pagg_tab_ml.a Filter: (avg(pagg_tab_ml.b) < '3'::numeric) -> Sort - Sort Key: pagg_tab_ml.a + Sort Key: pagg_tab_ml.a, pagg_tab_ml.c -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml -> GroupAggregate Group Key: pagg_tab_ml_5.a Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric) -> Sort - Sort Key: pagg_tab_ml_5.a + Sort Key: pagg_tab_ml_5.a, pagg_tab_ml_5.c -> Append -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5 -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6 @@ -973,7 +973,7 @@ SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HA Group Key: pagg_tab_ml_2.a Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric) -> Sort - Sort Key: pagg_tab_ml_2.a + Sort Key: pagg_tab_ml_2.a, pagg_tab_ml_2.c -> Append -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2 -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3 @@ -1005,13 +1005,13 @@ SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HA Group Key: pagg_tab_ml.a Filter: (avg(pagg_tab_ml.b) < '3'::numeric) -> Sort - Sort Key: pagg_tab_ml.a + Sort Key: pagg_tab_ml.a, pagg_tab_ml.c -> Seq Scan on pagg_tab_ml_p1 pagg_tab_ml -> GroupAggregate Group Key: pagg_tab_ml_5.a Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric) -> Sort - Sort Key: pagg_tab_ml_5.a + Sort Key: pagg_tab_ml_5.a, pagg_tab_ml_5.c -> Append -> Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5 -> Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6 @@ -1019,7 +1019,7 @@ SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HA Group Key: pagg_tab_ml_2.a Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric) -> Sort - Sort Key: pagg_tab_ml_2.a + Sort Key: pagg_tab_ml_2.a, pagg_tab_ml_2.c -> Append -> Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2 -> Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3 diff --git a/src/test/regress/expected/tuplesort.out b/src/test/regress/expected/tuplesort.out index 418f296a3f..ef79574ecf 100644 --- a/src/test/regress/expected/tuplesort.out +++ b/src/test/regress/expected/tuplesort.out @@ -622,15 +622,17 @@ EXPLAIN (COSTS OFF) :qry; -> GroupAggregate Group Key: a.col12 Filter: (count(*) > 1) - -> Merge Join - Merge Cond: (a.col12 = b.col12) - -> Sort - Sort Key: a.col12 DESC - -> Seq Scan on test_mark_restore a - -> Sort - Sort Key: b.col12 DESC - -> Seq Scan on test_mark_restore b -(14 rows) + -> Sort + Sort Key: a.col12 DESC, a.col1 + -> Merge Join + Merge Cond: (a.col12 = b.col12) + -> Sort + Sort Key: a.col12 + -> Seq Scan on test_mark_restore a + -> Sort + Sort Key: b.col12 + -> Seq Scan on test_mark_restore b +(16 rows) :qry; col12 | count | count | count | count | count @@ -658,15 +660,17 @@ EXPLAIN (COSTS OFF) :qry; -> GroupAggregate Group Key: a.col12 Filter: (count(*) > 1) - -> Merge Join - Merge Cond: (a.col12 = b.col12) - -> Sort - Sort Key: a.col12 DESC - -> Seq Scan on test_mark_restore a - -> Sort - Sort Key: b.col12 DESC - -> Seq Scan on test_mark_restore b -(14 rows) + -> Sort + Sort Key: a.col12 DESC, a.col1 + -> Merge Join + Merge Cond: (a.col12 = b.col12) + -> Sort + Sort Key: a.col12 + -> Seq Scan on test_mark_restore a + -> Sort + Sort Key: b.col12 + -> Seq Scan on test_mark_restore b +(16 rows) :qry; col12 | count | count | count | count | count