提交 0c17e600 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Add window frame exclusion clause implementation

上级 32d3ca3f
...@@ -2535,17 +2535,10 @@ SELECT * FROM (VALUES(1, 'Hello'), (2, 'World')) AS V; ...@@ -2535,17 +2535,10 @@ SELECT * FROM (VALUES(1, 'Hello'), (2, 'World')) AS V;
"Other Grammar","Window specification"," "Other Grammar","Window specification","
([PARTITION BY expression [,...]] [ORDER BY order [,...]] ([PARTITION BY expression [,...]] [ORDER BY order [,...]]
[RANGE BETWEEN { [windowFrame])
UNBOUNDED PRECEDING AND CURRENT ROW
|CURRENT ROW AND UNBOUNDED FOLLOWING
|UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
}])
"," ","
A window specification for a window function or aggregate. A window specification for a window function or aggregate.
Window frame clause (RANGE) is currently supported only in aggregates and
FIRST_VALUE(), LAST_VALUE(), and NTH_VALUE() window functions.
Window functions are currently experimental in H2 and should be used with caution. Window functions are currently experimental in H2 and should be used with caution.
They also may require a lot of memory for large queries. They also may require a lot of memory for large queries.
"," ","
...@@ -2553,6 +2546,21 @@ They also may require a lot of memory for large queries. ...@@ -2553,6 +2546,21 @@ They also may require a lot of memory for large queries.
(ORDER BY ID) (ORDER BY ID)
(PARTITION BY CATEGORY) (PARTITION BY CATEGORY)
(PARTITION BY CATEGORY ORDER BY NAME, ID) (PARTITION BY CATEGORY ORDER BY NAME, ID)
(ORDER BY Y RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE TIES)
"
"Other Grammar","Window frame","
[RANGE BETWEEN {
UNBOUNDED PRECEDING AND CURRENT ROW
|CURRENT ROW AND UNBOUNDED FOLLOWING
|UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
}]
[EXCLUDE {CURRENT ROW|GROUP|TIES|NO OTHERS}]
","
A window frame clause.
Is currently supported only in aggregates and FIRST_VALUE(), LAST_VALUE(), and NTH_VALUE() window functions.
","
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP
" "
"Other Grammar","Term"," "Other Grammar","Term","
......
...@@ -177,6 +177,7 @@ import org.h2.expression.aggregate.JavaAggregate; ...@@ -177,6 +177,7 @@ 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.WindowFrame.SimpleExtent;
import org.h2.expression.aggregate.WindowFrame.WindowFrameExclusion;
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;
...@@ -3073,7 +3074,8 @@ public class Parser { ...@@ -3073,7 +3074,8 @@ public class Parser {
frame = readWindowFrame(); frame = readWindowFrame();
break; break;
default: default:
frame = new WindowFrame(SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW); frame = new WindowFrame(SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW,
WindowFrameExclusion.EXCLUDE_NO_OTHERS);
} }
} else { } else {
frame = readWindowFrame(); frame = readWindowFrame();
...@@ -3091,6 +3093,7 @@ public class Parser { ...@@ -3091,6 +3093,7 @@ public class Parser {
private WindowFrame readWindowFrame() { private WindowFrame readWindowFrame() {
SimpleExtent extent; SimpleExtent extent;
WindowFrameExclusion exclusion = WindowFrameExclusion.EXCLUDE_NO_OTHERS;
if (readIf("RANGE")) { if (readIf("RANGE")) {
read("BETWEEN"); read("BETWEEN");
if (readIf("UNBOUNDED")) { if (readIf("UNBOUNDED")) {
...@@ -3112,10 +3115,23 @@ public class Parser { ...@@ -3112,10 +3115,23 @@ public class Parser {
read("FOLLOWING"); read("FOLLOWING");
extent = SimpleExtent.RANGE_BETWEEN_CURRENT_ROW_AND_UNBOUNDED_FOLLOWING; extent = SimpleExtent.RANGE_BETWEEN_CURRENT_ROW_AND_UNBOUNDED_FOLLOWING;
} }
if (readIf("EXCLUDE")) {
if (readIf("CURRENT")) {
read("ROW");
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 { } else {
extent = SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW; extent = SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW;
} }
return new WindowFrame(extent); return new WindowFrame(extent, exclusion);
} }
private AggregateType getAggregateType(String name) { private AggregateType getAggregateType(String name) {
......
...@@ -8,6 +8,7 @@ package org.h2.expression.aggregate; ...@@ -8,6 +8,7 @@ package org.h2.expression.aggregate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.dml.Select; import org.h2.command.dml.Select;
...@@ -441,12 +442,12 @@ public abstract class AbstractAggregate extends Expression { ...@@ -441,12 +442,12 @@ public abstract class AbstractAggregate extends Expression {
result.put(row[rowIdColumn].getInt(), value); result.put(row[rowIdColumn].getInt(), value);
} }
} else { } else {
// TODO optimize unordered aggregates
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 (int j = i; j < size; j++) { for (Iterator<Value[]> iter = frame.iterator(ordered, getOverOrderBySort(), i, false); iter
updateFromExpressions(session, aggregateData, ordered.get(j)); .hasNext();) {
updateFromExpressions(session, aggregateData, iter.next());
} }
result.put(ordered.get(i)[rowIdColumn].getInt(), getAggregatedValue(session, aggregateData)); result.put(ordered.get(i)[rowIdColumn].getInt(), getAggregatedValue(session, aggregateData));
} }
......
...@@ -36,7 +36,10 @@ public final class Window { ...@@ -36,7 +36,10 @@ public final class Window {
*/ */
static void appendOrderBy(StringBuilder builder, ArrayList<SelectOrderBy> orderBy) { static void appendOrderBy(StringBuilder builder, ArrayList<SelectOrderBy> orderBy) {
if (orderBy != null && !orderBy.isEmpty()) { if (orderBy != null && !orderBy.isEmpty()) {
builder.append(" ORDER BY "); if (builder.charAt(builder.length() - 1) != '(') {
builder.append(' ');
}
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);
if (i > 0) { if (i > 0) {
...@@ -187,10 +190,10 @@ public final class Window { ...@@ -187,10 +190,10 @@ public final class Window {
builder.append(StringUtils.unEnclose(partitionBy.get(i).getSQL())); builder.append(StringUtils.unEnclose(partitionBy.get(i).getSQL()));
} }
} }
appendOrderBy(builder, orderBy);
if (!frame.isDefault()) { if (!frame.isDefault()) {
builder.append(' ').append(frame.getSQL()); builder.append(' ').append(frame.getSQL());
} }
appendOrderBy(builder, orderBy);
return builder.append(')').toString(); return builder.append(')').toString();
} }
......
...@@ -6,10 +6,13 @@ ...@@ -6,10 +6,13 @@
package org.h2.expression.aggregate; package org.h2.expression.aggregate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.result.SortOrder;
import org.h2.value.Value; import org.h2.value.Value;
/** /**
...@@ -59,68 +62,76 @@ public final class WindowFrame { ...@@ -59,68 +62,76 @@ public final class WindowFrame {
} }
private final SimpleExtent extent; /**
* Window frame exclusion clause.
*/
public enum WindowFrameExclusion {
/**
* EXCLUDE CURRENT ROW exclusion clause.
*/
EXCLUDE_CURRENT_ROW("EXCLUDE CURRENT ROW"),
/** /**
* Creates new instance of window frame clause. * EXCLUDE GROUP exclusion clause.
*
* @param extent
* window frame extent
*/ */
public WindowFrame(SimpleExtent extent) { EXCLUDE_GROUP("EXCLUDE GROUP"),
this.extent = extent;
}
/** /**
* Returns whether window frame specification can be omitted. * EXCLUDE TIES exclusion clause.
*
* @return whether window frame specification can be omitted
*/ */
public boolean isDefault() { EXCLUDE_TIES("EXCLUDE TIES"),
return extent == SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW;
}
/** /**
* Returns whether window frame specification contains all rows in * EXCLUDE NO OTHERS exclusion clause.
* partition.
*
* @return whether window frame specification contains all rows in partition
*/ */
public boolean isFullPartition() { EXCLUDE_NO_OTHERS("EXCLUDE NO OTHERS"),
return extent == SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_UNBOUNDED_FOLLOWING;
;
private final String sql;
private WindowFrameExclusion(String sql) {
this.sql = sql;
} }
/** /**
* Returns iterator. * Returns SQL representation.
* *
* @param orderedRows * @return SQL representation.
* ordered rows * @see org.h2.expression.Expression#getSQL()
* @param currentRow
* index of the current row
* @return iterator
*/ */
public Iterator<Value[]> iterator(final ArrayList<Value[]> orderedRows, int currentRow) { public String getSQL() {
int size = orderedRows.size(); return sql;
final int startIndex, endIndex; }
switch (extent) {
case RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW: }
startIndex = 0;
endIndex = currentRow; private abstract class Itr implements Iterator<Value[]> {
break;
case RANGE_BETWEEN_CURRENT_ROW_AND_UNBOUNDED_FOLLOWING: final ArrayList<Value[]> orderedRows;
startIndex = currentRow;
endIndex = size - 1; Itr(ArrayList<Value[]> orderedRows) {
break; this.orderedRows = orderedRows;
case RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_UNBOUNDED_FOLLOWING: }
startIndex = 0;
endIndex = size - 1; @Override
break; public final void remove() {
default: throw new UnsupportedOperationException();
throw DbException.getUnsupportedException("window frame extent =" + extent); }
} }
return new Iterator<Value[]>() {
private int cursor = startIndex; private final class PlainItr extends Itr {
private final int endIndex;
private int cursor;
PlainItr(ArrayList<Value[]> orderedRows, int startIndex, int endIndex) {
super(orderedRows);
this.endIndex = endIndex;
cursor = startIndex;
}
@Override @Override
public boolean hasNext() { public boolean hasNext() {
...@@ -135,24 +146,144 @@ public final class WindowFrame { ...@@ -135,24 +146,144 @@ public final class WindowFrame {
return orderedRows.get(cursor++); return orderedRows.get(cursor++);
} }
}
private final class PlainReverseItr extends Itr {
private final int startIndex;
private int cursor;
PlainReverseItr(ArrayList<Value[]> orderedRows, int startIndex, int endIndex) {
super(orderedRows);
this.startIndex = startIndex;
cursor = endIndex;
}
@Override @Override
public void remove() { public boolean hasNext() {
throw new UnsupportedOperationException(); return cursor >= startIndex;
} }
}; @Override
public Value[] next() {
if (cursor < startIndex) {
throw new NoSuchElementException();
}
return orderedRows.get(cursor--);
} }
}
private abstract class AbstractBitSetItr extends Itr {
final BitSet set;
int cursor;
AbstractBitSetItr(ArrayList<Value[]> orderedRows, BitSet set) {
super(orderedRows);
this.set = set;
}
@Override
public final boolean hasNext() {
return cursor >= 0;
}
}
private final class BitSetItr extends AbstractBitSetItr {
BitSetItr(ArrayList<Value[]> orderedRows, BitSet set) {
super(orderedRows, set);
cursor = set.nextSetBit(0);
}
@Override
public Value[] next() {
if (cursor < 0) {
throw new NoSuchElementException();
}
Value[] result = orderedRows.get(cursor);
cursor = set.nextSetBit(cursor + 1);
return result;
}
}
private final class BitSetReverseItr extends AbstractBitSetItr {
BitSetReverseItr(ArrayList<Value[]> orderedRows, BitSet set) {
super(orderedRows, set);
cursor = set.length() - 1;
}
@Override
public Value[] next() {
if (cursor < 0) {
throw new NoSuchElementException();
}
Value[] result = orderedRows.get(cursor);
cursor = set.previousSetBit(cursor - 1);
return result;
}
}
private final SimpleExtent extent;
private final WindowFrameExclusion exclusion;
/** /**
* Returns iterator in descending order. * Creates new instance of window frame clause.
*
* @param extent
* window frame extent
* @param exclusion
* window frame exclusion
*/
public WindowFrame(SimpleExtent extent, WindowFrameExclusion exclusion) {
this.extent = extent;
this.exclusion = exclusion;
}
/**
* Returns whether window frame specification can be omitted.
*
* @return whether window frame specification can be omitted
*/
public boolean isDefault() {
return extent == SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW
&& exclusion == WindowFrameExclusion.EXCLUDE_NO_OTHERS;
}
/**
* Returns whether window frame specification contains all rows in
* partition.
*
* @return whether window frame specification contains all rows in partition
*/
public boolean isFullPartition() {
return extent == SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_UNBOUNDED_FOLLOWING
&& exclusion == WindowFrameExclusion.EXCLUDE_NO_OTHERS;
}
/**
* Returns iterator.
* *
* @param orderedRows * @param orderedRows
* ordered rows * ordered rows
* @param sortOrder
* sort order
* @param currentRow * @param currentRow
* index of the current row * index of the current row
* @return iterator in descending order * @param reverse
* whether iterator should iterate in reverse order
* @return iterator
*/ */
public Iterator<Value[]> reverseIterator(final ArrayList<Value[]> orderedRows, int currentRow) { public Iterator<Value[]> iterator(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow,
boolean reverse) {
int size = orderedRows.size(); int size = orderedRows.size();
final int startIndex, endIndex; final int startIndex, endIndex;
switch (extent) { switch (extent) {
...@@ -171,29 +302,45 @@ public final class WindowFrame { ...@@ -171,29 +302,45 @@ public final class WindowFrame {
default: default:
throw DbException.getUnsupportedException("window frame extent =" + extent); throw DbException.getUnsupportedException("window frame extent =" + extent);
} }
return new Iterator<Value[]>() { if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
return complexIterator(orderedRows, sortOrder, currentRow, startIndex, endIndex, reverse);
private int cursor = endIndex; }
return reverse ? new PlainReverseItr(orderedRows, startIndex, endIndex)
@Override : new PlainItr(orderedRows, startIndex, endIndex);
public boolean hasNext() {
return cursor >= startIndex;
} }
@Override private Iterator<Value[]> complexIterator(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow,
public Value[] next() { int startIndex, int endIndex, boolean reverse) {
if (cursor < startIndex) { int size = orderedRows.size();
throw new NoSuchElementException(); BitSet set = new BitSet(size);
set.set(startIndex, endIndex + 1);
switch (exclusion) {
case EXCLUDE_CURRENT_ROW:
set.clear(currentRow);
break;
case EXCLUDE_GROUP:
case EXCLUDE_TIES: {
int exStart = currentRow;
Value[] row = orderedRows.get(currentRow);
while (exStart > startIndex && sortOrder.compare(row, orderedRows.get(exStart - 1)) == 0) {
exStart--;
} }
return orderedRows.get(cursor--); int exEnd = currentRow;
while (exEnd < endIndex && sortOrder.compare(row, orderedRows.get(exEnd + 1)) == 0) {
exEnd++;
} }
set.clear(exStart, exEnd + 1);
@Override if (exclusion == WindowFrameExclusion.EXCLUDE_TIES) {
public void remove() { set.set(currentRow);
throw new UnsupportedOperationException();
} }
}
}; //$FALL-THROUGH$
default:
}
if (set.isEmpty()) {
return Collections.emptyIterator();
}
return reverse ? new BitSetReverseItr(orderedRows, set) : new BitSetItr(orderedRows, set);
} }
/** /**
...@@ -203,7 +350,11 @@ public final class WindowFrame { ...@@ -203,7 +350,11 @@ public final class WindowFrame {
* @see org.h2.expression.Expression#getSQL() * @see org.h2.expression.Expression#getSQL()
*/ */
public String getSQL() { public String getSQL() {
return extent.getSQL(); String sql = extent.getSQL();
if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
sql = sql + ' ' + exclusion.getSQL();
}
return sql;
} }
} }
...@@ -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, i), 0, ignoreNulls); v = getNthValue(frame.iterator(ordered, getOverOrderBySort(), i, false), 0, ignoreNulls);
break; break;
} }
case LAST_VALUE: case LAST_VALUE:
v = getNthValue(frame.reverseIterator(ordered, i), 0, ignoreNulls); v = getNthValue(frame.iterator(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 = fromLast ? frame.reverseIterator(ordered, i) : frame.iterator(ordered, i); Iterator<Value[]> iter = frame.iterator(ordered, getOverOrderBySort(), i, fromLast);
v = getNthValue(iter, n, ignoreNulls); v = getNthValue(iter, n, ignoreNulls);
break; break;
} }
......
...@@ -233,8 +233,8 @@ SELECT ...@@ -233,8 +233,8 @@ SELECT
> rows: 6 > rows: 6
SELECT ARRAY_AGG(SUM(ID)) OVER(ORDER /**/ BY ID) FROM TEST GROUP BY ID; SELECT ARRAY_AGG(SUM(ID)) OVER(ORDER /**/ BY ID) FROM TEST GROUP BY ID;
> ARRAY_AGG(SUM(ID)) OVER ( ORDER BY ID) > ARRAY_AGG(SUM(ID)) OVER (ORDER BY ID)
> -------------------------------------- > -------------------------------------
> (1) > (1)
> (1, 2) > (1, 2)
> (1, 2, 3) > (1, 2, 3)
...@@ -245,3 +245,33 @@ SELECT ARRAY_AGG(SUM(ID)) OVER(ORDER /**/ BY ID) FROM TEST GROUP BY ID; ...@@ -245,3 +245,33 @@ SELECT ARRAY_AGG(SUM(ID)) OVER(ORDER /**/ BY ID) FROM TEST GROUP BY ID;
DROP TABLE TEST; DROP TABLE TEST;
> ok > ok
CREATE TABLE TEST(ID INT, G INT);
> ok
INSERT INTO TEST VALUES
(1, 1),
(2, 2),
(3, 2),
(4, 2),
(5, 3);
> update count: 5
SELECT
ARRAY_AGG(ID) OVER (ORDER BY G RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) D,
ARRAY_AGG(ID) OVER (ORDER BY G RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) R,
ARRAY_AGG(ID) OVER (ORDER BY G RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP) G,
ARRAY_AGG(ID) OVER (ORDER BY G RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) T,
ARRAY_AGG(ID) OVER (ORDER BY G RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE NO OTHERS) N
FROM TEST;
> D R G T N
> --------------- ------------ ------------ --------------- ---------------
> (1, 2, 3, 4, 5) (2, 3, 4, 5) (2, 3, 4, 5) (1, 2, 3, 4, 5) (1, 2, 3, 4, 5)
> (1, 2, 3, 4, 5) (1, 3, 4, 5) (1, 5) (1, 2, 5) (1, 2, 3, 4, 5)
> (1, 2, 3, 4, 5) (1, 2, 4, 5) (1, 5) (1, 3, 5) (1, 2, 3, 4, 5)
> (1, 2, 3, 4, 5) (1, 2, 3, 5) (1, 5) (1, 4, 5) (1, 2, 3, 4, 5)
> (1, 2, 3, 4, 5) (1, 2, 3, 4) (1, 2, 3, 4) (1, 2, 3, 4, 5) (1, 2, 3, 4, 5)
> rows (ordered): 5
DROP TABLE TEST;
> ok
...@@ -135,5 +135,16 @@ SELECT *, ...@@ -135,5 +135,16 @@ SELECT *,
> 13 4 null 41 null null > 13 4 null 41 null null
> rows (ordered): 13 > rows (ordered): 13
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 EXCLUDE CURRENT ROW)
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)
> -- -------- ---- ---------------------------------------------------------------------------------------------------------------------
> 1 1 null null
> 2 1 1 null
> 3 1 1 1
> rows (ordered): 3
DROP TABLE TEST; DROP TABLE TEST;
> ok > ok
...@@ -796,4 +796,4 @@ interior envelopes multilinestring multipoint packed exterior normalization awkw ...@@ -796,4 +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 cume reopens preceding unbounded rightly ranks rno dro rko precede cume reopens preceding unbounded rightly itr
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论