Skip to content

Commit

Permalink
Implement the FILTER clause for aggregate function calls.
Browse files Browse the repository at this point in the history
This is SQL-standard with a few extensions, namely support for
subqueries and outer references in clause expressions.

catversion bump due to change in Aggref and WindowFunc.

David Fetter, reviewed by Dean Rasheed.
  • Loading branch information
nmisch committed Jul 17, 2013
1 parent 7a8e9f2 commit b560ec1
Show file tree
Hide file tree
Showing 35 changed files with 403 additions and 51 deletions.
2 changes: 2 additions & 0 deletions contrib/pg_stat_statements/pg_stat_statements.c
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,7 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
JumbleExpr(jstate, (Node *) expr->args);
JumbleExpr(jstate, (Node *) expr->aggorder);
JumbleExpr(jstate, (Node *) expr->aggdistinct);
JumbleExpr(jstate, (Node *) expr->aggfilter);
}
break;
case T_WindowFunc:
Expand All @@ -1555,6 +1556,7 @@ JumbleExpr(pgssJumbleState *jstate, Node *node)
APP_JUMB(expr->winfnoid);
APP_JUMB(expr->winref);
JumbleExpr(jstate, (Node *) expr->args);
JumbleExpr(jstate, (Node *) expr->aggfilter);
}
break;
case T_ArrayRef:
Expand Down
2 changes: 1 addition & 1 deletion doc/src/sgml/keywords.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -1786,7 +1786,7 @@
</row>
<row>
<entry><token>FILTER</token></entry>
<entry></entry>
<entry>non-reserved</entry>
<entry>reserved</entry>
<entry>reserved</entry>
<entry></entry>
Expand Down
5 changes: 5 additions & 0 deletions doc/src/sgml/ref/select.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,11 @@ GROUP BY <replaceable class="parameter">expression</replaceable> [, ...]
making up each group, producing a separate value for each group
(whereas without <literal>GROUP BY</literal>, an aggregate
produces a single value computed across all the selected rows).
The set of rows fed to the aggregate function can be further filtered by
attaching a <literal>FILTER</literal> clause to the aggregate function
call; see <xref linkend="syntax-aggregates"> for more information. When
a <literal>FILTER</literal> clause is present, only those rows matching it
are included.
When <literal>GROUP BY</literal> is present, it is not valid for
the <command>SELECT</command> list expressions to refer to
ungrouped columns except within aggregate functions or if the
Expand Down
48 changes: 35 additions & 13 deletions doc/src/sgml/syntax.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -1554,6 +1554,10 @@ sqrt(2)
<secondary>invocation</secondary>
</indexterm>

<indexterm zone="syntax-aggregates">
<primary>filter</primary>
</indexterm>

<para>
An <firstterm>aggregate expression</firstterm> represents the
application of an aggregate function across the rows selected by a
Expand All @@ -1562,19 +1566,19 @@ sqrt(2)
syntax of an aggregate expression is one of the following:

<synopsis>
<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] )
<replaceable>aggregate_name</replaceable> ( * )
<replaceable>aggregate_name</replaceable> (<replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
<replaceable>aggregate_name</replaceable> (ALL <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
<replaceable>aggregate_name</replaceable> (DISTINCT <replaceable>expression</replaceable> [ , ... ] [ <replaceable>order_by_clause</replaceable> ] ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
<replaceable>aggregate_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ]
</synopsis>

where <replaceable>aggregate_name</replaceable> is a previously
defined aggregate (possibly qualified with a schema name),
defined aggregate (possibly qualified with a schema name) and
<replaceable>expression</replaceable> is
any value expression that does not itself contain an aggregate
expression or a window function call, and
<replaceable>order_by_clause</replaceable> is a optional
<literal>ORDER BY</> clause as described below.
expression or a window function call. The optional
<replaceable>order_by_clause</replaceable> and
<replaceable>filter_clause</replaceable> are described below.
</para>

<para>
Expand Down Expand Up @@ -1606,6 +1610,23 @@ sqrt(2)
distinct non-null values of <literal>f1</literal>.
</para>

<para>
If <literal>FILTER</literal> is specified, then only the input
rows for which the <replaceable>filter_clause</replaceable>
evaluates to true are fed to the aggregate function; other rows
are discarded. For example:
<programlisting>
SELECT
count(*) AS unfiltered,
count(*) FILTER (WHERE i < 5) AS filtered
FROM generate_series(1,10) AS s(i);
unfiltered | filtered
------------+----------
10 | 4
(1 row)
</programlisting>
</para>

<para>
Ordinarily, the input rows are fed to the aggregate function in an
unspecified order. In many cases this does not matter; for example,
Expand Down Expand Up @@ -1709,10 +1730,10 @@ SELECT string_agg(a ORDER BY a, ',') FROM table; -- incorrect
The syntax of a window function call is one of the following:

<synopsis>
<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER ( <replaceable class="parameter">window_definition</replaceable> )
<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) OVER <replaceable>window_name</replaceable>
<replaceable>function_name</replaceable> ( * ) OVER ( <replaceable class="parameter">window_definition</replaceable> )
<replaceable>function_name</replaceable> ( * ) OVER <replaceable>window_name</replaceable>
<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
<replaceable>function_name</replaceable> (<optional><replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ... </optional></optional>) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER ( <replaceable class="parameter">window_definition</replaceable> )
<replaceable>function_name</replaceable> ( * ) [ FILTER ( WHERE <replaceable>filter_clause</replaceable> ) ] OVER <replaceable>window_name</replaceable>
</synopsis>
where <replaceable class="parameter">window_definition</replaceable>
has the syntax
Expand Down Expand Up @@ -1836,7 +1857,8 @@ UNBOUNDED FOLLOWING
The built-in window functions are described in <xref
linkend="functions-window-table">. Other window functions can be added by
the user. Also, any built-in or user-defined aggregate function can be
used as a window function.
used as a window function. Only aggregate window functions accept
a <literal>FILTER</literal> clause.
</para>

<para>
Expand Down
4 changes: 4 additions & 0 deletions src/backend/executor/execQual.c
Original file line number Diff line number Diff line change
Expand Up @@ -4410,6 +4410,8 @@ ExecInitExpr(Expr *node, PlanState *parent)

astate->args = (List *) ExecInitExpr((Expr *) aggref->args,
parent);
astate->aggfilter = ExecInitExpr(aggref->aggfilter,
parent);

/*
* Complain if the aggregate's arguments contain any
Expand Down Expand Up @@ -4448,6 +4450,8 @@ ExecInitExpr(Expr *node, PlanState *parent)

wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
parent);
wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
parent);

/*
* Complain if the windowfunc's arguments contain any
Expand Down
6 changes: 3 additions & 3 deletions src/backend/executor/execUtils.c
Original file line number Diff line number Diff line change
Expand Up @@ -649,9 +649,9 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo)
}

/*
* Don't examine the arguments of Aggrefs or WindowFuncs, because those do
* not represent expressions to be evaluated within the overall
* targetlist's econtext.
* Don't examine the arguments or filters of Aggrefs or WindowFuncs,
* because those do not represent expressions to be evaluated within the
* overall targetlist's econtext.
*/
if (IsA(node, Aggref))
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/backend/executor/functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
param = ParseFuncOrColumn(pstate,
list_make1(subfield),
list_make1(param),
NIL, false, false, false,
NIL, NULL, false, false, false,
NULL, true, cref->location);
}

Expand Down
13 changes: 13 additions & 0 deletions src/backend/executor/nodeAgg.c
Original file line number Diff line number Diff line change
Expand Up @@ -484,10 +484,23 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
{
AggStatePerAgg peraggstate = &aggstate->peragg[aggno];
AggStatePerGroup pergroupstate = &pergroup[aggno];
ExprState *filter = peraggstate->aggrefstate->aggfilter;
int nargs = peraggstate->numArguments;
int i;
TupleTableSlot *slot;

/* Skip anything FILTERed out */
if (filter)
{
bool isnull;
Datum res;

res = ExecEvalExprSwitchContext(filter, aggstate->tmpcontext,
&isnull, NULL);
if (isnull || !DatumGetBool(res))
continue;
}

/* Evaluate the current input expressions for this aggregate */
slot = ExecProject(peraggstate->evalproj, NULL);

Expand Down
14 changes: 14 additions & 0 deletions src/backend/executor/nodeWindowAgg.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,23 @@ advance_windowaggregate(WindowAggState *winstate,
int i;
MemoryContext oldContext;
ExprContext *econtext = winstate->tmpcontext;
ExprState *filter = wfuncstate->aggfilter;

oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);

/* Skip anything FILTERed out */
if (filter)
{
bool isnull;
Datum res = ExecEvalExpr(filter, econtext, &isnull, NULL);

if (isnull || !DatumGetBool(res))
{
MemoryContextSwitchTo(oldContext);
return;
}
}

/* We start from 1, since the 0th arg will be the transition value */
i = 1;
foreach(arg, wfuncstate->args)
Expand Down
3 changes: 3 additions & 0 deletions src/backend/nodes/copyfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,7 @@ _copyAggref(const Aggref *from)
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggorder);
COPY_NODE_FIELD(aggdistinct);
COPY_NODE_FIELD(aggfilter);
COPY_SCALAR_FIELD(aggstar);
COPY_SCALAR_FIELD(agglevelsup);
COPY_LOCATION_FIELD(location);
Expand All @@ -1157,6 +1158,7 @@ _copyWindowFunc(const WindowFunc *from)
COPY_SCALAR_FIELD(wincollid);
COPY_SCALAR_FIELD(inputcollid);
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(aggfilter);
COPY_SCALAR_FIELD(winref);
COPY_SCALAR_FIELD(winstar);
COPY_SCALAR_FIELD(winagg);
Expand Down Expand Up @@ -2152,6 +2154,7 @@ _copyFuncCall(const FuncCall *from)
COPY_NODE_FIELD(funcname);
COPY_NODE_FIELD(args);
COPY_NODE_FIELD(agg_order);
COPY_NODE_FIELD(agg_filter);
COPY_SCALAR_FIELD(agg_star);
COPY_SCALAR_FIELD(agg_distinct);
COPY_SCALAR_FIELD(func_variadic);
Expand Down
3 changes: 3 additions & 0 deletions src/backend/nodes/equalfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ _equalAggref(const Aggref *a, const Aggref *b)
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggorder);
COMPARE_NODE_FIELD(aggdistinct);
COMPARE_NODE_FIELD(aggfilter);
COMPARE_SCALAR_FIELD(aggstar);
COMPARE_SCALAR_FIELD(agglevelsup);
COMPARE_LOCATION_FIELD(location);
Expand All @@ -211,6 +212,7 @@ _equalWindowFunc(const WindowFunc *a, const WindowFunc *b)
COMPARE_SCALAR_FIELD(wincollid);
COMPARE_SCALAR_FIELD(inputcollid);
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(aggfilter);
COMPARE_SCALAR_FIELD(winref);
COMPARE_SCALAR_FIELD(winstar);
COMPARE_SCALAR_FIELD(winagg);
Expand Down Expand Up @@ -1993,6 +1995,7 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
COMPARE_NODE_FIELD(funcname);
COMPARE_NODE_FIELD(args);
COMPARE_NODE_FIELD(agg_order);
COMPARE_NODE_FIELD(agg_filter);
COMPARE_SCALAR_FIELD(agg_star);
COMPARE_SCALAR_FIELD(agg_distinct);
COMPARE_SCALAR_FIELD(func_variadic);
Expand Down
1 change: 1 addition & 0 deletions src/backend/nodes/makefuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ makeFuncCall(List *name, List *args, int location)
n->args = args;
n->location = location;
n->agg_order = NIL;
n->agg_filter = NULL;
n->agg_star = FALSE;
n->agg_distinct = FALSE;
n->func_variadic = FALSE;
Expand Down
8 changes: 8 additions & 0 deletions src/backend/nodes/nodeFuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->aggdistinct,
walker, context))
return true;
if (walker((Node *) expr->aggfilter, context))
return true;
}
break;
case T_WindowFunc:
Expand All @@ -1580,6 +1582,8 @@ expression_tree_walker(Node *node,
if (expression_tree_walker((Node *) expr->args,
walker, context))
return true;
if (walker((Node *) expr->aggfilter, context))
return true;
}
break;
case T_ArrayRef:
Expand Down Expand Up @@ -2079,6 +2083,7 @@ expression_tree_mutator(Node *node,
MUTATE(newnode->args, aggref->args, List *);
MUTATE(newnode->aggorder, aggref->aggorder, List *);
MUTATE(newnode->aggdistinct, aggref->aggdistinct, List *);
MUTATE(newnode->aggfilter, aggref->aggfilter, Expr *);
return (Node *) newnode;
}
break;
Expand All @@ -2089,6 +2094,7 @@ expression_tree_mutator(Node *node,

FLATCOPY(newnode, wfunc, WindowFunc);
MUTATE(newnode->args, wfunc->args, List *);
MUTATE(newnode->aggfilter, wfunc->aggfilter, Expr *);
return (Node *) newnode;
}
break;
Expand Down Expand Up @@ -2951,6 +2957,8 @@ raw_expression_tree_walker(Node *node,
return true;
if (walker(fcall->agg_order, context))
return true;
if (walker(fcall->agg_filter, context))
return true;
if (walker(fcall->over, context))
return true;
/* function name is deemed uninteresting */
Expand Down
3 changes: 3 additions & 0 deletions src/backend/nodes/outfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,7 @@ _outAggref(StringInfo str, const Aggref *node)
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggorder);
WRITE_NODE_FIELD(aggdistinct);
WRITE_NODE_FIELD(aggfilter);
WRITE_BOOL_FIELD(aggstar);
WRITE_UINT_FIELD(agglevelsup);
WRITE_LOCATION_FIELD(location);
Expand All @@ -973,6 +974,7 @@ _outWindowFunc(StringInfo str, const WindowFunc *node)
WRITE_OID_FIELD(wincollid);
WRITE_OID_FIELD(inputcollid);
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(aggfilter);
WRITE_UINT_FIELD(winref);
WRITE_BOOL_FIELD(winstar);
WRITE_BOOL_FIELD(winagg);
Expand Down Expand Up @@ -2080,6 +2082,7 @@ _outFuncCall(StringInfo str, const FuncCall *node)
WRITE_NODE_FIELD(funcname);
WRITE_NODE_FIELD(args);
WRITE_NODE_FIELD(agg_order);
WRITE_NODE_FIELD(agg_filter);
WRITE_BOOL_FIELD(agg_star);
WRITE_BOOL_FIELD(agg_distinct);
WRITE_BOOL_FIELD(func_variadic);
Expand Down
2 changes: 2 additions & 0 deletions src/backend/nodes/readfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ _readAggref(void)
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggorder);
READ_NODE_FIELD(aggdistinct);
READ_NODE_FIELD(aggfilter);
READ_BOOL_FIELD(aggstar);
READ_UINT_FIELD(agglevelsup);
READ_LOCATION_FIELD(location);
Expand All @@ -499,6 +500,7 @@ _readWindowFunc(void)
READ_OID_FIELD(wincollid);
READ_OID_FIELD(inputcollid);
READ_NODE_FIELD(args);
READ_NODE_FIELD(aggfilter);
READ_UINT_FIELD(winref);
READ_BOOL_FIELD(winstar);
READ_BOOL_FIELD(winagg);
Expand Down
8 changes: 8 additions & 0 deletions src/backend/optimizer/path/costsize.c
Original file line number Diff line number Diff line change
Expand Up @@ -1590,6 +1590,14 @@ cost_windowagg(Path *path, PlannerInfo *root,
startup_cost += argcosts.startup;
wfunccost += argcosts.per_tuple;

/*
* Add the filter's cost to per-input-row costs. XXX We should reduce
* input expression costs according to filter selectivity.
*/
cost_qual_eval_node(&argcosts, (Node *) wfunc->aggfilter, root);
startup_cost += argcosts.startup;
wfunccost += argcosts.per_tuple;

total_cost += wfunccost * input_tuples;
}

Expand Down
6 changes: 6 additions & 0 deletions src/backend/optimizer/plan/planagg.c
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,12 @@ find_minmax_aggs_walker(Node *node, List **context)
*/
if (aggref->aggorder != NIL)
return true;
/*
* We might implement the optimization when a FILTER clause is present
* by adding the filter to the quals of the generated subquery.
*/
if (aggref->aggfilter != NULL)
return true;
/* note: we do not care if DISTINCT is mentioned ... */

aggsortop = fetch_agg_sort_op(aggref->aggfnoid);
Expand Down
Loading

0 comments on commit b560ec1

Please sign in to comment.