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

Add limited support for window frame clause

上级 64100847
......@@ -175,6 +175,7 @@ import org.h2.expression.aggregate.Aggregate;
import org.h2.expression.aggregate.Aggregate.AggregateType;
import org.h2.expression.aggregate.JavaAggregate;
import org.h2.expression.aggregate.Window;
import org.h2.expression.aggregate.Window.SimpleWindowFrame;
import org.h2.expression.aggregate.WindowFunction;
import org.h2.expression.aggregate.WindowFunction.WindowFunctionType;
import org.h2.index.Index;
......@@ -3061,8 +3062,23 @@ public class Parser {
} else if (!isAggregate) {
orderBy = new ArrayList<>(0);
}
SimpleWindowFrame frame;
if (aggregate instanceof WindowFunction) {
WindowFunction w = (WindowFunction) aggregate;
switch (w.getFunctionType()) {
case FIRST_VALUE:
case LAST_VALUE:
case NTH_VALUE:
frame = readWindowFrame();
break;
default:
frame = SimpleWindowFrame.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW;
}
} else {
frame = readWindowFrame();
}
read(CLOSE_PAREN);
over = new Window(partitionBy, orderBy);
over = new Window(partitionBy, orderBy, frame);
aggregate.setOverCondition(over);
currentSelect.setWindowQuery();
} else if (!isAggregate) {
......@@ -3072,6 +3088,35 @@ public class Parser {
}
}
private SimpleWindowFrame readWindowFrame() {
SimpleWindowFrame frame;
if (readIf("RANGE")) {
read("BETWEEN");
if (readIf("UNBOUNDED")) {
read("PRECEDING");
read("AND");
if (readIf("CURRENT")) {
read("ROW");
frame = SimpleWindowFrame.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW;
} else {
read("UNBOUNDED");
read("FOLLOWING");
frame = SimpleWindowFrame.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_UNBOUNDED_FOLLOWING;
}
} else {
read("CURRENT");
read("ROW");
read("AND");
read("UNBOUNDED");
read("FOLLOWING");
frame = SimpleWindowFrame.RANGE_BETWEEN_CURRENT_ROW_AND_UNBOUNDED_FOLLOWING;
}
} else {
frame = SimpleWindowFrame.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW;
}
return frame;
}
private AggregateType getAggregateType(String name) {
if (!identifiersToUpper) {
// if not yet converted to uppercase, do it now
......
......@@ -424,10 +424,37 @@ public abstract class AbstractAggregate extends Expression {
*/
protected void getOrderedResultLoop(Session session, HashMap<Integer, Value> result, ArrayList<Value[]> ordered,
int rowIdColumn) {
Object aggregateData = createAggregateData();
for (Value[] row : ordered) {
updateFromExpressions(session, aggregateData, row);
result.put(row[rowIdColumn].getInt(), getAggregatedValue(session, aggregateData));
switch (over.getWindowFrame()) {
case RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW: {
Object aggregateData = createAggregateData();
for (Value[] row : ordered) {
updateFromExpressions(session, aggregateData, row);
result.put(row[rowIdColumn].getInt(), getAggregatedValue(session, aggregateData));
}
break;
}
case RANGE_BETWEEN_CURRENT_ROW_AND_UNBOUNDED_FOLLOWING: {
// TODO optimize unordered aggregates
int size = ordered.size();
for (int i = 0; i < size; i++) {
Object aggregateData = createAggregateData();
for (int j = i; j < size; j++) {
updateFromExpressions(session, aggregateData, ordered.get(j));
}
result.put(ordered.get(i)[rowIdColumn].getInt(), getAggregatedValue(session, aggregateData));
}
break;
}
case RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_UNBOUNDED_FOLLOWING: {
Object aggregateData = createAggregateData();
for (Value[] row : ordered) {
updateFromExpressions(session, aggregateData, row);
}
Value value = getAggregatedValue(session, aggregateData);
for (Value[] row : ordered) {
result.put(row[rowIdColumn].getInt(), value);
}
}
}
}
......
......@@ -22,10 +22,54 @@ import org.h2.value.ValueArray;
*/
public final class Window {
/**
* Simple window frame.
*/
public enum SimpleWindowFrame {
/**
* 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 SimpleWindowFrame(String sql) {
this.sql = sql;
}
/**
* Returns SQL representation.
*
* @return SQL representation.
* @see Expression#getSQL()
*/
public String getSQL() {
return sql;
}
}
private final ArrayList<Expression> partitionBy;
private final ArrayList<SelectOrderBy> orderBy;
private final SimpleWindowFrame frame;
/**
* @param builder
* string builder
......@@ -53,10 +97,13 @@ public final class Window {
* PARTITION BY clause, or null
* @param orderBy
* ORDER BY clause, or null
* @param frame
* window frame clause
*/
public Window(ArrayList<Expression> partitionBy, ArrayList<SelectOrderBy> orderBy) {
public Window(ArrayList<Expression> partitionBy, ArrayList<SelectOrderBy> orderBy, SimpleWindowFrame frame) {
this.partitionBy = partitionBy;
this.orderBy = orderBy;
this.frame = frame;
}
/**
......@@ -132,6 +179,15 @@ public final class Window {
return orderBy;
}
/**
* Returns window frame.
*
* @return window frame
*/
public SimpleWindowFrame getWindowFrame() {
return frame;
}
/**
* Returns the key for the current group.
*
......@@ -173,6 +229,9 @@ public final class Window {
builder.append(StringUtils.unEnclose(partitionBy.get(i).getSQL()));
}
}
if (frame != SimpleWindowFrame.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW) {
builder.append(' ').append(frame.getSQL());
}
appendOrderBy(builder, orderBy);
return builder.append(')').toString();
}
......
......@@ -130,18 +130,18 @@ public class WindowFunction extends AbstractAggregate {
}
}
private static Value getNthValue(ArrayList<Value[]> ordered, int currentRowNumber, int number, boolean fromLast,
boolean ignoreNulls) {
return ignoreNulls ? getNthValueIgnoreNulls(ordered, currentRowNumber, number, fromLast)
: ordered.get(fromLast ? currentRowNumber - number : number)[0];
private static Value getNthValue(ArrayList<Value[]> ordered, int startIndex, int endIndex, int number,
boolean fromLast, boolean ignoreNulls) {
return ignoreNulls ? getNthValueIgnoreNulls(ordered, startIndex, endIndex, number, fromLast)
: ordered.get(fromLast ? endIndex - number : startIndex + number)[0];
}
private static Value getNthValueIgnoreNulls(ArrayList<Value[]> ordered, int currentRowNumber, int number,
private static Value getNthValueIgnoreNulls(ArrayList<Value[]> ordered, int startIndex, int endIndex, int number,
boolean fromLast) {
Value v = ValueNull.INSTANCE;
int cnt = 0;
if (fromLast) {
for (int i = currentRowNumber; i >= 0; i--) {
for (int i = endIndex; i >= startIndex; i--) {
Value t = ordered.get(i)[0];
if (t != ValueNull.INSTANCE) {
if (cnt++ == number) {
......@@ -151,7 +151,7 @@ public class WindowFunction extends AbstractAggregate {
}
}
} else {
for (int i = 0; i <= currentRowNumber; i++) {
for (int i = startIndex; i <= endIndex; i++) {
Value t = ordered.get(i)[0];
if (t != ValueNull.INSTANCE) {
if (cnt++ == number) {
......@@ -180,6 +180,15 @@ public class WindowFunction extends AbstractAggregate {
this.args = args;
}
/**
* Returns the type of this function.
*
* @return the type of this function
*/
public WindowFunctionType getFunctionType() {
return type;
}
/**
* Sets FROM FIRST or FROM LAST clause value.
*
......@@ -245,9 +254,16 @@ public class WindowFunction extends AbstractAggregate {
@Override
protected void getOrderedResultLoop(Session session, HashMap<Integer, Value> result, ArrayList<Value[]> ordered,
int rowIdColumn) {
if (type == WindowFunctionType.CUME_DIST) {
switch (type) {
case CUME_DIST:
getCumeDist(session, result, ordered, rowIdColumn);
return;
case FIRST_VALUE:
case LAST_VALUE:
case NTH_VALUE:
getNth(session, result, ordered, rowIdColumn);
return;
default:
}
int size = ordered.size();
int number = 0;
......@@ -289,25 +305,6 @@ public class WindowFunction extends AbstractAggregate {
v = ValueDouble.get((double) nm / size);
break;
}
case FIRST_VALUE:
v = getNthValue(ordered, i, 0, false, ignoreNulls);
break;
case LAST_VALUE:
v = getNthValue(ordered, i, 0, true, ignoreNulls);
break;
case NTH_VALUE: {
int n = row[1].getInt();
if (n <= 0) {
throw DbException.getInvalidValueException("nth row", n);
}
n--;
if (n > i) {
v = ValueNull.INSTANCE;
} else {
v = getNthValue(ordered, i, n, fromLast, ignoreNulls);
}
break;
}
default:
throw DbException.throwInternalError("type=" + type);
}
......@@ -333,6 +330,57 @@ public class WindowFunction extends AbstractAggregate {
}
}
private void getNth(Session session, HashMap<Integer, Value> result, ArrayList<Value[]> ordered, int rowIdColumn) {
int size = ordered.size();
for (int i = 0; i < size; i++) {
int startIndex, endIndex;
switch (over.getWindowFrame()) {
case RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW:
startIndex = 0;
endIndex = i;
break;
case RANGE_BETWEEN_CURRENT_ROW_AND_UNBOUNDED_FOLLOWING:
startIndex = i;
endIndex = size - 1;
break;
case RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_UNBOUNDED_FOLLOWING:
startIndex = 0;
endIndex = size - 1;
break;
default:
throw DbException.getUnsupportedException("window frame=" + over.getWindowFrame());
}
Value[] row = ordered.get(i);
int rowId = row[rowIdColumn].getInt();
Value v;
switch (type) {
case FIRST_VALUE: {
v = getNthValue(ordered, startIndex, endIndex, 0, false, ignoreNulls);
break;
}
case LAST_VALUE:
v = getNthValue(ordered, startIndex, endIndex, 0, true, ignoreNulls);
break;
case NTH_VALUE: {
int n = row[1].getInt();
if (n <= 0) {
throw DbException.getInvalidValueException("nth row", n);
}
n--;
if (n > endIndex - startIndex) {
v = ValueNull.INSTANCE;
} else {
v = getNthValue(ordered, startIndex, endIndex, n, fromLast, ignoreNulls);
}
break;
}
default:
throw DbException.throwInternalError("type=" + type);
}
result.put(rowId, v);
}
}
@Override
protected Value getAggregatedValue(Session session, Object aggregateData) {
throw DbException.getUnsupportedException("Window function");
......
......@@ -62,3 +62,22 @@ SELECT SUM(ID) OVER () FROM TEST;
DROP TABLE TEST;
> ok
SELECT
ID,
SUM(ID) OVER (ORDER BY ID) S,
SUM(ID) OVER (ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) S_U_C,
SUM(ID) OVER (ORDER BY ID RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) S_C_U,
SUM(ID) OVER (ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) S_U_U
FROM (SELECT X ID FROM SYSTEM_RANGE(1, 8));
> ID S S_U_C S_C_U S_U_U
> -- -- ----- ----- -----
> 1 1 1 36 36
> 2 3 3 35 36
> 3 6 6 33 36
> 4 10 10 30 36
> 5 15 15 26 36
> 6 21 21 21 36
> 7 28 28 15 36
> 8 36 36 8 36
> rows (ordered): 8
......@@ -82,6 +82,34 @@ SELECT *,
> 6 1 13 12 12 null 12 12 null 13 13 13
> rows (ordered): 6
SELECT *,
NTH_VALUE(VALUE, 2) OVER(ORDER BY ID) F,
NTH_VALUE(VALUE, 2) OVER(ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) F_U_C,
NTH_VALUE(VALUE, 2) OVER(ORDER BY ID RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) F_C_U,
NTH_VALUE(VALUE, 2) OVER(ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) F_U_U,
NTH_VALUE(VALUE, 2) FROM LAST OVER(ORDER BY ID) L,
NTH_VALUE(VALUE, 2) FROM LAST OVER(ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) L_U_C,
NTH_VALUE(VALUE, 2) FROM LAST OVER(ORDER BY ID RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) L_C_U,
NTH_VALUE(VALUE, 2) FROM LAST OVER(ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) L_U_U
FROM TEST;
> ID CATEGORY VALUE F F_U_C F_C_U F_U_U L L_U_C L_C_U L_U_U
> -- -------- ----- ---- ----- ----- ----- ---- ----- ----- -----
> 1 1 null null null 12 12 null null 41 41
> 2 1 12 12 12 null 12 null null 41 41
> 3 1 null 12 12 13 12 12 12 41 41
> 4 1 13 12 12 null 12 null null 41 41
> 5 1 null 12 12 13 12 13 13 41 41
> 6 1 13 12 12 21 12 null null 41 41
> 7 2 21 12 12 22 12 13 13 41 41
> 8 2 22 12 12 31 12 21 21 41 41
> 9 3 31 12 12 32 12 22 22 41 41
> 10 3 32 12 12 33 12 31 31 41 41
> 11 3 33 12 12 41 12 32 32 41 41
> 12 4 41 12 12 null 12 33 33 41 41
> 13 4 null 12 12 null 12 41 41 null 41
> rows (ordered): 13
SELECT NTH_VALUE(VALUE, 0) OVER (ORDER BY ID) FROM TEST;
> exception INVALID_VALUE_2
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论