From: Tom Lane Date: Thu, 27 Aug 2009 20:08:03 +0000 (+0000) Subject: Modify the definition of window-function PARTITION BY and ORDER BY clauses X-Git-Tag: REL8_5_ALPHA2~153 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=bb16dc49abd5b5af8f10781fb10f0439cd8431d6;p=postgresql Modify the definition of window-function PARTITION BY and ORDER BY clauses so that their elements are always taken as simple expressions over the query's input columns. It originally seemed like a good idea to make them act exactly like GROUP BY and ORDER BY, right down to the SQL92-era behavior of accepting output column names or numbers. However, that was not such a great idea, for two reasons: 1. It permits circular references, as exhibited in bug #5018: the output column could be the one containing the window function itself. (We actually had a regression test case illustrating this, but nobody thought twice about how confusing that would be.) 2. It doesn't seem like a good idea for, eg, "lead(foo) OVER (ORDER BY foo)" to potentially use two completely different meanings for "foo". Accordingly, narrow down the behavior of window clauses to use only the SQL99-compliant interpretation that the expressions are simple expressions. --- diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml index d67d2b4ff7..f22eeb2804 100644 --- a/doc/src/sgml/ref/select.sgml +++ b/doc/src/sgml/ref/select.sgml @@ -1,5 +1,5 @@ @@ -595,17 +595,24 @@ WINDOW window_name AS ( The elements of the PARTITION BY list are interpreted in - the same fashion as elements of a - , and - the elements of the ORDER BY list are interpreted in the - same fashion as elements of an - . - The only difference is that these expressions can contain aggregate + much the same fashion as elements of a + , except that + they are always simple expressions and never the name or number of an + output column. + Another difference is that these expressions can contain aggregate function calls, which are not allowed in a regular GROUP BY clause. They are allowed here because windowing occurs after grouping and aggregation. + + Similarly, the elements of the ORDER BY list are interpreted + in much the same fashion as elements of an + , except that + the expressions are always taken as simple expressions and never the name + or number of an output column. + + The optional frame_clause defines the window frame for window functions that depend on the diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index bc562e1f97..c2dd31b98d 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -1,4 +1,4 @@ - + SQL Syntax @@ -1619,7 +1619,9 @@ ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING expression that does not itself contain window function calls. The PARTITION BY and ORDER BY lists have essentially the same syntax and semantics as GROUP BY - and ORDER BY clauses of the whole query. + and ORDER BY clauses of the whole query, except that their + expressions are always just expressions and cannot be output-column + names or numbers. window_name is a reference to a named window specification defined in the query's WINDOW clause. Named window specifications are usually referenced with just diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 97c560b309..c441be220e 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -17,7 +17,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.389 2009/06/11 14:48:59 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.390 2009/08/27 20:08:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -825,13 +825,14 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) qry->sortClause = transformSortClause(pstate, stmt->sortClause, &qry->targetList, - true /* fix unknowns */ ); + true /* fix unknowns */, + false /* not window function */); qry->groupClause = transformGroupClause(pstate, stmt->groupClause, &qry->targetList, qry->sortClause, - false); + false /* not window function */); if (stmt->distinctClause == NIL) { @@ -1039,7 +1040,8 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) qry->sortClause = transformSortClause(pstate, stmt->sortClause, &qry->targetList, - true /* fix unknowns */ ); + true /* fix unknowns */, + false /* not window function */); qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, "OFFSET"); @@ -1291,7 +1293,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) qry->sortClause = transformSortClause(pstate, sortClause, &qry->targetList, - false /* no unknowns expected */ ); + false /* no unknowns expected */, + false /* not window function */); pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length); pstate->p_relnamespace = sv_relnamespace; diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 139202f667..a63a280a8c 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.190 2009/07/16 06:33:43 petere Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.191 2009/08/27 20:08:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,16 +37,15 @@ #include "utils/rel.h" +/* clause types for findTargetlistEntrySQL92 */ #define ORDER_CLAUSE 0 #define GROUP_CLAUSE 1 #define DISTINCT_ON_CLAUSE 2 -#define PARTITION_CLAUSE 3 static const char *const clauseText[] = { "ORDER BY", "GROUP BY", - "DISTINCT ON", - "PARTITION BY" + "DISTINCT ON" }; static void extractRemainingColumns(List *common_colnames, @@ -73,8 +72,10 @@ static Node *transformFromClauseItem(ParseState *pstate, Node *n, Relids *containedRels); static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype, Var *l_colvar, Var *r_colvar); -static TargetEntry *findTargetlistEntry(ParseState *pstate, Node *node, - List **tlist, int clause); +static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node, + List **tlist, int clause); +static TargetEntry *findTargetlistEntrySQL99(ParseState *pstate, Node *node, + List **tlist); static int get_matching_location(int sortgroupref, List *sortgrouprefs, List *exprs); static List *addTargetToSortList(ParseState *pstate, TargetEntry *tle, @@ -1216,21 +1217,27 @@ transformLimitClause(ParseState *pstate, Node *clause, /* - * findTargetlistEntry - + * findTargetlistEntrySQL92 - * Returns the targetlist entry matching the given (untransformed) node. * If no matching entry exists, one is created and appended to the target * list as a "resjunk" node. * + * This function supports the old SQL92 ORDER BY interpretation, where the + * expression is an output column name or number. If we fail to find a + * match of that sort, we fall through to the SQL99 rules. For historical + * reasons, Postgres also allows this interpretation for GROUP BY, though + * the standard never did. However, for GROUP BY we prefer a SQL99 match. + * This function is *not* used for WINDOW definitions. + * * node the ORDER BY, GROUP BY, or DISTINCT ON expression to be matched * tlist the target list (passed by reference so we can append to it) * clause identifies clause type being processed */ static TargetEntry * -findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) +findTargetlistEntrySQL92(ParseState *pstate, Node *node, List **tlist, + int clause) { - TargetEntry *target_result = NULL; ListCell *tl; - Node *expr; /*---------- * Handle two special cases as mandated by the SQL92 spec: @@ -1258,8 +1265,7 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) * 2. IntegerConstant * This means to use the n'th item in the existing target list. * Note that it would make no sense to order/group/distinct by an - * actual constant, so this does not create a conflict with our - * extension to order/group by an expression. + * actual constant, so this does not create a conflict with SQL99. * GROUP BY column-number is not allowed by SQL92, but since * the standard has no other behavior defined for this syntax, * we may as well accept this common extension. @@ -1268,7 +1274,7 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) * since the user didn't write them in his SELECT list. * * If neither special case applies, fall through to treat the item as - * an expression. + * an expression per SQL99. *---------- */ if (IsA(node, ColumnRef) && @@ -1278,15 +1284,15 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) char *name = strVal(linitial(((ColumnRef *) node)->fields)); int location = ((ColumnRef *) node)->location; - if (clause == GROUP_CLAUSE || clause == PARTITION_CLAUSE) + if (clause == GROUP_CLAUSE) { /* * In GROUP BY, we must prefer a match against a FROM-clause * column to one against the targetlist. Look to see if there is - * a matching column. If so, fall through to let transformExpr() - * do the rest. NOTE: if name could refer ambiguously to more - * than one column name exposed by FROM, colNameToVar will - * ereport(ERROR). That's just what we want here. + * a matching column. If so, fall through to use SQL99 rules. + * NOTE: if name could refer ambiguously to more than one column + * name exposed by FROM, colNameToVar will ereport(ERROR). That's + * just what we want here. * * Small tweak for 7.4.3: ignore matches in upper query levels. * This effectively changes the search order for bare names to (1) @@ -1295,8 +1301,6 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) * SQL99 do not allow GROUPing BY an outer reference, so this * breaks no cases that are legal per spec, and it seems a more * self-consistent behavior. - * - * Window PARTITION BY clauses should act exactly like GROUP BY. */ if (colNameToVar(pstate, name, true, location) != NULL) name = NULL; @@ -1304,6 +1308,8 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) if (name != NULL) { + TargetEntry *target_result = NULL; + foreach(tl, *tlist) { TargetEntry *tle = (TargetEntry *) lfirst(tl); @@ -1367,12 +1373,36 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) } /* - * Otherwise, we have an expression (this is a Postgres extension not - * found in SQL92). Convert the untransformed node to a transformed - * expression, and search for a match in the tlist. NOTE: it doesn't - * really matter whether there is more than one match. Also, we are - * willing to match a resjunk target here, though the above cases must - * ignore resjunk targets. + * Otherwise, we have an expression, so process it per SQL99 rules. + */ + return findTargetlistEntrySQL99(pstate, node, tlist); +} + +/* + * findTargetlistEntrySQL99 - + * Returns the targetlist entry matching the given (untransformed) node. + * If no matching entry exists, one is created and appended to the target + * list as a "resjunk" node. + * + * This function supports the SQL99 interpretation, wherein the expression + * is just an ordinary expression referencing input column names. + * + * node the ORDER BY, GROUP BY, etc expression to be matched + * tlist the target list (passed by reference so we can append to it) + */ +static TargetEntry * +findTargetlistEntrySQL99(ParseState *pstate, Node *node, List **tlist) +{ + TargetEntry *target_result; + ListCell *tl; + Node *expr; + + /* + * Convert the untransformed node to a transformed expression, and search + * for a match in the tlist. NOTE: it doesn't really matter whether there + * is more than one match. Also, we are willing to match an existing + * resjunk target here, though the SQL92 cases above must ignore resjunk + * targets. */ expr = transformExpr(pstate, node); @@ -1403,16 +1433,15 @@ findTargetlistEntry(ParseState *pstate, Node *node, List **tlist, int clause) * GROUP BY items will be added to the targetlist (as resjunk columns) * if not already present, so the targetlist must be passed by reference. * - * This is also used for window PARTITION BY clauses (which actually act - * just the same, except for the clause name used in error messages). + * This is also used for window PARTITION BY clauses (which act almost the + * same, but are always interpreted per SQL99 rules). */ List * transformGroupClause(ParseState *pstate, List *grouplist, List **targetlist, List *sortClause, - bool isPartition) + bool isWindowFunc) { List *result = NIL; - int clause = isPartition ? PARTITION_CLAUSE : GROUP_CLAUSE; ListCell *gl; foreach(gl, grouplist) @@ -1421,7 +1450,11 @@ transformGroupClause(ParseState *pstate, List *grouplist, TargetEntry *tle; bool found = false; - tle = findTargetlistEntry(pstate, gexpr, targetlist, clause); + if (isWindowFunc) + tle = findTargetlistEntrySQL99(pstate, gexpr, targetlist); + else + tle = findTargetlistEntrySQL92(pstate, gexpr, targetlist, + GROUP_CLAUSE); /* Eliminate duplicates (GROUP BY x, x) */ if (targetIsInSortList(tle, InvalidOid, result)) @@ -1475,12 +1508,16 @@ transformGroupClause(ParseState *pstate, List *grouplist, * * ORDER BY items will be added to the targetlist (as resjunk columns) * if not already present, so the targetlist must be passed by reference. + * + * This is also used for window ORDER BY clauses (which act almost the + * same, but are always interpreted per SQL99 rules). */ List * transformSortClause(ParseState *pstate, List *orderlist, List **targetlist, - bool resolveUnknown) + bool resolveUnknown, + bool isWindowFunc) { List *sortlist = NIL; ListCell *olitem; @@ -1490,8 +1527,11 @@ transformSortClause(ParseState *pstate, SortBy *sortby = (SortBy *) lfirst(olitem); TargetEntry *tle; - tle = findTargetlistEntry(pstate, sortby->node, - targetlist, ORDER_CLAUSE); + if (isWindowFunc) + tle = findTargetlistEntrySQL99(pstate, sortby->node, targetlist); + else + tle = findTargetlistEntrySQL92(pstate, sortby->node, targetlist, + ORDER_CLAUSE); sortlist = addTargetToSortList(pstate, tle, sortlist, *targetlist, sortby, @@ -1550,18 +1590,19 @@ transformWindowDefinitions(ParseState *pstate, /* * Transform PARTITION and ORDER specs, if any. These are treated - * exactly like top-level GROUP BY and ORDER BY clauses, including the - * special handling of nondefault operator semantics. + * almost exactly like top-level GROUP BY and ORDER BY clauses, + * including the special handling of nondefault operator semantics. */ orderClause = transformSortClause(pstate, windef->orderClause, targetlist, - true); + true /* fix unknowns */, + true /* window function */); partitionClause = transformGroupClause(pstate, windef->partitionClause, targetlist, orderClause, - true); + true /* window function */); /* * And prepare the new WindowClause. @@ -1736,8 +1777,8 @@ transformDistinctOnClause(ParseState *pstate, List *distinctlist, int sortgroupref; TargetEntry *tle; - tle = findTargetlistEntry(pstate, dexpr, - targetlist, DISTINCT_ON_CLAUSE); + tle = findTargetlistEntrySQL92(pstate, dexpr, targetlist, + DISTINCT_ON_CLAUSE); sortgroupref = assignSortGroupRef(tle, *targetlist); sortgrouprefs = lappend_int(sortgrouprefs, sortgroupref); } diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h index c642761cb8..6bfb021f86 100644 --- a/src/include/parser/parse_clause.h +++ b/src/include/parser/parse_clause.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_clause.h,v 1.55 2009/06/11 14:49:11 momjian Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_clause.h,v 1.56 2009/08/27 20:08:02 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -28,9 +28,9 @@ extern Node *transformLimitClause(ParseState *pstate, Node *clause, const char *constructName); extern List *transformGroupClause(ParseState *pstate, List *grouplist, List **targetlist, List *sortClause, - bool isPartition); + bool isWindowFunc); extern List *transformSortClause(ParseState *pstate, List *orderlist, - List **targetlist, bool resolveUnknown); + List **targetlist, bool resolveUnknown, bool isWindowFunc); extern List *transformWindowDefinitions(ParseState *pstate, List *windowdefs, diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index 5ef3e6c317..c14011ce0e 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -741,11 +741,11 @@ SELECT rank() OVER (ORDER BY length('abc')); 1 (1 row) --- but this draws an error: "ORDER BY 1" means order by first SELECT column -SELECT rank() OVER (ORDER BY 1); +-- can't order by another window function +SELECT rank() OVER (ORDER BY rank() OVER (ORDER BY random())); ERROR: window functions not allowed in window definition -LINE 1: SELECT rank() OVER (ORDER BY 1); - ^ +LINE 1: SELECT rank() OVER (ORDER BY rank() OVER (ORDER BY random())... + ^ -- some other errors SELECT * FROM empsalary WHERE row_number() OVER (ORDER BY salary) < 10; ERROR: window functions not allowed in WHERE clause diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql index 5ecb3e3e65..fc62a6fd6e 100644 --- a/src/test/regress/sql/window.sql +++ b/src/test/regress/sql/window.sql @@ -167,8 +167,8 @@ SELECT count(*) OVER (PARTITION BY four) FROM (SELECT * FROM tenk1 UNION ALL SEL -- ordering by a non-integer constant is allowed SELECT rank() OVER (ORDER BY length('abc')); --- but this draws an error: "ORDER BY 1" means order by first SELECT column -SELECT rank() OVER (ORDER BY 1); +-- can't order by another window function +SELECT rank() OVER (ORDER BY rank() OVER (ORDER BY random())); -- some other errors SELECT * FROM empsalary WHERE row_number() OVER (ORDER BY salary) < 10;