提交 d08cab6c authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Add ROW_NUMBER(), RANK(), and DENSE_RANK() window functions

上级 a306265c
...@@ -2533,6 +2533,19 @@ The column list of the resulting table is C1, C2, and so on. ...@@ -2533,6 +2533,19 @@ The column list of the resulting table is C1, C2, and so on.
SELECT * FROM (VALUES(1, 'Hello'), (2, 'World')) AS V; SELECT * FROM (VALUES(1, 'Hello'), (2, 'World')) AS V;
" "
"Other Grammar","Window specification","
([PARTITION BY expression [,...])] [ORDER BY order [,...]])
","
A window specification for a window function or aggregate.
Window functions are currently experimental in H2 and should be used with caution.
They also may require a lot of memory for large queries.
","
()
(ORDER BY ID)
(PARTITION BY CATEGORY)
(PARTITION BY CATEGORY ORDER BY NAME, ID)
"
"Other Grammar","Term"," "Other Grammar","Term","
value value
| columnName | columnName
...@@ -3251,7 +3264,8 @@ INTERVAL MINUTE TO SECOND ...@@ -3251,7 +3264,8 @@ INTERVAL MINUTE TO SECOND
" "
"Functions (Aggregate)","AVG"," "Functions (Aggregate)","AVG","
AVG ( [ DISTINCT ] { numeric } ) [ FILTER ( WHERE expression ) ] AVG ( [ DISTINCT ] { numeric } )
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The average (mean) value. The average (mean) value.
If no rows are selected, the result is NULL. If no rows are selected, the result is NULL.
...@@ -3262,7 +3276,8 @@ AVG(X) ...@@ -3262,7 +3276,8 @@ AVG(X)
" "
"Functions (Aggregate)","BIT_AND"," "Functions (Aggregate)","BIT_AND","
BIT_AND(expression) [ FILTER ( WHERE expression ) ] BIT_AND(expression)
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The bitwise AND of all non-null values. The bitwise AND of all non-null values.
If no rows are selected, the result is NULL. If no rows are selected, the result is NULL.
...@@ -3272,7 +3287,8 @@ BIT_AND(ID) ...@@ -3272,7 +3287,8 @@ BIT_AND(ID)
" "
"Functions (Aggregate)","BIT_OR"," "Functions (Aggregate)","BIT_OR","
BIT_OR(expression) [ FILTER ( WHERE expression ) ] BIT_OR(expression)
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The bitwise OR of all non-null values. The bitwise OR of all non-null values.
If no rows are selected, the result is NULL. If no rows are selected, the result is NULL.
...@@ -3282,7 +3298,8 @@ BIT_OR(ID) ...@@ -3282,7 +3298,8 @@ BIT_OR(ID)
" "
"Functions (Aggregate)","BOOL_AND"," "Functions (Aggregate)","BOOL_AND","
BOOL_AND(boolean) [ FILTER ( WHERE expression ) ] BOOL_AND(boolean)
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
Returns true if all expressions are true. Returns true if all expressions are true.
If no rows are selected, the result is NULL. If no rows are selected, the result is NULL.
...@@ -3292,7 +3309,8 @@ BOOL_AND(ID>10) ...@@ -3292,7 +3309,8 @@ BOOL_AND(ID>10)
" "
"Functions (Aggregate)","BOOL_OR"," "Functions (Aggregate)","BOOL_OR","
BOOL_OR(boolean) [ FILTER ( WHERE expression ) ] BOOL_OR(boolean)
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
Returns true if any expression is true. Returns true if any expression is true.
If no rows are selected, the result is NULL. If no rows are selected, the result is NULL.
...@@ -3302,7 +3320,8 @@ BOOL_OR(NAME LIKE 'W%') ...@@ -3302,7 +3320,8 @@ BOOL_OR(NAME LIKE 'W%')
" "
"Functions (Aggregate)","COUNT"," "Functions (Aggregate)","COUNT","
COUNT( { * | { [ DISTINCT ] expression } } ) [ FILTER ( WHERE expression ) ] COUNT( { * | { [ DISTINCT ] expression } } )
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The count of all row, or of the non-null values. The count of all row, or of the non-null values.
This method returns a long. This method returns a long.
...@@ -3315,7 +3334,8 @@ COUNT(*) ...@@ -3315,7 +3334,8 @@ COUNT(*)
"Functions (Aggregate)","GROUP_CONCAT"," "Functions (Aggregate)","GROUP_CONCAT","
GROUP_CONCAT ( [ DISTINCT ] string GROUP_CONCAT ( [ DISTINCT ] string
[ ORDER BY { expression [ ASC | DESC ] } [,...] ] [ ORDER BY { expression [ ASC | DESC ] } [,...] ]
[ SEPARATOR expression ] ) [ FILTER ( WHERE expression ) ] [ SEPARATOR expression ] )
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
Concatenates strings with a separator. Concatenates strings with a separator.
The default separator is a ',' (without space). The default separator is a ',' (without space).
...@@ -3329,7 +3349,7 @@ GROUP_CONCAT(NAME ORDER BY ID SEPARATOR ', ') ...@@ -3329,7 +3349,7 @@ GROUP_CONCAT(NAME ORDER BY ID SEPARATOR ', ')
"Functions (Aggregate)","ARRAY_AGG"," "Functions (Aggregate)","ARRAY_AGG","
ARRAY_AGG ( [ DISTINCT ] string ARRAY_AGG ( [ DISTINCT ] string
[ ORDER BY { expression [ ASC | DESC ] } [,...] ] ) [ ORDER BY { expression [ ASC | DESC ] } [,...] ] )
[ FILTER ( WHERE expression ) ] [FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
Aggregate the value into an array. Aggregate the value into an array.
This method returns an array. This method returns an array.
...@@ -3340,7 +3360,8 @@ ARRAY_AGG(NAME ORDER BY ID) ...@@ -3340,7 +3360,8 @@ ARRAY_AGG(NAME ORDER BY ID)
" "
"Functions (Aggregate)","MAX"," "Functions (Aggregate)","MAX","
MAX(value) [ FILTER ( WHERE expression ) ] MAX(value)
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The highest value. The highest value.
If no rows are selected, the result is NULL. If no rows are selected, the result is NULL.
...@@ -3351,7 +3372,8 @@ MAX(NAME) ...@@ -3351,7 +3372,8 @@ MAX(NAME)
" "
"Functions (Aggregate)","MIN"," "Functions (Aggregate)","MIN","
MIN(value) [ FILTER ( WHERE expression ) ] MIN(value)
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The lowest value. The lowest value.
If no rows are selected, the result is NULL. If no rows are selected, the result is NULL.
...@@ -3362,7 +3384,8 @@ MIN(NAME) ...@@ -3362,7 +3384,8 @@ MIN(NAME)
" "
"Functions (Aggregate)","SUM"," "Functions (Aggregate)","SUM","
SUM( [ DISTINCT ] { numeric } ) [ FILTER ( WHERE expression ) ] SUM( [ DISTINCT ] { numeric } )
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The sum of all values. The sum of all values.
If no rows are selected, the result is NULL. If no rows are selected, the result is NULL.
...@@ -3374,7 +3397,8 @@ SUM(X) ...@@ -3374,7 +3397,8 @@ SUM(X)
" "
"Functions (Aggregate)","SELECTIVITY"," "Functions (Aggregate)","SELECTIVITY","
SELECTIVITY(value) [ FILTER ( WHERE expression ) ] SELECTIVITY(value)
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
Estimates the selectivity (0-100) of a value. Estimates the selectivity (0-100) of a value.
The value is defined as (100 * distinctCount / rowCount). The value is defined as (100 * distinctCount / rowCount).
...@@ -3386,7 +3410,8 @@ SELECT SELECTIVITY(FIRSTNAME), SELECTIVITY(NAME) FROM TEST WHERE ROWNUM()<20000 ...@@ -3386,7 +3410,8 @@ SELECT SELECTIVITY(FIRSTNAME), SELECTIVITY(NAME) FROM TEST WHERE ROWNUM()<20000
" "
"Functions (Aggregate)","STDDEV_POP"," "Functions (Aggregate)","STDDEV_POP","
STDDEV_POP( [ DISTINCT ] numeric ) [ FILTER ( WHERE expression ) ] STDDEV_POP( [ DISTINCT ] numeric )
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The population standard deviation. The population standard deviation.
This method returns a double. This method returns a double.
...@@ -3397,7 +3422,8 @@ STDDEV_POP(X) ...@@ -3397,7 +3422,8 @@ STDDEV_POP(X)
" "
"Functions (Aggregate)","STDDEV_SAMP"," "Functions (Aggregate)","STDDEV_SAMP","
STDDEV_SAMP( [ DISTINCT ] numeric ) [ FILTER ( WHERE expression ) ] STDDEV_SAMP( [ DISTINCT ] numeric )
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The sample standard deviation. The sample standard deviation.
This method returns a double. This method returns a double.
...@@ -3408,7 +3434,8 @@ STDDEV(X) ...@@ -3408,7 +3434,8 @@ STDDEV(X)
" "
"Functions (Aggregate)","VAR_POP"," "Functions (Aggregate)","VAR_POP","
VAR_POP( [ DISTINCT ] numeric ) [ FILTER ( WHERE expression ) ] VAR_POP( [ DISTINCT ] numeric )
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The population variance (square of the population standard deviation). The population variance (square of the population standard deviation).
This method returns a double. This method returns a double.
...@@ -3419,7 +3446,8 @@ VAR_POP(X) ...@@ -3419,7 +3446,8 @@ VAR_POP(X)
" "
"Functions (Aggregate)","VAR_SAMP"," "Functions (Aggregate)","VAR_SAMP","
VAR_SAMP( [ DISTINCT ] numeric ) [ FILTER ( WHERE expression ) ] VAR_SAMP( [ DISTINCT ] numeric )
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The sample variance (square of the sample standard deviation). The sample variance (square of the sample standard deviation).
This method returns a double. This method returns a double.
...@@ -3430,7 +3458,8 @@ VAR_SAMP(X) ...@@ -3430,7 +3458,8 @@ VAR_SAMP(X)
" "
"Functions (Aggregate)","MEDIAN"," "Functions (Aggregate)","MEDIAN","
MEDIAN( [ DISTINCT ] value ) [ FILTER ( WHERE expression ) ] MEDIAN( [ DISTINCT ] value )
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
The value separating the higher half of a values from the lower half. The value separating the higher half of a values from the lower half.
Returns the middle value or an interpolated value between two middle values if number of values is even. Returns the middle value or an interpolated value between two middle values if number of values is even.
...@@ -3445,7 +3474,7 @@ MEDIAN(X) ...@@ -3445,7 +3474,7 @@ MEDIAN(X)
"Functions (Aggregate)","MODE"," "Functions (Aggregate)","MODE","
{ MODE( value ) [ ORDER BY expression [ ASC | DESC ] ] } { MODE( value ) [ ORDER BY expression [ ASC | DESC ] ] }
| { MODE() WITHIN GROUP(ORDER BY expression [ ASC | DESC ]) } | { MODE() WITHIN GROUP(ORDER BY expression [ ASC | DESC ]) }
[ FILTER ( WHERE expression ) ] [FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
Returns the value that occurs with the greatest frequency. Returns the value that occurs with the greatest frequency.
If there are multiple values with the same frequency only one value will be returned. If there are multiple values with the same frequency only one value will be returned.
...@@ -3464,7 +3493,8 @@ MODE() WITHIN GROUP(ORDER BY X) ...@@ -3464,7 +3493,8 @@ MODE() WITHIN GROUP(ORDER BY X)
" "
"Functions (Aggregate)","ENVELOPE"," "Functions (Aggregate)","ENVELOPE","
ENVELOPE( value ) [ FILTER ( WHERE expression ) ] ENVELOPE( value )
[FILTER (WHERE expression)] [OVER windowSpecification]
"," ","
Returns the minimum bounding box that encloses all specified GEOMETRY values. Returns the minimum bounding box that encloses all specified GEOMETRY values.
Only 2D coordinate plane is supported. Only 2D coordinate plane is supported.
...@@ -4931,14 +4961,14 @@ READONLY() ...@@ -4931,14 +4961,14 @@ READONLY()
" "
"Functions (System)","ROWNUM"," "Functions (System)","ROWNUM","
{ ROWNUM() } | { ROW_NUMBER() OVER() } ROWNUM()
"," ","
Returns the number of the current row. Returns the number of the current row.
This method returns a long. This method returns an integer value.
It is supported for SELECT statements, as well as for DELETE and UPDATE. It is supported for SELECT statements, as well as for DELETE and UPDATE.
The first row has the row number 1, and is calculated before ordering and grouping the result set, The first row has the row number 1, and is calculated before ordering and grouping the result set,
but after evaluating index conditions (even when the index conditions are specified in an outer query). but after evaluating index conditions (even when the index conditions are specified in an outer query).
To get the row number after ordering and grouping, use a subquery. Use the ROW_NUMBER() OVER () function to get row numbers after grouping or in specified order.
"," ","
SELECT ROWNUM(), * FROM TEST; SELECT ROWNUM(), * FROM TEST;
SELECT ROWNUM(), * FROM (SELECT * FROM TEST ORDER BY NAME); SELECT ROWNUM(), * FROM (SELECT * FROM TEST ORDER BY NAME);
...@@ -5036,6 +5066,49 @@ Returns the H2 version as a String. ...@@ -5036,6 +5066,49 @@ Returns the H2 version as a String.
H2VERSION() H2VERSION()
" "
"Functions (Window)","ROW_NUMBER","
ROW_NUMBER() OVER windowSpecification
","
Returns the number of the current row starting with 1.
Window functions are currently experimental in H2 and should be used with caution.
They also may require a lot of memory for large queries.
","
SELECT ROW_NUMBER() OVER (), * FROM TEST;
SELECT ROW_NUMBER() OVER (ORDER BY ID), * FROM TEST;
SELECT ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST;
"
"Functions (Window)","RANK","
RANK() OVER windowSpecification
","
Returns the rank of the current row.
The rank of a row is the number of rows that precede this row plus 1.
If two or more rows have the same values in ORDER BY columns, these rows get the same rank from the first row with the same values.
It means that gaps in ranks are possible.
Window functions are currently experimental in H2 and should be used with caution.
They also may require a lot of memory for large queries.
","
SELECT RANK() OVER (ORDER BY ID), * FROM TEST;
SELECT RANK() OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST;
"
"Functions (Window)","DENSE_RANK","
DENSE_RANK() OVER windowSpecification
","
Returns the dense rank of the current row.
The rank of a row is the number of groups of rows with the same values in ORDER BY columns that precede group with this row plus 1.
If two or more rows have the same values in ORDER BY columns, these rows get the same rank.
Gaps in ranks are not possible.
Window functions are currently experimental in H2 and should be used with caution.
They also may require a lot of memory for large queries.
","
SELECT RANK() OVER (ORDER BY ID), * FROM TEST;
SELECT RANK() OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST;
"
"System Tables","Information Schema"," "System Tables","Information Schema","
INFORMATION_SCHEMA INFORMATION_SCHEMA
"," ","
......
...@@ -159,6 +159,34 @@ syntax-end --> ...@@ -159,6 +159,34 @@ syntax-end -->
</table> </table>
<!-- railroad-end --> <!-- railroad-end -->
<h3>Window Functions</h3>
<!-- syntax-start
<p class="notranslate">
<c:forEach var="item" items="functionsWindow">
<a href="#${item.link}" >${item.topic}</a><br />
</c:forEach>
</p>
syntax-end -->
<!-- railroad-start -->
<table class="notranslate index">
<tr>
<td class="index">
<c:forEach var="item" items="functionsWindow-0">
<a href="#${item.link}" >${item.topic}</a><br />
</c:forEach>
</td><td class="index">
<c:forEach var="item" items="functionsWindow-1">
<a href="#${item.link}" >${item.topic}</a><br />
</c:forEach>
</td><td class="index">
<c:forEach var="item" items="functionsWindow-2">
<a href="#${item.link}" >${item.topic}</a><br />
</c:forEach>
</td>
</tr>
</table>
<!-- railroad-end -->
<!-- railroad-start --> <!-- railroad-start -->
<h2>Details</h2> <h2>Details</h2>
<p>Click on the header to switch between railroad diagram and BNF.</p> <p>Click on the header to switch between railroad diagram and BNF.</p>
...@@ -269,6 +297,27 @@ syntax-end --> ...@@ -269,6 +297,27 @@ syntax-end -->
<p class="notranslate">${item.example}</p> <p class="notranslate">${item.example}</p>
</c:forEach> </c:forEach>
<h2>Window Functions</h2>
<c:forEach var="item" items="functionsWindow">
<h3 id="${item.link}" class="notranslate" onclick="switchBnf(this)">${item.topic}</h3>
<!-- railroad-start -->
<pre name="bnf" style="display: none">
${item.syntax}
</pre>
<div name="railroad">
${item.railroad}
</div>
<!-- railroad-end -->
<!-- syntax-start
<pre>
${item.syntax}
</pre>
syntax-end -->
<p>${item.text}</p>
<p>Example:</p>
<p class="notranslate">${item.example}</p>
</c:forEach>
<!--[if lte IE 7]><script language="javascript">switchBnf(null);</script><![endif]--> <!--[if lte IE 7]><script language="javascript">switchBnf(null);</script><![endif]-->
<!-- [close] { --></div></td></tr></table><!-- } --><!-- analytics --></body></html> <!-- [close] { --></div></td></tr></table><!-- } --><!-- analytics --></body></html>
......
...@@ -175,6 +175,8 @@ import org.h2.expression.aggregate.Aggregate; ...@@ -175,6 +175,8 @@ import org.h2.expression.aggregate.Aggregate;
import org.h2.expression.aggregate.Aggregate.AggregateType; import org.h2.expression.aggregate.Aggregate.AggregateType;
import org.h2.expression.aggregate.JavaAggregate; import org.h2.expression.aggregate.JavaAggregate;
import org.h2.expression.aggregate.Window; import org.h2.expression.aggregate.Window;
import org.h2.expression.aggregate.WindowFunction;
import org.h2.expression.aggregate.WindowFunction.WindowFunctionType;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
...@@ -3032,13 +3034,15 @@ public class Parser { ...@@ -3032,13 +3034,15 @@ public class Parser {
} }
private void readFilterAndOver(AbstractAggregate aggregate) { private void readFilterAndOver(AbstractAggregate aggregate) {
if (readIf("FILTER")) { boolean isAggregate = aggregate.isAggregate();
if (isAggregate && readIf("FILTER")) {
read(OPEN_PAREN); read(OPEN_PAREN);
read(WHERE); read(WHERE);
Expression filterCondition = readExpression(); Expression filterCondition = readExpression();
read(CLOSE_PAREN); read(CLOSE_PAREN);
aggregate.setFilterCondition(filterCondition); aggregate.setFilterCondition(filterCondition);
} }
Window over = null;
if (readIf("OVER")) { if (readIf("OVER")) {
read(OPEN_PAREN); read(OPEN_PAREN);
ArrayList<Expression> partitionBy = null; ArrayList<Expression> partitionBy = null;
...@@ -3054,10 +3058,15 @@ public class Parser { ...@@ -3054,10 +3058,15 @@ public class Parser {
if (readIf(ORDER)) { if (readIf(ORDER)) {
read("BY"); read("BY");
orderBy = parseSimpleOrderList(); orderBy = parseSimpleOrderList();
} else if (!isAggregate) {
orderBy = new ArrayList<>(0);
} }
read(CLOSE_PAREN); read(CLOSE_PAREN);
aggregate.setOverCondition(new Window(partitionBy, orderBy)); over = new Window(partitionBy, orderBy);
aggregate.setOverCondition(over);
currentSelect.setWindowQuery(); currentSelect.setWindowQuery();
} else if (!isAggregate) {
throw getSyntaxError();
} else { } else {
currentSelect.setGroupQuery(); currentSelect.setGroupQuery();
} }
...@@ -3088,6 +3097,10 @@ public class Parser { ...@@ -3088,6 +3097,10 @@ public class Parser {
} }
Function function = Function.getFunction(database, name); Function function = Function.getFunction(database, name);
if (function == null) { if (function == null) {
WindowFunction windowFunction = readWindowFunction(name);
if (windowFunction != null) {
return windowFunction;
}
UserAggregate aggregate = database.findAggregate(name); UserAggregate aggregate = database.findAggregate(name);
if (aggregate != null) { if (aggregate != null) {
return readJavaAggregate(aggregate); return readJavaAggregate(aggregate);
...@@ -3233,16 +3246,6 @@ public class Parser { ...@@ -3233,16 +3246,6 @@ public class Parser {
tf.setColumns(columns); tf.setColumns(columns);
break; break;
} }
case Function.ROW_NUMBER:
read(CLOSE_PAREN);
read("OVER");
read(OPEN_PAREN);
read(CLOSE_PAREN);
if (currentSelect == null && currentPrepared == null) {
throw getSyntaxError();
}
return new Rownum(currentSelect == null ? currentPrepared
: currentSelect);
default: default:
if (!readIf(CLOSE_PAREN)) { if (!readIf(CLOSE_PAREN)) {
int i = 0; int i = 0;
...@@ -3255,6 +3258,24 @@ public class Parser { ...@@ -3255,6 +3258,24 @@ public class Parser {
return function; return function;
} }
private WindowFunction readWindowFunction(String name) {
if (!database.getSettings().databaseToUpper) {
// if not yet converted to uppercase, do it now
name = StringUtils.toUpperEnglish(name);
}
WindowFunctionType type = WindowFunctionType.get(name);
if (type == null) {
return null;
}
if (currentSelect == null) {
throw getSyntaxError();
}
read(CLOSE_PAREN);
WindowFunction function = new WindowFunction(type, currentSelect);
readFilterAndOver(function);
return function;
}
private Expression readFunctionWithoutParameters(String name) { private Expression readFunctionWithoutParameters(String name) {
if (database.isAllowBuiltinAliasOverride()) { if (database.isAllowBuiltinAliasOverride()) {
FunctionAlias functionAlias = database.getSchema(session.getCurrentSchemaName()).findFunction(name); FunctionAlias functionAlias = database.getSchema(session.getCurrentSchemaName()).findFunction(name);
......
...@@ -146,8 +146,6 @@ public class Function extends Expression implements FunctionCall { ...@@ -146,8 +146,6 @@ public class Function extends Expression implements FunctionCall {
*/ */
public static final int H2VERSION = 231; public static final int H2VERSION = 231;
public static final int ROW_NUMBER = 300;
protected static final int VAR_ARGS = -1; protected static final int VAR_ARGS = -1;
private static final long PRECISION_UNKNOWN = -1; private static final long PRECISION_UNKNOWN = -1;
...@@ -470,9 +468,6 @@ public class Function extends Expression implements FunctionCall { ...@@ -470,9 +468,6 @@ public class Function extends Expression implements FunctionCall {
addFunctionWithNull("TABLE_DISTINCT", TABLE_DISTINCT, addFunctionWithNull("TABLE_DISTINCT", TABLE_DISTINCT,
VAR_ARGS, Value.RESULT_SET); VAR_ARGS, Value.RESULT_SET);
// pseudo function
addFunctionWithNull("ROW_NUMBER", ROW_NUMBER, 0, Value.LONG);
// ON DUPLICATE KEY VALUES function // ON DUPLICATE KEY VALUES function
addFunction("VALUES", VALUES, 1, Value.NULL, false, true, false); addFunction("VALUES", VALUES, 1, Value.NULL, false, true, false);
} }
......
...@@ -25,7 +25,7 @@ import org.h2.value.ValueArray; ...@@ -25,7 +25,7 @@ import org.h2.value.ValueArray;
import org.h2.value.ValueInt; import org.h2.value.ValueInt;
/** /**
* A base class for aggregates. * A base class for aggregates and window functions.
*/ */
public abstract class AbstractAggregate extends Expression { public abstract class AbstractAggregate extends Expression {
...@@ -65,7 +65,11 @@ public abstract class AbstractAggregate extends Expression { ...@@ -65,7 +65,11 @@ public abstract class AbstractAggregate extends Expression {
* FILTER condition * FILTER condition
*/ */
public void setFilterCondition(Expression filterCondition) { public void setFilterCondition(Expression filterCondition) {
this.filterCondition = filterCondition; if (isAggregate()) {
this.filterCondition = filterCondition;
} else {
throw DbException.getUnsupportedException("Window function");
}
} }
/** /**
...@@ -78,6 +82,23 @@ public abstract class AbstractAggregate extends Expression { ...@@ -78,6 +82,23 @@ public abstract class AbstractAggregate extends Expression {
this.over = over; this.over = over;
} }
/**
* Checks whether this expression is an aggregate function.
*
* @return true if this is an aggregate function (including aggregates with
* OVER clause), false if this is a window function
*/
public abstract boolean isAggregate();
/**
* Returns the sort order for OVER clause.
*
* @return the sort order for OVER clause
*/
SortOrder getOverOrderBySort() {
return overOrderBySort;
}
@Override @Override
public void mapColumns(ColumnResolver resolver, int level) { public void mapColumns(ColumnResolver resolver, int level) {
if (filterCondition != null) { if (filterCondition != null) {
......
...@@ -251,6 +251,11 @@ public class Aggregate extends AbstractAggregate { ...@@ -251,6 +251,11 @@ public class Aggregate extends AbstractAggregate {
return AGGREGATES.get(name); return AGGREGATES.get(name);
} }
@Override
public boolean isAggregate() {
return true;
}
/** /**
* Set the order for ARRAY_AGG() or GROUP_CONCAT() aggregate. * Set the order for ARRAY_AGG() or GROUP_CONCAT() aggregate.
* *
......
...@@ -40,6 +40,11 @@ public class JavaAggregate extends AbstractAggregate { ...@@ -40,6 +40,11 @@ public class JavaAggregate extends AbstractAggregate {
this.args = args; this.args = args;
} }
@Override
public boolean isAggregate() {
return true;
}
@Override @Override
public int getCost() { public int getCost() {
int cost = 5; int cost = 5;
......
...@@ -33,7 +33,7 @@ public final class Window { ...@@ -33,7 +33,7 @@ public final class Window {
* ORDER BY clause, or null * ORDER BY clause, or null
*/ */
static void appendOrderBy(StringBuilder builder, ArrayList<SelectOrderBy> orderBy) { static void appendOrderBy(StringBuilder builder, ArrayList<SelectOrderBy> orderBy) {
if (orderBy != null) { if (orderBy != null && !orderBy.isEmpty()) {
builder.append(" ORDER BY "); builder.append(" ORDER BY ");
for (int i = 0; i < orderBy.size(); i++) { for (int i = 0; i < orderBy.size(); i++) {
SelectOrderBy o = orderBy.get(i); SelectOrderBy o = orderBy.get(i);
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.expression.aggregate;
import org.h2.command.dml.Select;
import org.h2.engine.Session;
import org.h2.expression.ExpressionVisitor;
import org.h2.message.DbException;
import org.h2.value.Value;
import org.h2.value.ValueInt;
/**
* A window function.
*/
public class WindowFunction extends AbstractAggregate {
/**
* A type of a window function.
*/
public enum WindowFunctionType {
/**
* The type for ROW_NUMBER() window function.
*/
ROW_NUMBER,
/**
* The type for RANK() window function.
*/
RANK,
/**
* The type for DENSE_RANK() window function.
*/
DENSE_RANK,
;
/**
* Returns the type of window function with the specified name, or null.
*
* @param name
* name of a window function
* @return the type of window function, or null.
*/
public static WindowFunctionType get(String name) {
switch (name) {
case "ROW_NUMBER":
return WindowFunctionType.ROW_NUMBER;
case "RANK":
return RANK;
case "DENSE_RANK":
return WindowFunctionType.DENSE_RANK;
default:
return null;
}
}
}
private static class RowNumberData {
int number;
RowNumberData() {
}
}
private static final class RankData extends RowNumberData {
Value[] previousRow;
int previousNumber;
RankData() {
}
}
private WindowFunctionType type;
/**
* Creates new instance of a window function.
*
* @param type
* the type
* @param select
* the select statement
*/
public WindowFunction(WindowFunctionType type, Select select) {
super(select, false);
this.type = type;
}
@Override
public boolean isAggregate() {
return false;
}
@Override
protected void updateAggregate(Session session, Object aggregateData) {
switch (type) {
case ROW_NUMBER:
((RowNumberData) aggregateData).number++;
break;
case RANK:
case DENSE_RANK: {
RankData data = (RankData) aggregateData;
data.number++;
data.previousNumber++;
break;
}
default:
throw DbException.throwInternalError("type=" + type);
}
}
@Override
protected void updateGroupAggregates(Session session, int stage) {
// Nothing to do
}
@Override
protected int getNumExpressions() {
return 0;
}
@Override
protected void rememberExpressions(Session session, Value[] array) {
// Nothing to do
}
@Override
protected void updateFromExpressions(Session session, Object aggregateData, Value[] array) {
switch (type) {
case ROW_NUMBER:
((RowNumberData) aggregateData).number++;
break;
case RANK:
case DENSE_RANK: {
RankData data = (RankData) aggregateData;
data.number++;
Value[] previous = data.previousRow;
if (previous == null) {
data.previousNumber++;
} else {
if (getOverOrderBySort().compare(previous, array) != 0) {
if (type == WindowFunctionType.RANK) {
data.previousNumber = data.number;
} else /* DENSE_RANK */ {
data.previousNumber++;
}
}
}
data.previousRow = array;
break;
}
default:
throw DbException.throwInternalError("type=" + type);
}
}
@Override
protected Object createAggregateData() {
switch (type) {
case ROW_NUMBER:
return new RowNumberData();
case RANK:
case DENSE_RANK:
return new RankData();
default:
throw DbException.throwInternalError("type=" + type);
}
}
@Override
protected Value getAggregatedValue(Session session, Object aggregateData) {
switch (type) {
case ROW_NUMBER:
return ValueInt.get(((RowNumberData) aggregateData).number);
case RANK:
case DENSE_RANK:
return ValueInt.get(((RankData) aggregateData).previousNumber);
default:
throw DbException.throwInternalError("type=" + type);
}
}
@Override
public int getType() {
return Value.INT;
}
@Override
public int getScale() {
return 0;
}
@Override
public long getPrecision() {
return ValueInt.PRECISION;
}
@Override
public int getDisplaySize() {
return ValueInt.DISPLAY_SIZE;
}
@Override
public String getSQL() {
String text;
switch (type) {
case ROW_NUMBER:
text = "ROW_NUMBER";
break;
case RANK:
text = "RANK";
break;
case DENSE_RANK:
text = "DENSE_RANK";
break;
default:
throw DbException.throwInternalError("type=" + type);
}
StringBuilder builder = new StringBuilder().append(text).append("()");
return appendTailConditions(builder).toString();
}
@Override
public boolean isEverything(ExpressionVisitor visitor) {
if (visitor.getType() == ExpressionVisitor.OPTIMIZABLE_MIN_MAX_COUNT_ALL) {
return false;
}
return true;
}
@Override
public int getCost() {
int cost = 1;
return cost;
}
}
...@@ -9,6 +9,6 @@ Initial Developer: H2 Group ...@@ -9,6 +9,6 @@ Initial Developer: H2 Group
Javadoc package documentation Javadoc package documentation
</title></head><body style="font: 9pt/130% Tahoma, Arial, Helvetica, sans-serif; font-weight: normal;"><p> </title></head><body style="font: 9pt/130% Tahoma, Arial, Helvetica, sans-serif; font-weight: normal;"><p>
Aggregate functions. Aggregate and window functions.
</p></body></html> </p></body></html>
\ No newline at end of file
...@@ -136,9 +136,9 @@ public class TestScript extends TestDb { ...@@ -136,9 +136,9 @@ public class TestScript extends TestDb {
for (String s : new String[] { "help" }) { for (String s : new String[] { "help" }) {
testScript("other/" + s + ".sql"); testScript("other/" + s + ".sql");
} }
for (String s : new String[] { "avg", "bit-and", "bit-or", "count", "envelope", for (String s : new String[] { "array-agg", "avg", "bit-and", "bit-or", "count", "envelope",
"group-concat", "max", "median", "min", "mode", "selectivity", "stddev-pop", "group-concat", "max", "median", "min", "mode", "selectivity", "stddev-pop",
"stddev-samp", "sum", "var-pop", "var-samp", "array-agg" }) { "stddev-samp", "sum", "var-pop", "var-samp" }) {
testScript("functions/aggregate/" + s + ".sql"); testScript("functions/aggregate/" + s + ".sql");
} }
for (String s : new String[] { "abs", "acos", "asin", "atan", "atan2", for (String s : new String[] { "abs", "acos", "asin", "atan", "atan2",
...@@ -179,6 +179,9 @@ public class TestScript extends TestDb { ...@@ -179,6 +179,9 @@ public class TestScript extends TestDb {
"parsedatetime", "quarter", "second", "truncate", "week", "year", "date_trunc" }) { "parsedatetime", "quarter", "second", "truncate", "week", "year", "date_trunc" }) {
testScript("functions/timeanddate/" + s + ".sql"); testScript("functions/timeanddate/" + s + ".sql");
} }
for (String s : new String[] { "row_number" }) {
testScript("functions/window/" + s + ".sql");
}
deleteDb("script"); deleteDb("script");
System.out.flush(); System.out.flush();
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
create table test as (select char(x) as str from system_range(48,90)); create table test as (select char(x) as str from system_range(48,90));
> ok > ok
select row_number() over () as rnum, str from test where str = 'A'; select rownum() as rnum, str from test where str = 'A';
> RNUM STR > RNUM STR
> ---- --- > ---- ---
> 1 A > 1 A
......
-- Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
CREATE TABLE TEST (ID INT PRIMARY KEY, CATEGORY INT, VALUE INT);
> ok
INSERT INTO TEST VALUES
(1, 1, 11),
(2, 1, 12),
(3, 1, 13),
(4, 2, 21),
(5, 2, 22),
(6, 3, 31),
(7, 3, 32),
(8, 3, 33);
> update count: 8
SELECT *,
ROW_NUMBER() OVER () RN, RANK() OVER () RK, DENSE_RANK() OVER () DR,
ROW_NUMBER() OVER (ORDER BY ID) RNO, RANK() OVER (ORDER BY ID) RKO, DENSE_RANK() OVER (ORDER BY ID) DRO
FROM TEST;
> ID CATEGORY VALUE RN RK DR RNO RKO DRO
> -- -------- ----- -- -- -- --- --- ---
> 1 1 11 1 1 1 1 1 1
> 2 1 12 2 1 1 2 2 2
> 3 1 13 3 1 1 3 3 3
> 4 2 21 4 1 1 4 4 4
> 5 2 22 5 1 1 5 5 5
> 6 3 31 6 1 1 6 6 6
> 7 3 32 7 1 1 7 7 7
> 8 3 33 8 1 1 8 8 8
> rows (ordered): 8
SELECT *,
ROW_NUMBER() OVER (ORDER BY CATEGORY) RN, RANK() OVER (ORDER BY CATEGORY) RK, DENSE_RANK() OVER (ORDER BY CATEGORY) DR
FROM TEST;
> ID CATEGORY VALUE RN RK DR
> -- -------- ----- -- -- --
> 1 1 11 1 1 1
> 2 1 12 2 1 1
> 3 1 13 3 1 1
> 4 2 21 4 4 2
> 5 2 22 5 4 2
> 6 3 31 6 6 3
> 7 3 32 7 6 3
> 8 3 33 8 6 3
> rows (ordered): 8
SELECT *,
ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID) RN,
RANK() OVER (PARTITION BY CATEGORY ORDER BY ID) RK,
DENSE_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID) DR
FROM TEST;
> ID CATEGORY VALUE RN RK DR
> -- -------- ----- -- -- --
> 1 1 11 1 1 1
> 2 1 12 2 2 2
> 3 1 13 3 3 3
> 4 2 21 1 1 1
> 5 2 22 2 2 2
> 6 3 31 1 1 1
> 7 3 32 2 2 2
> 8 3 33 3 3 3
> rows (ordered): 8
SELECT
ROW_NUMBER() OVER () RN, RANK() OVER () RK, DENSE_RANK() OVER () DR
FROM TEST GROUP BY CATEGORY;
> RN RK DR
> -- -- --
> 1 1 1
> 2 1 1
> 3 1 1
> rows: 3
DROP TABLE TEST;
> ok
...@@ -281,21 +281,21 @@ create table test(id int primary key, name varchar(255), row_number int); ...@@ -281,21 +281,21 @@ create table test(id int primary key, name varchar(255), row_number int);
insert into test values(1, 'hello', 10), (2, 'world', 20); insert into test values(1, 'hello', 10), (2, 'world', 20);
> update count: 2 > update count: 2
select row_number() over(), id, name from test order by id; select rownum(), id, name from test order by id;
> ROWNUM() ID NAME > ROWNUM() ID NAME
> -------- -- ----- > -------- -- -----
> 1 1 hello > 1 1 hello
> 2 2 world > 2 2 world
> rows (ordered): 2 > rows (ordered): 2
select row_number() over(), id, name from test order by name; select rownum(), id, name from test order by name;
> ROWNUM() ID NAME > ROWNUM() ID NAME
> -------- -- ----- > -------- -- -----
> 1 1 hello > 1 1 hello
> 2 2 world > 2 2 world
> rows (ordered): 2 > rows (ordered): 2
select row_number() over(), id, name from test order by name desc; select rownum(), id, name from test order by name desc;
> ROWNUM() ID NAME > ROWNUM() ID NAME
> -------- -- ----- > -------- -- -----
> 2 2 world > 2 2 world
......
...@@ -90,6 +90,8 @@ public class GenerateDoc { ...@@ -90,6 +90,8 @@ public class GenerateDoc {
help + "= 'Functions (Time and Date)' ORDER BY ID", true, false); help + "= 'Functions (Time and Date)' ORDER BY ID", true, false);
map("functionsSystem", map("functionsSystem",
help + "= 'Functions (System)' ORDER BY ID", true, false); help + "= 'Functions (System)' ORDER BY ID", true, false);
map("functionsWindow",
help + "= 'Functions (Window)' ORDER BY ID", true, false);
map("dataTypes", map("dataTypes",
help + "LIKE 'Data Types%' ORDER BY SECTION, ID", true, true); help + "LIKE 'Data Types%' ORDER BY SECTION, ID", true, true);
map("intervalDataTypes", map("intervalDataTypes",
......
...@@ -796,3 +796,4 @@ interior envelopes multilinestring multipoint packed exterior normalization awkw ...@@ -796,3 +796,4 @@ interior envelopes multilinestring multipoint packed exterior normalization awkw
xym normalizes coord setz xyzm geometrycollection multipolygon mixup rings polygons rejection finite xym normalizes coord setz xyzm geometrycollection multipolygon mixup rings polygons rejection finite
pointzm pointz pointm dimensionality redefine forum measures pointzm pointz pointm dimensionality redefine forum measures
mpg casted pzm mls constrained subtypes complains mpg casted pzm mls constrained subtypes complains
ranks rno dro rko precede
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论