提交 2170322c authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Implement remaming types of window frames

上级 88807617
...@@ -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 windowFramePreceding AND windowFrameFollowing}
|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,28 @@ Is currently supported only in aggregates and FIRST_VALUE(), LAST_VALUE(), and N ...@@ -2563,6 +2560,28 @@ 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 following","
UNBOUNDED FOLLOWING|value FOLLOWING|CURRENT ROW
","
A window frame following clause.
If value is specified it should be non-negative value or parameter.
","
UNBOUNDED FOLLOWING
1 FOLLOWING
CURRENT ROW
"
"Other Grammar","Term"," "Other Grammar","Term","
value value
| columnName | columnName
......
...@@ -3132,13 +3132,13 @@ public class Parser { ...@@ -3132,13 +3132,13 @@ public class Parser {
private WindowFrameBound readWindowFrameStarting() { private WindowFrameBound readWindowFrameStarting() {
if (readIf("UNBOUNDED")) { if (readIf("UNBOUNDED")) {
read("PRECEDING"); read("PRECEDING");
return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED, 0); return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED, null);
} }
if (readIf("CURRENT")) { if (readIf("CURRENT")) {
read("ROW"); read("ROW");
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, 0); return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
} }
int value = readNonNegativeInt(); Expression value = readValueOrParameter();
read("PRECEDING"); read("PRECEDING");
return new WindowFrameBound(WindowFrameBoundType.VALUE, value); return new WindowFrameBound(WindowFrameBoundType.VALUE, value);
} }
...@@ -3146,17 +3146,27 @@ public class Parser { ...@@ -3146,17 +3146,27 @@ public class Parser {
private WindowFrameBound readWindowFrameFollowing() { private WindowFrameBound readWindowFrameFollowing() {
if (readIf("UNBOUNDED")) { if (readIf("UNBOUNDED")) {
read("FOLLOWING"); read("FOLLOWING");
return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED, 0); return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED, null);
} }
if (readIf("CURRENT")) { if (readIf("CURRENT")) {
read("ROW"); read("ROW");
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, 0); return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
} }
int value = readNonNegativeInt(); Expression value = readValueOrParameter();
read("FOLLOWING"); read("FOLLOWING");
return new WindowFrameBound(WindowFrameBoundType.VALUE, value); return new WindowFrameBound(WindowFrameBoundType.VALUE, 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) {
if (!identifiersToUpper) { if (!identifiersToUpper) {
// if not yet converted to uppercase, do it now // if not yet converted to uppercase, do it now
......
...@@ -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());
} }
......
...@@ -66,7 +66,7 @@ public final class Window { ...@@ -66,7 +66,7 @@ public final class Window {
this.partitionBy = partitionBy; this.partitionBy = partitionBy;
this.orderBy = orderBy; this.orderBy = orderBy;
if (frame == null) { if (frame == null) {
frame = new WindowFrame(WindowFrameUnits.RANGE, new WindowFrameBound(WindowFrameBoundType.UNBOUNDED, 0), frame = new WindowFrame(WindowFrameUnits.RANGE, new WindowFrameBound(WindowFrameBoundType.UNBOUNDED, null),
null, WindowFrameExclusion.EXCLUDE_NO_OTHERS); 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.expression.aggregate.WindowFrameBound.WindowFrameBoundType; import org.h2.expression.aggregate.WindowFrameBound.WindowFrameBoundType;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
...@@ -154,6 +158,54 @@ public final class WindowFrame { ...@@ -154,6 +158,54 @@ public final class WindowFrame {
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 plus(Session session, ArrayList<Value[]> orderedRows, int currentRow, WindowFrameBound bound,
int index) {
return new BinaryOperation(OpType.PLUS, //
ValueExpression.get(orderedRows.get(currentRow)[index]),
ValueExpression.get(getValueOffset(bound, session))) //
.optimize(session).getValue(session);
}
private static Value minus(Session session, ArrayList<Value[]> orderedRows, int currentRow, WindowFrameBound bound,
int index) {
return new BinaryOperation(OpType.MINUS, //
ValueExpression.get(orderedRows.get(currentRow)[index]),
ValueExpression.get(getValueOffset(bound, session))) //
.optimize(session).getValue(session);
}
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.
* *
...@@ -202,6 +254,8 @@ public final class WindowFrame { ...@@ -202,6 +254,8 @@ public final class WindowFrame {
/** /**
* Returns iterator. * Returns iterator.
* *
* @param session
* the session
* @param orderedRows * @param orderedRows
* ordered rows * ordered rows
* @param sortOrder * @param sortOrder
...@@ -210,13 +264,15 @@ public final class WindowFrame { ...@@ -210,13 +264,15 @@ 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 size = orderedRows.size(); int size = orderedRows.size();
final int startIndex; WindowFrameBound bound = starting;
switch (starting.getType()) { int startIndex;
switch (bound.getType()) {
case UNBOUNDED: case UNBOUNDED:
startIndex = 0; startIndex = 0;
break; break;
...@@ -226,26 +282,53 @@ public final class WindowFrame { ...@@ -226,26 +282,53 @@ public final class WindowFrame {
case VALUE: case VALUE:
switch (units) { switch (units) {
case ROWS: { case ROWS: {
int value = starting.getValue(); int value = getIntOffset(bound, session);
startIndex = value > currentRow ? 0 : currentRow - value; startIndex = value > currentRow ? 0 : currentRow - value;
break; break;
} }
case GROUPS: {
int value = getIntOffset(bound, session);
startIndex = toGroupStart(orderedRows, sortOrder, currentRow, 0);
while (value > 0 && startIndex > 0) {
value--;
startIndex = toGroupStart(orderedRows, sortOrder, startIndex - 1, 0);
}
break;
}
case RANGE: {
startIndex = currentRow;
int index = sortOrder.getQueryColumnIndexes()[0];
if ((sortOrder.getSortTypes()[0] & SortOrder.DESCENDING) != 0) {
Value c = plus(session, orderedRows, currentRow, bound, index);
while (startIndex > 0
&& session.getDatabase().compare(c, orderedRows.get(startIndex - 1)[index]) >= 0) {
startIndex--;
}
} else {
Value c = minus(session, orderedRows, currentRow, bound, index);
while (startIndex > 0
&& session.getDatabase().compare(c, orderedRows.get(startIndex - 1)[index]) <= 0) {
startIndex--;
}
}
break;
}
default: default:
// TODO
throw DbException.getUnsupportedException("units=" + units); throw DbException.getUnsupportedException("units=" + units);
} }
break; break;
default: default:
throw DbException.getUnsupportedException("window frame bound type=" + starting.getType()); throw DbException.getUnsupportedException("window frame bound type=" + bound.getType());
} }
bound = following;
int endIndex; int endIndex;
if (following == null) { if (bound == null) {
endIndex = currentRow; endIndex = currentRow;
} else { } else {
switch (following.getType()) { int last = size - 1;
switch (bound.getType()) {
case UNBOUNDED: case UNBOUNDED:
endIndex = size - 1; endIndex = last;
break; break;
case CURRENT_ROW: case CURRENT_ROW:
endIndex = currentRow; endIndex = currentRow;
...@@ -253,18 +336,44 @@ public final class WindowFrame { ...@@ -253,18 +336,44 @@ public final class WindowFrame {
case VALUE: case VALUE:
switch (units) { switch (units) {
case ROWS: { case ROWS: {
int value = following.getValue(); int value = getIntOffset(bound, session);
int rem = size - currentRow - 1; int rem = last - currentRow;
endIndex = value > rem ? size - 1 : currentRow + value; endIndex = value > rem ? last : currentRow + value;
break;
}
case GROUPS: {
int value = getIntOffset(bound, session);
endIndex = toGroupEnd(orderedRows, sortOrder, currentRow, last);
while (value > 0 && endIndex < last) {
value--;
endIndex = toGroupEnd(orderedRows, sortOrder, endIndex + 1, last);
}
break;
}
case RANGE: {
endIndex = currentRow;
int index = sortOrder.getQueryColumnIndexes()[0];
if ((sortOrder.getSortTypes()[0] & SortOrder.DESCENDING) != 0) {
Value c = minus(session, orderedRows, currentRow, bound, index);
while (endIndex < last
&& session.getDatabase().compare(c, orderedRows.get(endIndex + 1)[index]) <= 0) {
endIndex++;
}
} else {
Value c = plus(session, orderedRows, currentRow, bound, index);
while (endIndex < last
&& session.getDatabase().compare(c, orderedRows.get(endIndex + 1)[index]) >= 0) {
endIndex++;
}
}
break; break;
} }
default: default:
// TODO
throw DbException.getUnsupportedException("units=" + units); throw DbException.getUnsupportedException("units=" + units);
} }
break; break;
default: default:
throw DbException.getUnsupportedException("window frame bound type=" + following.getType()); throw DbException.getUnsupportedException("window frame bound type=" + bound.getType());
} }
} }
if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) { if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
...@@ -285,15 +394,8 @@ public final class WindowFrame { ...@@ -285,15 +394,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);
......
...@@ -37,7 +37,7 @@ public class WindowFrameBound { ...@@ -37,7 +37,7 @@ public class WindowFrameBound {
private final WindowFrameBoundType type; private final WindowFrameBoundType type;
private final int value; private final Expression value;
/** /**
* Creates new instance of window frame bound. * Creates new instance of window frame bound.
...@@ -47,15 +47,12 @@ public class WindowFrameBound { ...@@ -47,15 +47,12 @@ public class WindowFrameBound {
* @param value * @param value
* bound value, if any * bound value, if any
*/ */
public WindowFrameBound(WindowFrameBoundType type, int value) { public WindowFrameBound(WindowFrameBoundType type, Expression value) {
this.type = type; this.type = type;
if (type == WindowFrameBoundType.VALUE) { if (type == WindowFrameBoundType.VALUE) {
if (value < 0) {
throw DbException.getInvalidValueException("unsigned", value);
}
this.value = value; this.value = value;
} else { } else {
this.value = 0; this.value = null;
} }
} }
...@@ -73,7 +70,7 @@ public class WindowFrameBound { ...@@ -73,7 +70,7 @@ public class WindowFrameBound {
* *
* @return the value * @return the value
*/ */
public int getValue() { public Expression getValue() {
return value; return value;
} }
...@@ -93,7 +90,7 @@ public class WindowFrameBound { ...@@ -93,7 +90,7 @@ public class WindowFrameBound {
case CURRENT_ROW: case CURRENT_ROW:
return "CURRENT ROW"; return "CURRENT ROW";
case VALUE: case VALUE:
return value + (following ? " FOLLOWING" : " PRECEDING"); return value.getSQL() + (following ? " FOLLOWING" : " PRECEDING");
default: default:
throw DbException.throwInternalError("type=" + type); throw DbException.throwInternalError("type=" + type);
} }
......
...@@ -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,44 @@ SELECT ...@@ -275,3 +275,44 @@ 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
DROP TABLE TEST;
> ok
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论