Unverified 提交 4f23bb18 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1456 from katzyn/window

Add experimental implementation of remaining types of window frames
...@@ -2550,11 +2550,8 @@ They also may require a lot of memory for large queries. ...@@ -2550,11 +2550,8 @@ They also may require a lot of memory for large queries.
" "
"Other Grammar","Window frame"," "Other Grammar","Window frame","
[RANGE BETWEEN { ROWS|RANGE|GROUP
UNBOUNDED PRECEDING AND CURRENT ROW {windowFramePreceding|BETWEEN windowFrameBound AND windowFrameBound}
|CURRENT ROW AND UNBOUNDED FOLLOWING
|UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
}]
[EXCLUDE {CURRENT ROW|GROUP|TIES|NO OTHERS}] [EXCLUDE {CURRENT ROW|GROUP|TIES|NO OTHERS}]
"," ","
A window frame clause. A window frame clause.
...@@ -2563,6 +2560,30 @@ Is currently supported only in aggregates and FIRST_VALUE(), LAST_VALUE(), and N ...@@ -2563,6 +2560,30 @@ Is currently supported only in aggregates and FIRST_VALUE(), LAST_VALUE(), and N
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP
" "
"Other Grammar","Window frame preceding","
UNBOUNDED PRECEDING|value PRECEDING|CURRENT ROW
","
A window frame preceding clause.
If value is specified it should be non-negative value or parameter.
","
UNBOUNDED PRECEDING
1 PRECEDING
CURRENT ROW
"
"Other Grammar","Window frame bound","
UNBOUNDED PRECEDING|value PRECEDING|CURRENT ROW
|value FOLLOWING|UNBOUNDED FOLLOWING
","
A window frame bound clause.
If value is specified it should be non-negative value or parameter.
","
UNBOUNDED PRECEDING
UNBOUNDED FOLLOWING
1 FOLLOWING
CURRENT ROW
"
"Other Grammar","Term"," "Other Grammar","Term","
value value
| columnName | columnName
......
...@@ -176,8 +176,10 @@ import org.h2.expression.aggregate.Aggregate.AggregateType; ...@@ -176,8 +176,10 @@ 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.WindowFrame; import org.h2.expression.aggregate.WindowFrame;
import org.h2.expression.aggregate.WindowFrame.SimpleExtent; import org.h2.expression.aggregate.WindowFrameBound;
import org.h2.expression.aggregate.WindowFrame.WindowFrameExclusion; import org.h2.expression.aggregate.WindowFrameBoundType;
import org.h2.expression.aggregate.WindowFrameExclusion;
import org.h2.expression.aggregate.WindowFrameUnits;
import org.h2.expression.aggregate.WindowFunction; import org.h2.expression.aggregate.WindowFunction;
import org.h2.expression.aggregate.WindowFunction.WindowFunctionType; import org.h2.expression.aggregate.WindowFunction.WindowFunctionType;
import org.h2.index.Index; import org.h2.index.Index;
...@@ -3074,8 +3076,7 @@ public class Parser { ...@@ -3074,8 +3076,7 @@ public class Parser {
frame = readWindowFrame(); frame = readWindowFrame();
break; break;
default: default:
frame = new WindowFrame(SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW, frame = null;
WindowFrameExclusion.EXCLUDE_NO_OTHERS);
} }
} else { } else {
frame = readWindowFrame(); frame = readWindowFrame();
...@@ -3092,46 +3093,89 @@ public class Parser { ...@@ -3092,46 +3093,89 @@ public class Parser {
} }
private WindowFrame readWindowFrame() { private WindowFrame readWindowFrame() {
SimpleExtent extent; WindowFrameUnits units;
if (readIf("ROWS")) {
units = WindowFrameUnits.ROWS;
} else if (readIf("RANGE")) {
units = WindowFrameUnits.RANGE;
} else if (readIf("GROUPS")) {
units = WindowFrameUnits.GROUPS;
} else {
return null;
}
WindowFrameBound starting, following;
if (readIf("BETWEEN")) {
starting = readWindowFrameRange();
read("AND");
following = readWindowFrameRange();
} else {
starting = readWindowFrameStarting();
following = null;
}
int idx = lastParseIndex;
WindowFrameExclusion exclusion = WindowFrameExclusion.EXCLUDE_NO_OTHERS; WindowFrameExclusion exclusion = WindowFrameExclusion.EXCLUDE_NO_OTHERS;
if (readIf("RANGE")) { if (readIf("EXCLUDE")) {
read("BETWEEN"); if (readIf("CURRENT")) {
if (readIf("UNBOUNDED")) {
read("PRECEDING");
read("AND");
if (readIf("CURRENT")) {
read("ROW");
extent = SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW;
} else {
read("UNBOUNDED");
read("FOLLOWING");
extent = SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_UNBOUNDED_FOLLOWING;
}
} else {
read("CURRENT");
read("ROW"); read("ROW");
read("AND"); exclusion = WindowFrameExclusion.EXCLUDE_CURRENT_ROW;
read("UNBOUNDED"); } else if (readIf(GROUP)) {
read("FOLLOWING"); exclusion = WindowFrameExclusion.EXCLUDE_GROUP;
extent = SimpleExtent.RANGE_BETWEEN_CURRENT_ROW_AND_UNBOUNDED_FOLLOWING; } else if (readIf("TIES")) {
} exclusion = WindowFrameExclusion.EXCLUDE_TIES;
if (readIf("EXCLUDE")) { } else {
if (readIf("CURRENT")) { read("NO");
read("ROW"); read("OTHERS");
exclusion = WindowFrameExclusion.EXCLUDE_CURRENT_ROW;
} else if (readIf(GROUP)) {
exclusion = WindowFrameExclusion.EXCLUDE_GROUP;
} else if (readIf("TIES")) {
exclusion = WindowFrameExclusion.EXCLUDE_TIES;
} else {
read("NO");
read("OTHERS");
}
} }
} else {
extent = SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW;
} }
return new WindowFrame(extent, exclusion); WindowFrame frame = new WindowFrame(units, starting, following, exclusion);
if (!frame.isValid()) {
throw DbException.getSyntaxError(sqlCommand, idx);
}
return frame;
}
private WindowFrameBound readWindowFrameStarting() {
if (readIf("UNBOUNDED")) {
read("PRECEDING");
return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_PRECEDING, null);
}
if (readIf("CURRENT")) {
read("ROW");
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
}
Expression value = readValueOrParameter();
read("PRECEDING");
return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value);
}
private WindowFrameBound readWindowFrameRange() {
if (readIf("UNBOUNDED")) {
if (readIf("PRECEDING")) {
return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_PRECEDING, null);
}
read("FOLLOWING");
return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_FOLLOWING, null);
}
if (readIf("CURRENT")) {
read("ROW");
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
}
Expression value = readValueOrParameter();
if (readIf("PRECEDING")) {
return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value);
}
read("FOLLOWING");
return new WindowFrameBound(WindowFrameBoundType.FOLLOWING, value);
}
private Expression readValueOrParameter() {
int index = parseIndex;
Expression value = readExpression();
if (!(value instanceof ValueExpression) && !(value instanceof Parameter)) {
parseIndex = index;
throw getSyntaxError();
}
return value;
} }
private AggregateType getAggregateType(String name) { private AggregateType getAggregateType(String name) {
......
...@@ -445,7 +445,7 @@ public abstract class AbstractAggregate extends Expression { ...@@ -445,7 +445,7 @@ public abstract class AbstractAggregate extends Expression {
int size = ordered.size(); int size = ordered.size();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
Object aggregateData = createAggregateData(); Object aggregateData = createAggregateData();
for (Iterator<Value[]> iter = frame.iterator(ordered, getOverOrderBySort(), i, false); iter for (Iterator<Value[]> iter = frame.iterator(session, ordered, getOverOrderBySort(), i, false); iter
.hasNext();) { .hasNext();) {
updateFromExpressions(session, aggregateData, iter.next()); updateFromExpressions(session, aggregateData, iter.next());
} }
......
...@@ -64,6 +64,11 @@ public final class Window { ...@@ -64,6 +64,11 @@ public final class Window {
public Window(ArrayList<Expression> partitionBy, ArrayList<SelectOrderBy> orderBy, WindowFrame frame) { public Window(ArrayList<Expression> partitionBy, ArrayList<SelectOrderBy> orderBy, WindowFrame frame) {
this.partitionBy = partitionBy; this.partitionBy = partitionBy;
this.orderBy = orderBy; this.orderBy = orderBy;
if (frame == null) {
frame = new WindowFrame(WindowFrameUnits.RANGE,
new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_PRECEDING, null), null,
WindowFrameExclusion.EXCLUDE_NO_OTHERS);
}
this.frame = frame; this.frame = frame;
} }
......
...@@ -11,6 +11,10 @@ import java.util.Collections; ...@@ -11,6 +11,10 @@ import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import org.h2.engine.Session;
import org.h2.expression.BinaryOperation;
import org.h2.expression.BinaryOperation.OpType;
import org.h2.expression.ValueExpression;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -20,92 +24,6 @@ import org.h2.value.Value; ...@@ -20,92 +24,6 @@ import org.h2.value.Value;
*/ */
public final class WindowFrame { public final class WindowFrame {
/**
* Simple extent.
*/
public enum SimpleExtent {
/**
* RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW frame specification.
*/
RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW("RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"),
/**
* RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING frame specification.
*/
RANGE_BETWEEN_CURRENT_ROW_AND_UNBOUNDED_FOLLOWING("RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING"),
/**
* RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING frame
* specification.
*/
RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_UNBOUNDED_FOLLOWING(
"RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"),
;
private final String sql;
private SimpleExtent(String sql) {
this.sql = sql;
}
/**
* Returns SQL representation.
*
* @return SQL representation.
* @see org.h2.expression.Expression#getSQL()
*/
public String getSQL() {
return sql;
}
}
/**
* Window frame exclusion clause.
*/
public enum WindowFrameExclusion {
/**
* EXCLUDE CURRENT ROW exclusion clause.
*/
EXCLUDE_CURRENT_ROW("EXCLUDE CURRENT ROW"),
/**
* EXCLUDE GROUP exclusion clause.
*/
EXCLUDE_GROUP("EXCLUDE GROUP"),
/**
* EXCLUDE TIES exclusion clause.
*/
EXCLUDE_TIES("EXCLUDE TIES"),
/**
* EXCLUDE NO OTHERS exclusion clause.
*/
EXCLUDE_NO_OTHERS("EXCLUDE NO OTHERS"),
;
private final String sql;
private WindowFrameExclusion(String sql) {
this.sql = sql;
}
/**
* Returns SQL representation.
*
* @return SQL representation.
* @see org.h2.expression.Expression#getSQL()
*/
public String getSQL() {
return sql;
}
}
private abstract class Itr implements Iterator<Value[]> { private abstract class Itr implements Iterator<Value[]> {
final ArrayList<Value[]> orderedRows; final ArrayList<Value[]> orderedRows;
...@@ -231,30 +149,100 @@ public final class WindowFrame { ...@@ -231,30 +149,100 @@ public final class WindowFrame {
} }
private final SimpleExtent extent; private final WindowFrameUnits units;
private final WindowFrameBound starting;
private final WindowFrameBound following;
private final WindowFrameExclusion exclusion; private final WindowFrameExclusion exclusion;
private static int toGroupStart(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int offset, int minOffset) {
Value[] row = orderedRows.get(offset);
while (offset > minOffset && sortOrder.compare(row, orderedRows.get(offset - 1)) == 0) {
offset--;
}
return offset;
}
private static int toGroupEnd(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int offset, int maxOffset) {
Value[] row = orderedRows.get(offset);
while (offset < maxOffset && sortOrder.compare(row, orderedRows.get(offset + 1)) == 0) {
offset++;
}
return offset;
}
private static int getIntOffset(WindowFrameBound bound, Session session) {
int value = bound.getValue().getValue(session).getInt();
if (value < 0) {
throw DbException.getInvalidValueException("unsigned", value);
}
return value;
}
private static Value[] getCompareRow(Session session, ArrayList<Value[]> orderedRows, SortOrder sortOrder,
int currentRow, WindowFrameBound bound, boolean add) {
int sortIndex = sortOrder.getQueryColumnIndexes()[0];
OpType opType = add ^ (sortOrder.getSortTypes()[0] & SortOrder.DESCENDING) != 0 ? OpType.PLUS : OpType.MINUS;
Value[] row = orderedRows.get(currentRow);
Value[] newRow = row.clone();
newRow[sortIndex] = new BinaryOperation(opType, //
ValueExpression.get(row[sortIndex]), ValueExpression.get(getValueOffset(bound, session))) //
.optimize(session).getValue(session);
return newRow;
}
private static Value getValueOffset(WindowFrameBound bound, Session session) {
Value value = bound.getValue().getValue(session);
if (value.getSignum() < 0) {
throw DbException.getInvalidValueException("unsigned", value.getTraceSQL());
}
return value;
}
/** /**
* Creates new instance of window frame clause. * Creates new instance of window frame clause.
* *
* @param extent * @param units
* window frame extent * units
* @param starting
* starting clause
* @param following
* following clause
* @param exclusion * @param exclusion
* window frame exclusion * exclusion clause
*/ */
public WindowFrame(SimpleExtent extent, WindowFrameExclusion exclusion) { public WindowFrame(WindowFrameUnits units, WindowFrameBound starting, WindowFrameBound following,
this.extent = extent; WindowFrameExclusion exclusion) {
this.units = units;
this.starting = starting;
if (following != null && following.getType() == WindowFrameBoundType.CURRENT_ROW) {
following = null;
}
this.following = following;
this.exclusion = exclusion; this.exclusion = exclusion;
} }
/**
* Checks validity of this frame.
*
* @return whether bounds of this frame valid
*/
public boolean isValid() {
WindowFrameBoundType s = starting.getType(),
f = following != null ? following.getType() : WindowFrameBoundType.CURRENT_ROW;
return s != WindowFrameBoundType.UNBOUNDED_FOLLOWING && f != WindowFrameBoundType.UNBOUNDED_PRECEDING
&& s.compareTo(f) <= 0;
}
/** /**
* Returns whether window frame specification can be omitted. * Returns whether window frame specification can be omitted.
* *
* @return whether window frame specification can be omitted * @return whether window frame specification can be omitted
*/ */
public boolean isDefault() { public boolean isDefault() {
return extent == SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW return starting.getType() == WindowFrameBoundType.UNBOUNDED_PRECEDING && following == null
&& exclusion == WindowFrameExclusion.EXCLUDE_NO_OTHERS; && exclusion == WindowFrameExclusion.EXCLUDE_NO_OTHERS;
} }
...@@ -265,13 +253,16 @@ public final class WindowFrame { ...@@ -265,13 +253,16 @@ public final class WindowFrame {
* @return whether window frame specification contains all rows in partition * @return whether window frame specification contains all rows in partition
*/ */
public boolean isFullPartition() { public boolean isFullPartition() {
return extent == SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_UNBOUNDED_FOLLOWING return starting.getType() == WindowFrameBoundType.UNBOUNDED_PRECEDING && following != null
&& following.getType() == WindowFrameBoundType.UNBOUNDED_FOLLOWING
&& exclusion == WindowFrameExclusion.EXCLUDE_NO_OTHERS; && exclusion == WindowFrameExclusion.EXCLUDE_NO_OTHERS;
} }
/** /**
* Returns iterator. * Returns iterator.
* *
* @param session
* the session
* @param orderedRows * @param orderedRows
* ordered rows * ordered rows
* @param sortOrder * @param sortOrder
...@@ -280,27 +271,26 @@ public final class WindowFrame { ...@@ -280,27 +271,26 @@ public final class WindowFrame {
* index of the current row * index of the current row
* @param reverse * @param reverse
* whether iterator should iterate in reverse order * whether iterator should iterate in reverse order
*
* @return iterator * @return iterator
*/ */
public Iterator<Value[]> iterator(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow, public Iterator<Value[]> iterator(Session session, ArrayList<Value[]> orderedRows, SortOrder sortOrder,
boolean reverse) { int currentRow, boolean reverse) {
int startIndex = getIndex(session, orderedRows, sortOrder, currentRow, starting, false);
int endIndex = following != null ? getIndex(session, orderedRows, sortOrder, currentRow, following, true)
: currentRow;
if (endIndex < startIndex) {
return Collections.emptyIterator();
}
int size = orderedRows.size(); int size = orderedRows.size();
final int startIndex, endIndex; if (startIndex >= size || endIndex < 0) {
switch (extent) { return Collections.emptyIterator();
case RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW: }
startIndex = 0; if (startIndex < 0) {
endIndex = currentRow;
break;
case RANGE_BETWEEN_CURRENT_ROW_AND_UNBOUNDED_FOLLOWING:
startIndex = currentRow;
endIndex = size - 1;
break;
case RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_UNBOUNDED_FOLLOWING:
startIndex = 0; startIndex = 0;
}
if (endIndex >= size) {
endIndex = size - 1; endIndex = size - 1;
break;
default:
throw DbException.getUnsupportedException("window frame extent =" + extent);
} }
if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) { if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
return complexIterator(orderedRows, sortOrder, currentRow, startIndex, endIndex, reverse); return complexIterator(orderedRows, sortOrder, currentRow, startIndex, endIndex, reverse);
...@@ -309,6 +299,148 @@ public final class WindowFrame { ...@@ -309,6 +299,148 @@ public final class WindowFrame {
: new PlainItr(orderedRows, startIndex, endIndex); : new PlainItr(orderedRows, startIndex, endIndex);
} }
private int getIndex(Session session, ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow,
WindowFrameBound bound, boolean forFollowing) {
int size = orderedRows.size();
int last = size - 1;
int index;
switch (bound.getType()) {
case UNBOUNDED_PRECEDING:
index = -1;
break;
case PRECEDING:
switch (units) {
case ROWS: {
int value = getIntOffset(bound, session);
index = value > currentRow ? -1 : currentRow - value;
break;
}
case GROUPS: {
int value = getIntOffset(bound, session);
if (!forFollowing) {
index = toGroupStart(orderedRows, sortOrder, currentRow, 0);
while (value > 0 && index > 0) {
value--;
index = toGroupStart(orderedRows, sortOrder, index - 1, 0);
}
if (value > 0) {
index = -1;
}
} else {
if (value == 0) {
index = toGroupEnd(orderedRows, sortOrder, currentRow, last);
} else {
index = currentRow;
while (value > 0 && index >= 0) {
value--;
index = toGroupStart(orderedRows, sortOrder, index, 0) - 1;
}
}
}
break;
}
case RANGE: {
index = currentRow;
Value[] row = getCompareRow(session, orderedRows, sortOrder, index, bound, false);
index = Collections.binarySearch(orderedRows, row, sortOrder);
if (index >= 0) {
if (!forFollowing) {
while (index > 0 && sortOrder.compare(row, orderedRows.get(index - 1)) == 0) {
index--;
}
} else {
while (index < last && sortOrder.compare(row, orderedRows.get(index + 1)) == 0) {
index++;
}
}
} else {
index = ~index;
if (!forFollowing) {
if (index == 0) {
index = -1;
}
} else {
index--;
}
}
break;
}
default:
throw DbException.getUnsupportedException("units=" + units);
}
break;
case CURRENT_ROW:
index = currentRow;
break;
case FOLLOWING:
switch (units) {
case ROWS: {
int value = getIntOffset(bound, session);
int rem = last - currentRow;
index = value > rem ? size : currentRow + value;
break;
}
case GROUPS: {
int value = getIntOffset(bound, session);
if (forFollowing) {
index = toGroupEnd(orderedRows, sortOrder, currentRow, last);
while (value > 0 && index < last) {
value--;
index = toGroupEnd(orderedRows, sortOrder, index + 1, last);
}
if (value > 0) {
index = size;
}
} else {
if (value == 0) {
index = toGroupStart(orderedRows, sortOrder, currentRow, 0);
} else {
index = currentRow;
while (value > 0 && index <= last) {
value--;
index = toGroupEnd(orderedRows, sortOrder, index, last) + 1;
}
}
}
break;
}
case RANGE: {
index = currentRow;
Value[] row = getCompareRow(session, orderedRows, sortOrder, index, bound, true);
index = Collections.binarySearch(orderedRows, row, sortOrder);
if (index >= 0) {
if (forFollowing) {
while (index < last && sortOrder.compare(row, orderedRows.get(index + 1)) == 0) {
index++;
}
} else {
while (index > 0 && sortOrder.compare(row, orderedRows.get(index - 1)) == 0) {
index--;
}
}
} else {
index = ~index;
if (forFollowing) {
if (index != size) {
index--;
}
}
}
break;
}
default:
throw DbException.getUnsupportedException("units=" + units);
}
break;
case UNBOUNDED_FOLLOWING:
index = size;
break;
default:
throw DbException.getUnsupportedException("window frame bound type=" + bound.getType());
}
return index;
}
private Iterator<Value[]> complexIterator(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow, private Iterator<Value[]> complexIterator(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow,
int startIndex, int endIndex, boolean reverse) { int startIndex, int endIndex, boolean reverse) {
int size = orderedRows.size(); int size = orderedRows.size();
...@@ -320,15 +452,8 @@ public final class WindowFrame { ...@@ -320,15 +452,8 @@ public final class WindowFrame {
break; break;
case EXCLUDE_GROUP: case EXCLUDE_GROUP:
case EXCLUDE_TIES: { case EXCLUDE_TIES: {
int exStart = currentRow; int exStart = toGroupStart(orderedRows, sortOrder, currentRow, startIndex);
Value[] row = orderedRows.get(currentRow); int exEnd = toGroupEnd(orderedRows, sortOrder, currentRow, endIndex);
while (exStart > startIndex && sortOrder.compare(row, orderedRows.get(exStart - 1)) == 0) {
exStart--;
}
int exEnd = currentRow;
while (exEnd < endIndex && sortOrder.compare(row, orderedRows.get(exEnd + 1)) == 0) {
exEnd++;
}
set.clear(exStart, exEnd + 1); set.clear(exStart, exEnd + 1);
if (exclusion == WindowFrameExclusion.EXCLUDE_TIES) { if (exclusion == WindowFrameExclusion.EXCLUDE_TIES) {
set.set(currentRow); set.set(currentRow);
...@@ -350,11 +475,17 @@ public final class WindowFrame { ...@@ -350,11 +475,17 @@ public final class WindowFrame {
* @see org.h2.expression.Expression#getSQL() * @see org.h2.expression.Expression#getSQL()
*/ */
public String getSQL() { public String getSQL() {
String sql = extent.getSQL(); StringBuilder builder = new StringBuilder();
builder.append(units.getSQL());
if (following == null) {
builder.append(' ').append(starting.getSQL(false));
} else {
builder.append(" BETWEEN ").append(starting.getSQL(false)).append(" AND ").append(following.getSQL(true));
}
if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) { if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
sql = sql + ' ' + exclusion.getSQL(); builder.append(' ').append(exclusion.getSQL());
} }
return sql; return builder.toString();
} }
} }
/*
* 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.expression.Expression;
/**
* Window frame bound.
*/
public class WindowFrameBound {
private final WindowFrameBoundType type;
private final Expression value;
/**
* Creates new instance of window frame bound.
*
* @param type
* bound type
* @param value
* bound value, if any
*/
public WindowFrameBound(WindowFrameBoundType type, Expression value) {
this.type = type;
if (type == WindowFrameBoundType.PRECEDING || type == WindowFrameBoundType.FOLLOWING) {
this.value = value;
} else {
this.value = null;
}
}
/**
* Returns the type
*
* @return the type
*/
public WindowFrameBoundType getType() {
return type;
}
/**
* Returns the value.
*
* @return the value
*/
public Expression getValue() {
return value;
}
/**
* Returns SQL representation.
*
* @param following
* if false return SQL for starting clause, if true return SQL
* for following clause
* @return SQL representation.
* @see Expression#getSQL()
*/
public String getSQL(boolean following) {
if (type == WindowFrameBoundType.PRECEDING || type == WindowFrameBoundType.FOLLOWING) {
return value.getSQL() + ' ' + type.getSQL();
}
return type.getSQL();
}
}
/*
* 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;
/**
* Window frame bound type.
*/
public enum WindowFrameBoundType {
/**
* UNBOUNDED PRECEDING clause.
*/
UNBOUNDED_PRECEDING("UNBOUNDED PRECEDING"),
/**
* PRECEDING clause.
*/
PRECEDING("PRECEDING"),
/**
* CURRENT_ROW clause.
*/
CURRENT_ROW("CURRENT_ROW"),
/**
* FOLLOWING clause.
*/
FOLLOWING("FOLLOWING"),
/**
* UNBOUNDED FOLLOWING clause.
*/
UNBOUNDED_FOLLOWING("UNBOUNDED FOLLOWING");
private final String sql;
private WindowFrameBoundType(String sql) {
this.sql = sql;
}
/**
* Returns SQL representation.
*
* @return SQL representation.
* @see org.h2.expression.Expression#getSQL()
*/
public String getSQL() {
return sql;
}
}
/*
* 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;
/**
* Window frame exclusion clause.
*/
public enum WindowFrameExclusion {
/**
* EXCLUDE CURRENT ROW exclusion clause.
*/
EXCLUDE_CURRENT_ROW("EXCLUDE CURRENT ROW"),
/**
* EXCLUDE GROUP exclusion clause.
*/
EXCLUDE_GROUP("EXCLUDE GROUP"),
/**
* EXCLUDE TIES exclusion clause.
*/
EXCLUDE_TIES("EXCLUDE TIES"),
/**
* EXCLUDE NO OTHERS exclusion clause.
*/
EXCLUDE_NO_OTHERS("EXCLUDE NO OTHERS"),
;
private final String sql;
private WindowFrameExclusion(String sql) {
this.sql = sql;
}
/**
* Returns SQL representation.
*
* @return SQL representation.
* @see org.h2.expression.Expression#getSQL()
*/
public String getSQL() {
return sql;
}
}
/*
* 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;
/**
* Window frame units.
*/
public enum WindowFrameUnits {
/**
* ROWS unit.
*/
ROWS,
/**
* RANGE unit.
*/
RANGE,
/**
* GROUPS unit.
*/
GROUPS,
;
/**
* Returns SQL representation.
*
* @return SQL representation.
* @see org.h2.expression.Expression#getSQL()
*/
public String getSQL() {
return name();
}
}
...@@ -321,11 +321,11 @@ public class WindowFunction extends AbstractAggregate { ...@@ -321,11 +321,11 @@ public class WindowFunction extends AbstractAggregate {
Value v; Value v;
switch (type) { switch (type) {
case FIRST_VALUE: { case FIRST_VALUE: {
v = getNthValue(frame.iterator(ordered, getOverOrderBySort(), i, false), 0, ignoreNulls); v = getNthValue(frame.iterator(session, ordered, getOverOrderBySort(), i, false), 0, ignoreNulls);
break; break;
} }
case LAST_VALUE: case LAST_VALUE:
v = getNthValue(frame.iterator(ordered, getOverOrderBySort(), i, true), 0, ignoreNulls); v = getNthValue(frame.iterator(session, ordered, getOverOrderBySort(), i, true), 0, ignoreNulls);
break; break;
case NTH_VALUE: { case NTH_VALUE: {
int n = row[1].getInt(); int n = row[1].getInt();
...@@ -333,7 +333,7 @@ public class WindowFunction extends AbstractAggregate { ...@@ -333,7 +333,7 @@ public class WindowFunction extends AbstractAggregate {
throw DbException.getInvalidValueException("nth row", n); throw DbException.getInvalidValueException("nth row", n);
} }
n--; n--;
Iterator<Value[]> iter = frame.iterator(ordered, getOverOrderBySort(), i, fromLast); Iterator<Value[]> iter = frame.iterator(session, ordered, getOverOrderBySort(), i, fromLast);
v = getNthValue(iter, n, ignoreNulls); v = getNthValue(iter, n, ignoreNulls);
break; break;
} }
......
...@@ -275,3 +275,160 @@ SELECT ...@@ -275,3 +275,160 @@ SELECT
DROP TABLE TEST; DROP TABLE TEST;
> ok > ok
CREATE TABLE TEST(ID INT, VALUE INT);
> ok
INSERT INTO TEST VALUES
(1, 1),
(2, 1),
(3, 5),
(4, 8),
(5, 8),
(6, 8),
(7, 9),
(8, 9);
> update count: 8
SELECT *,
ARRAY_AGG(ID) OVER (ORDER BY VALUE ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) R_ID,
ARRAY_AGG(VALUE) OVER (ORDER BY VALUE ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) R_V,
ARRAY_AGG(ID) OVER (ORDER BY VALUE RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) V_ID,
ARRAY_AGG(VALUE) OVER (ORDER BY VALUE RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) V_V,
ARRAY_AGG(VALUE) OVER (ORDER BY VALUE DESC RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) V_V_R,
ARRAY_AGG(ID) OVER (ORDER BY VALUE GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) G_ID,
ARRAY_AGG(VALUE) OVER (ORDER BY VALUE GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) G_V
FROM TEST;
> ID VALUE R_ID R_V V_ID V_V V_V_R G_ID G_V
> -- ----- --------- --------- --------------- --------------- --------------- ------------------ ------------------
> 1 1 (1, 2) (1, 1) (1, 2) (1, 1) (1, 1) (1, 2, 3) (1, 1, 5)
> 2 1 (1, 2, 3) (1, 1, 5) (1, 2) (1, 1) (1, 1) (1, 2, 3) (1, 1, 5)
> 3 5 (2, 3, 4) (1, 5, 8) (3) (5) (5) (1, 2, 3, 4, 5, 6) (1, 1, 5, 8, 8, 8)
> 4 8 (3, 4, 5) (5, 8, 8) (4, 5, 6, 7, 8) (8, 8, 8, 9, 9) (9, 9, 8, 8, 8) (3, 4, 5, 6, 7, 8) (5, 8, 8, 8, 9, 9)
> 5 8 (4, 5, 6) (8, 8, 8) (4, 5, 6, 7, 8) (8, 8, 8, 9, 9) (9, 9, 8, 8, 8) (3, 4, 5, 6, 7, 8) (5, 8, 8, 8, 9, 9)
> 6 8 (5, 6, 7) (8, 8, 9) (4, 5, 6, 7, 8) (8, 8, 8, 9, 9) (9, 9, 8, 8, 8) (3, 4, 5, 6, 7, 8) (5, 8, 8, 8, 9, 9)
> 7 9 (6, 7, 8) (8, 9, 9) (4, 5, 6, 7, 8) (8, 8, 8, 9, 9) (9, 9, 8, 8, 8) (4, 5, 6, 7, 8) (8, 8, 8, 9, 9)
> 8 9 (7, 8) (9, 9) (4, 5, 6, 7, 8) (8, 8, 8, 9, 9) (9, 9, 8, 8, 8) (4, 5, 6, 7, 8) (8, 8, 8, 9, 9)
> rows (ordered): 8
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY VALUE ROWS -1 PRECEDING) FROM TEST;
> exception INVALID_VALUE_2
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM TEST FETCH FIRST 4 ROWS ONLY;
> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY ID ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING)
> -- ----- -------------------------------------------------------------------------
> 1 1 null
> 2 1 (1)
> 3 5 (1, 2)
> 4 8 (2, 3)
> rows (ordered): 4
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) FROM TEST OFFSET 4 ROWS;
> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY ID ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING)
> -- ----- -------------------------------------------------------------------------
> 5 8 (6, 7)
> 6 8 (7, 8)
> 7 9 (8)
> 8 9 null
> rows (ordered): 4
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM TEST FETCH FIRST 4 ROWS ONLY;
> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING)
> -- ----- --------------------------------------------------------------------------
> 1 1 null
> 2 1 (1)
> 3 5 (1, 2)
> 4 8 (2, 3)
> rows (ordered): 4
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING) FROM TEST OFFSET 4 ROWS;
> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING)
> -- ----- --------------------------------------------------------------------------
> 5 8 (6, 7)
> 6 8 (7, 8)
> 7 9 (8)
> 8 9 null
> rows (ordered): 4
SELECT *,
ARRAY_AGG(ID) OVER (ORDER BY VALUE GROUPS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) U_P,
ARRAY_AGG(ID) OVER (ORDER BY VALUE GROUPS BETWEEN 2 PRECEDING AND 1 PRECEDING) P,
ARRAY_AGG(ID) OVER (ORDER BY VALUE GROUPS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) F,
ARRAY_AGG(ID) OVER (ORDER BY VALUE GROUPS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) U_F
FROM TEST;
> ID VALUE U_P P F U_F
> -- ----- ------------------ ------------ --------------- ------------------
> 1 1 null null (3, 4, 5, 6) (3, 4, 5, 6, 7, 8)
> 2 1 null null (3, 4, 5, 6) (3, 4, 5, 6, 7, 8)
> 3 5 (1, 2) (1, 2) (4, 5, 6, 7, 8) (4, 5, 6, 7, 8)
> 4 8 (1, 2, 3) (1, 2, 3) (7, 8) (7, 8)
> 5 8 (1, 2, 3) (1, 2, 3) (7, 8) (7, 8)
> 6 8 (1, 2, 3) (1, 2, 3) (7, 8) (7, 8)
> 7 9 (1, 2, 3, 4, 5, 6) (3, 4, 5, 6) null null
> 8 9 (1, 2, 3, 4, 5, 6) (3, 4, 5, 6) null null
> rows (ordered): 8
SELECT *,
ARRAY_AGG(ID) OVER (ORDER BY VALUE GROUPS BETWEEN 1 PRECEDING AND 0 PRECEDING) P,
ARRAY_AGG(ID) OVER (ORDER BY VALUE GROUPS BETWEEN 0 FOLLOWING AND 1 FOLLOWING) F
FROM TEST;
> ID VALUE P F
> -- ----- --------------- ---------------
> 1 1 (1, 2) (1, 2, 3)
> 2 1 (1, 2) (1, 2, 3)
> 3 5 (1, 2, 3) (3, 4, 5, 6)
> 4 8 (3, 4, 5, 6) (4, 5, 6, 7, 8)
> 5 8 (3, 4, 5, 6) (4, 5, 6, 7, 8)
> 6 8 (3, 4, 5, 6) (4, 5, 6, 7, 8)
> 7 9 (4, 5, 6, 7, 8) (7, 8)
> 8 9 (4, 5, 6, 7, 8) (7, 8)
> rows (ordered): 8
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN CURRENT ROW AND 1 PRECEDING) FROM TEST;
> exception SYNTAX_ERROR_1
DROP TABLE TEST;
> ok
CREATE TABLE TEST (ID INT, VALUE INT);
> ok
INSERT INTO TEST VALUES
(1, 1),
(2, 1),
(3, 2),
(4, 2),
(5, 3),
(6, 3),
(7, 4),
(8, 4);
> update count: 8
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY VALUE RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM TEST;
> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY VALUE RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING)
> -- ----- -----------------------------------------------------------------------------
> 1 1 null
> 2 1 null
> 3 2 (1, 2)
> 4 2 (1, 2)
> 5 3 (1, 2, 3, 4)
> 6 3 (1, 2, 3, 4)
> 7 4 (3, 4, 5, 6)
> 8 4 (3, 4, 5, 6)
> rows (ordered): 8
SELECT *, ARRAY_AGG(ID) OVER (ORDER BY VALUE RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING) FROM TEST;
> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY VALUE RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING)
> -- ----- -----------------------------------------------------------------------------
> 1 1 (3, 4, 5, 6)
> 2 1 (3, 4, 5, 6)
> 3 2 (5, 6, 7, 8)
> 4 2 (5, 6, 7, 8)
> 5 3 (7, 8)
> 6 3 (7, 8)
> 7 4 null
> 8 4 null
> rows (ordered): 8
DROP TABLE TEST;
> ok
...@@ -139,8 +139,8 @@ SELECT ID, CATEGORY, ...@@ -139,8 +139,8 @@ SELECT ID, CATEGORY,
NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) C, NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) C,
NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT ROW) NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT ROW)
FROM TEST FETCH FIRST 3 ROWS ONLY; FROM TEST FETCH FIRST 3 ROWS ONLY;
> ID CATEGORY C NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT ROW) > ID CATEGORY C NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY RANGE UNBOUNDED PRECEDING EXCLUDE CURRENT ROW)
> -- -------- ---- --------------------------------------------------------------------------------------------------------------------- > -- -------- ---- ---------------------------------------------------------------------------------------------
> 1 1 null null > 1 1 null null
> 2 1 1 null > 2 1 1 null
> 3 1 1 1 > 3 1 1 1
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论