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

Allow any expressions in window frames

上级 f27c631e
......@@ -3294,7 +3294,7 @@ public class Parser {
read(ROW);
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
}
Expression value = readValueOrParameter();
Expression value = readExpression();
read("PRECEDING");
return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value);
}
......@@ -3311,7 +3311,7 @@ public class Parser {
read(ROW);
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
}
Expression value = readValueOrParameter();
Expression value = readExpression();
if (readIf("PRECEDING")) {
return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value);
}
......@@ -3319,16 +3319,6 @@ public class Parser {
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) {
if (!identifiersToUpper) {
// if not yet converted to uppercase, do it now
......
......@@ -98,7 +98,9 @@ public abstract class AbstractAggregate extends DataAnalysisOperation {
aggregateFastPartition(session, result, ordered, rowIdColumn, grouped);
return;
}
if (frame.getExclusion() == WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
if (frame.isVariableBounds()) {
grouped = false;
} else if (frame.getExclusion() == WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
WindowFrameBound following = frame.getFollowing();
boolean unboundedFollowing = following != null
&& following.getType() == WindowFrameBoundType.UNBOUNDED_FOLLOWING;
......@@ -117,10 +119,11 @@ public abstract class AbstractAggregate extends DataAnalysisOperation {
}
// All other types of frames (slow)
int size = ordered.size();
int frameParametersOffset = getWindowFrameParametersOffset();
for (int i = 0; i < size;) {
Object aggregateData = createAggregateData();
for (Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i,
false); iter.hasNext();) {
for (Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
frameParametersOffset, i, false); iter.hasNext();) {
updateFromExpressions(session, aggregateData, iter.next());
}
Value r = getAggregatedValue(session, aggregateData);
......
......@@ -59,14 +59,19 @@ public abstract class DataAnalysisOperation extends Expression {
*/
protected SortOrder overOrderBySort;
private int numFrameExpressions;
private int lastGroupRowId;
/**
* Create sort order.
*
* @param session database session
* @param orderBy array of order by expressions
* @param offset index offset
* @param session
* database session
* @param orderBy
* array of order by expressions
* @param offset
* index offset
* @return the SortOrder
*/
protected static SortOrder createOrder(Session session, ArrayList<SelectOrderBy> orderBy, int offset) {
......@@ -131,9 +136,12 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Map the columns of the resolver to expression columns.
*
* @param resolver the column resolver
* @param level the subquery nesting level
* @param innerState one of the Expression MAP_IN_* values
* @param resolver
* the column resolver
* @param level
* the subquery nesting level
* @param innerState
* one of the Expression MAP_IN_* values
*/
protected void mapColumnsAnalysis(ColumnResolver resolver, int level, int innerState) {
if (over != null) {
......@@ -151,6 +159,18 @@ public abstract class DataAnalysisOperation extends Expression {
} else if (!isAggregate()) {
overOrderBySort = new SortOrder(session.getDatabase(), new int[getNumExpressions()], new int[0], null);
}
WindowFrame frame = over.getWindowFrame();
if (frame != null) {
int n = 0;
if (frame.getStarting().isVariable()) {
n++;
}
WindowFrameBound following = frame.getFollowing();
if (following != null && following.isVariable()) {
n++;
}
numFrameExpressions = n;
}
}
return this;
}
......@@ -200,9 +220,12 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Update a row of an aggregate.
*
* @param session the database session
* @param groupData data for the aggregate group
* @param groupRowId row id of group
* @param session
* the database session
* @param groupData
* data for the aggregate group
* @param groupRowId
* row id of group
*/
protected abstract void updateAggregate(Session session, SelectGroups groupData, int groupRowId);
......@@ -222,12 +245,21 @@ public abstract class DataAnalysisOperation extends Expression {
}
/**
* Returns the number of expressions, excluding FILTER and OVER clauses.
* Returns the number of expressions, excluding OVER clause.
*
* @return the number of expressions
*/
protected abstract int getNumExpressions();
/**
* Returns the number of window frame expressions.
*
* @return the number of window frame expressions
*/
private int getNumFrameExpressions() {
return numFrameExpressions;
}
/**
* Stores current values of expressions into the specified array.
*
......@@ -241,9 +273,12 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Get the aggregate data for a window clause.
*
* @param session database session
* @param groupData aggregate group data
* @param forOrderBy true if this is for ORDER BY
* @param session
* database session
* @param groupData
* aggregate group data
* @param forOrderBy
* true if this is for ORDER BY
* @return the aggregate data object, specific to each kind of aggregate.
*/
protected Object getWindowData(Session session, SelectGroups groupData, boolean forOrderBy) {
......@@ -261,9 +296,12 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Get the aggregate group data object from the collector object.
* @param groupData the collector object
* @param ifExists if true, return null if object not found,
* if false, return new object if nothing found
*
* @param groupData
* the collector object
* @param ifExists
* if true, return null if object not found, if false, return new
* object if nothing found
* @return group data object
*/
protected Object getGroupData(SelectGroups groupData, boolean ifExists) {
......@@ -320,6 +358,20 @@ public abstract class DataAnalysisOperation extends Expression {
: getWindowResult(session, groupData);
}
/**
* Returns offset of window frame parameters.
*
* @return offset of window frame parameters
*/
protected final int getWindowFrameParametersOffset() {
int frameParametersOffset = getNumExpressions();
ArrayList<SelectOrderBy> orderBy = over.getOrderBy();
if (orderBy != null) {
frameParametersOffset += orderBy.size();
}
return frameParametersOffset;
}
/**
* Returns result of this window function or window aggregate. This method
* is not used for plain aggregates.
......@@ -374,22 +426,38 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Update a row of an ordered aggregate.
*
* @param session the database session
* @param groupData data for the aggregate group
* @param groupRowId row id of group
* @param orderBy list of order by expressions
* @param session
* the database session
* @param groupData
* data for the aggregate group
* @param groupRowId
* row id of group
* @param orderBy
* list of order by expressions
*/
protected void updateOrderedAggregate(Session session, SelectGroups groupData, int groupRowId,
ArrayList<SelectOrderBy> orderBy) {
int ne = getNumExpressions();
int size = orderBy != null ? orderBy.size() : 0;
Value[] array = new Value[ne + size + 1];
int frameSize = getNumFrameExpressions();
Value[] array = new Value[ne + size + frameSize + 1];
rememberExpressions(session, array);
for (int i = 0; i < size; i++) {
@SuppressWarnings("null")
SelectOrderBy o = orderBy.get(i);
array[ne++] = o.expression.getValue(session);
}
if (frameSize > 0) {
WindowFrame frame = over.getWindowFrame();
WindowFrameBound bound = frame.getStarting();
if (bound.isVariable()) {
array[ne++] = bound.getValue().getValue(session);
}
bound = frame.getFollowing();
if (bound != null && bound.isVariable()) {
array[ne++] = bound.getValue().getValue(session);
}
}
array[ne] = ValueInt.get(groupRowId);
@SuppressWarnings("unchecked")
ArrayList<Value[]> data = (ArrayList<Value[]>) getWindowData(session, groupData, true);
......@@ -408,6 +476,7 @@ public abstract class DataAnalysisOperation extends Expression {
rowIdColumn += orderBy.size();
Collections.sort(orderedData, overOrderBySort);
}
rowIdColumn += getNumFrameExpressions();
getOrderedResultLoop(session, result, orderedData, rowIdColumn);
partition.setOrderedResult(result);
}
......@@ -433,7 +502,8 @@ public abstract class DataAnalysisOperation extends Expression {
/**
* Used to create SQL for the OVER and FILTER clauses.
*
* @param builder string builder
* @param builder
* string builder
* @return the builder object
*/
protected StringBuilder appendTailConditions(StringBuilder builder) {
......
......@@ -66,7 +66,7 @@ public final class Window {
* @param orderBy
* ORDER BY clause, or null
* @param frame
* window frame clause
* window frame clause, or null
*/
public Window(String parent, ArrayList<Expression> partitionBy, ArrayList<SelectOrderBy> orderBy,
WindowFrame frame) {
......@@ -97,6 +97,9 @@ public final class Window {
o.expression.mapColumns(resolver, level, Expression.MAP_IN_WINDOW);
}
}
if (frame != null) {
frame.mapColumns(resolver, level, Expression.MAP_IN_WINDOW);
}
}
private void resolveWindows(ColumnResolver resolver) {
......@@ -136,6 +139,9 @@ public final class Window {
o.expression = o.expression.optimize(session);
}
}
if (frame != null) {
frame.optimize(session);
}
}
/**
......@@ -253,6 +259,9 @@ public final class Window {
o.expression.updateAggregate(session, stage);
}
}
if (frame != null) {
frame.updateAggregate(session, stage);
}
}
@Override
......
......@@ -12,10 +12,12 @@ import java.util.NoSuchElementException;
import org.h2.engine.Session;
import org.h2.expression.BinaryOperation;
import org.h2.expression.Expression;
import org.h2.expression.BinaryOperation.OpType;
import org.h2.expression.ValueExpression;
import org.h2.message.DbException;
import org.h2.result.SortOrder;
import org.h2.table.ColumnResolver;
import org.h2.value.Value;
/**
......@@ -200,18 +202,19 @@ public final class WindowFrame {
* ordered rows
* @param sortOrder
* sort order
* @param frameParametersOffset
* offset of window frame parameters
* @param currentRow
* index of the current row
* @param reverse
* whether iterator should iterate in reverse order
*
* @return iterator
*/
public static Iterator<Value[]> iterator(Window over, Session session, ArrayList<Value[]> orderedRows,
SortOrder sortOrder, int currentRow, boolean reverse) {
SortOrder sortOrder, int frameParametersOffset, int currentRow, boolean reverse) {
WindowFrame frame = over.getWindowFrame();
if (frame != null) {
return frame.iterator(session, orderedRows, sortOrder, currentRow, reverse);
return frame.iterator(session, orderedRows, sortOrder, frameParametersOffset, currentRow, reverse);
}
int endIndex = orderedRows.size() - 1;
return plainIterator(orderedRows, 0,
......@@ -286,8 +289,9 @@ public final class WindowFrame {
return offset;
}
private static int getIntOffset(WindowFrameBound bound, Session session) {
int value = bound.getValue().getValue(session).getInt();
private static int getIntOffset(WindowFrameBound bound, Value[] values, int parameterOffset, Session session) {
Value v = bound.isVariable() ? values[parameterOffset] : bound.getValue().getValue(session);
int value = v.getInt();
if (value < 0) {
throw DbException.getInvalidValueException("unsigned", value);
}
......@@ -295,19 +299,20 @@ public final class WindowFrame {
}
private static Value[] getCompareRow(Session session, ArrayList<Value[]> orderedRows, SortOrder sortOrder,
int currentRow, WindowFrameBound bound, boolean add) {
int currentRow, WindowFrameBound bound, int parameterOffset, 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))) //
ValueExpression.get(row[sortIndex]),
ValueExpression.get(getValueOffset(bound, orderedRows.get(currentRow), parameterOffset, session)))
.optimize(session).getValue(session);
return newRow;
}
private static Value getValueOffset(WindowFrameBound bound, Session session) {
Value value = bound.getValue().getValue(session);
private static Value getValueOffset(WindowFrameBound bound, Value[] values, int parameterOffset, Session session) {
Value value = bound.isVariable() ? values[parameterOffset] : bound.getValue().getValue(session);
if (value.getSignum() < 0) {
throw DbException.getInvalidValueException("unsigned", value.getTraceSQL());
}
......@@ -385,6 +390,68 @@ public final class WindowFrame {
&& s.compareTo(f) <= 0;
}
/**
* Check if bounds of this frame has variable expressions. This method may
* be used only after {@link #optimize(Session)} invocation.
*
* @return if bounds of this frame has variable expressions
*/
public boolean isVariableBounds() {
if (starting.isVariable()) {
return true;
}
if (following != null && following.isVariable()) {
return true;
}
return false;
}
/**
* Map the columns of the resolver to expression columns.
*
* @param resolver
* the column resolver
* @param level
* the subquery nesting level
* @param state
* current state for nesting checks
*/
void mapColumns(ColumnResolver resolver, int level, int state) {
starting.mapColumns(resolver, level, state);
if (following != null) {
following.mapColumns(resolver, level, state);
}
}
/**
* Try to optimize bound expressions.
*
* @param session
* the session
*/
void optimize(Session session) {
starting.optimize(session);
if (following != null) {
following.optimize(session);
}
}
/**
* Update an aggregate value.
*
* @param session
* the session
* @param stage
* select stage
* @see Expression#updateAggregate(Session, int)
*/
void updateAggregate(Session session, int stage) {
starting.updateAggregate(session, stage);
if (following != null) {
following.updateAggregate(session, stage);
}
}
/**
* Returns iterator.
*
......@@ -394,6 +461,8 @@ public final class WindowFrame {
* ordered rows
* @param sortOrder
* sort order
* @param frameParametersOffset
* offset of window frame parameters
* @param currentRow
* index of the current row
* @param reverse
......@@ -401,9 +470,14 @@ public final class WindowFrame {
* @return iterator
*/
public Iterator<Value[]> iterator(Session session, ArrayList<Value[]> orderedRows, SortOrder sortOrder,
int currentRow, boolean reverse) {
int startIndex = getIndex(session, orderedRows, sortOrder, currentRow, starting, false);
int endIndex = following != null ? getIndex(session, orderedRows, sortOrder, currentRow, following, true)
int frameParametersOffset, int currentRow, boolean reverse) {
int followingOffset = frameParametersOffset;
if (starting.isVariable()) {
followingOffset++;
}
int startIndex = getIndex(session, orderedRows, sortOrder, currentRow, starting, frameParametersOffset, false);
int endIndex = following != null
? getIndex(session, orderedRows, sortOrder, currentRow, following, followingOffset, true)
: units == WindowFrameUnits.ROWS ? currentRow
: toGroupEnd(orderedRows, sortOrder, currentRow, orderedRows.size() - 1);
if (endIndex < startIndex) {
......@@ -443,7 +517,7 @@ public final class WindowFrame {
if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
throw new UnsupportedOperationException();
}
int startIndex = getIndex(session, orderedRows, sortOrder, currentRow, starting, false);
int startIndex = getIndex(session, orderedRows, sortOrder, currentRow, starting, -1, false);
if (startIndex < 0) {
startIndex = 0;
}
......@@ -469,7 +543,7 @@ public final class WindowFrame {
if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
throw new UnsupportedOperationException();
}
int endIndex = following != null ? getIndex(session, orderedRows, sortOrder, currentRow, following, true)
int endIndex = following != null ? getIndex(session, orderedRows, sortOrder, currentRow, following, -1, true)
: units == WindowFrameUnits.ROWS ? currentRow
: toGroupEnd(orderedRows, sortOrder, currentRow, orderedRows.size() - 1);
int size = orderedRows.size();
......@@ -480,7 +554,7 @@ public final class WindowFrame {
}
private int getIndex(Session session, ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow,
WindowFrameBound bound, boolean forFollowing) {
WindowFrameBound bound, int parameterOffset, boolean forFollowing) {
int size = orderedRows.size();
int last = size - 1;
int index;
......@@ -491,12 +565,12 @@ public final class WindowFrame {
case PRECEDING:
switch (units) {
case ROWS: {
int value = getIntOffset(bound, session);
int value = getIntOffset(bound, orderedRows.get(currentRow), parameterOffset, session);
index = value > currentRow ? -1 : currentRow - value;
break;
}
case GROUPS: {
int value = getIntOffset(bound, session);
int value = getIntOffset(bound, orderedRows.get(currentRow), parameterOffset, session);
if (!forFollowing) {
index = toGroupStart(orderedRows, sortOrder, currentRow, 0);
while (value > 0 && index > 0) {
......@@ -521,7 +595,7 @@ public final class WindowFrame {
}
case RANGE: {
index = currentRow;
Value[] row = getCompareRow(session, orderedRows, sortOrder, index, bound, false);
Value[] row = getCompareRow(session, orderedRows, sortOrder, index, bound, parameterOffset, false);
index = Collections.binarySearch(orderedRows, row, sortOrder);
if (index >= 0) {
if (!forFollowing) {
......@@ -566,13 +640,13 @@ public final class WindowFrame {
case FOLLOWING:
switch (units) {
case ROWS: {
int value = getIntOffset(bound, session);
int value = getIntOffset(bound, orderedRows.get(currentRow), parameterOffset, session);
int rem = last - currentRow;
index = value > rem ? size : currentRow + value;
break;
}
case GROUPS: {
int value = getIntOffset(bound, session);
int value = getIntOffset(bound, orderedRows.get(currentRow), parameterOffset, session);
if (forFollowing) {
index = toGroupEnd(orderedRows, sortOrder, currentRow, last);
while (value > 0 && index < last) {
......@@ -597,7 +671,7 @@ public final class WindowFrame {
}
case RANGE: {
index = currentRow;
Value[] row = getCompareRow(session, orderedRows, sortOrder, index, bound, true);
Value[] row = getCompareRow(session, orderedRows, sortOrder, index, bound, parameterOffset, true);
index = Collections.binarySearch(orderedRows, row, sortOrder);
if (index >= 0) {
if (forFollowing) {
......
......@@ -5,7 +5,9 @@
*/
package org.h2.expression.analysis;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.table.ColumnResolver;
/**
* Window frame bound.
......@@ -14,7 +16,9 @@ public class WindowFrameBound {
private final WindowFrameBoundType type;
private final Expression value;
private Expression value;
private boolean isVariable;
/**
* Creates new instance of window frame bound.
......@@ -51,6 +55,62 @@ public class WindowFrameBound {
return value;
}
/**
* Returns whether bound is defined with a variable. This method may be used
* only after {@link #optimize(Session)} invocation.
*
* @return whether bound is defined with a variable
*/
public boolean isVariable() {
return isVariable;
}
/**
* Map the columns of the resolver to expression columns.
*
* @param resolver
* the column resolver
* @param level
* the subquery nesting level
* @param state
* current state for nesting checks
*/
void mapColumns(ColumnResolver resolver, int level, int state) {
if (value != null) {
value.mapColumns(resolver, level, state);
}
}
/**
* Try to optimize bound expression.
*
* @param session
* the session
*/
void optimize(Session session) {
if (value != null) {
value = value.optimize(session);
if (!value.isConstant()) {
isVariable = true;
}
}
}
/**
* Update an aggregate value.
*
* @param session
* the session
* @param stage
* select stage
* @see Expression#updateAggregate(Session, int)
*/
void updateAggregate(Session session, int stage) {
if (value != null) {
value.updateAggregate(session, stage);
}
}
/**
* Appends SQL representation to the specified builder.
*
......
......@@ -349,18 +349,19 @@ public class WindowFunction extends DataAnalysisOperation {
private void getNth(Session session, HashMap<Integer, Value> result, ArrayList<Value[]> ordered, int rowIdColumn) {
int size = ordered.size();
int frameParametersOffset = getWindowFrameParametersOffset();
for (int i = 0; i < size; i++) {
Value[] row = ordered.get(i);
int rowId = row[rowIdColumn].getInt();
Value v;
switch (type) {
case FIRST_VALUE:
v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, false), 0,
ignoreNulls);
v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
frameParametersOffset, i, false), 0, ignoreNulls);
break;
case LAST_VALUE:
v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, true), 0,
ignoreNulls);
v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
frameParametersOffset, i, true), 0, ignoreNulls);
break;
case NTH_VALUE: {
int n = row[1].getInt();
......@@ -368,8 +369,8 @@ public class WindowFunction extends DataAnalysisOperation {
throw DbException.getInvalidValueException("nth row", n);
}
n--;
Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i,
fromLast);
Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
frameParametersOffset, i, fromLast);
v = getNthValue(iter, n, ignoreNulls);
break;
}
......
......@@ -596,5 +596,23 @@ SELECT ID, VALUE,
> 8 4 [1, 2, 3, 4, 5, 6, 7, 8] [8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8]
> rows: 8
SELECT ID, VALUE,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND VALUE FOLLOWING) RG,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID RANGE BETWEEN VALUE PRECEDING AND UNBOUNDED FOLLOWING) RGR,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND VALUE FOLLOWING) R,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID ROWS BETWEEN VALUE PRECEDING AND UNBOUNDED FOLLOWING) RR
FROM TEST;
> ID VALUE RG RGR R RR
> -- ----- ------------------------ ------------------------ ------------------------ ------------------------
> 1 1 [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8]
> 2 1 [1, 2, 3] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2, 3] [1, 2, 3, 4, 5, 6, 7, 8]
> 3 2 [1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8]
> 4 2 [1, 2, 3, 4, 5, 6] [2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [2, 3, 4, 5, 6, 7, 8]
> 5 3 [1, 2, 3, 4, 5, 6, 7, 8] [2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [2, 3, 4, 5, 6, 7, 8]
> 6 3 [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8]
> 7 4 [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8]
> 8 4 [1, 2, 3, 4, 5, 6, 7, 8] [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [4, 5, 6, 7, 8]
> rows: 8
DROP TABLE TEST;
> ok
......@@ -218,6 +218,28 @@ SELECT ID, CATEGORY,
> 13 4 1 1 1 2 3 4
> rows (ordered): 13
SELECT ID, CATEGORY,
FIRST_VALUE(ID) OVER (ORDER BY ID ROWS BETWEEN CATEGORY FOLLOWING AND UNBOUNDED FOLLOWING) F,
LAST_VALUE(ID) OVER (ORDER BY ID ROWS BETWEEN CURRENT ROW AND CATEGORY FOLLOWING) L,
NTH_VALUE(ID, 2) OVER (ORDER BY ID ROWS BETWEEN CATEGORY FOLLOWING AND UNBOUNDED FOLLOWING) N
FROM TEST ORDER BY ID;
> ID CATEGORY F L N
> -- -------- ---- -- ----
> 1 1 2 2 3
> 2 1 3 3 4
> 3 1 4 4 5
> 4 1 5 5 6
> 5 1 6 6 7
> 6 1 7 7 8
> 7 2 9 9 10
> 8 2 10 10 11
> 9 3 12 12 13
> 10 3 13 13 null
> 11 3 null 13 null
> 12 4 null 13 null
> 13 4 null 13 null
> rows (ordered): 13
DROP TABLE TEST;
> ok
......
......@@ -805,4 +805,4 @@ queryparser tokenized freeze factorings recompilation unenclosed rfe dsync
econd irst bcef ordinality nord unnest
analyst occupation distributive josaph aor engineer sajeewa isuru randil kevin doctor businessman artist ashan
corrupts splitted disruption unintentional octets preconditions predicates subq objectweb insn opcodes
preserves masking holder unboxing avert iae transformed subtle reevaluate exclusions subclause ftbl
preserves masking holder unboxing avert iae transformed subtle reevaluate exclusions subclause ftbl rgr
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论