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

Merge pull request #1454 from katzyn/window

Add FIRST_VALUE(), LAST_VALUE(), and NTH_VALUE()
......@@ -48,8 +48,8 @@ SELECT * FROM TEST ORDER BY NAME;
SELECT ID, COUNT(*) FROM TEST GROUP BY ID;
SELECT NAME, COUNT(*) FROM TEST GROUP BY NAME HAVING COUNT(*) > 2;
SELECT 'ID' COL, MAX(ID) AS MAX FROM TEST UNION SELECT 'NAME', MAX(NAME) FROM TEST;
SELECT * FROM TEST OFFSET 1000 ROWS FETCH NEXT 1000 ROWS ONLY;
SELECT A, B FROM TEST ORDER BY A FETCH NEXT 10 ROWS WITH TIES;
SELECT * FROM TEST OFFSET 1000 ROWS FETCH FIRST 1000 ROWS ONLY;
SELECT A, B FROM TEST ORDER BY A FETCH FIRST 10 ROWS WITH TIES;
SELECT * FROM (SELECT ID, COUNT(*) FROM TEST
GROUP BY ID UNION SELECT NULL, COUNT(*) FROM TEST)
ORDER BY 1 NULLS LAST;
......@@ -2534,9 +2534,11 @@ SELECT * FROM (VALUES(1, 'Hello'), (2, 'World')) AS V;
"
"Other Grammar","Window specification","
([PARTITION BY expression [,...]] [ORDER BY order [,...]])
([PARTITION BY expression [,...]] [ORDER BY order [,...]]
[windowFrame])
","
A window specification for a window function or aggregate.
Window functions are currently experimental in H2 and should be used with caution.
They also may require a lot of memory for large queries.
","
......@@ -2544,6 +2546,21 @@ They also may require a lot of memory for large queries.
(ORDER BY ID)
(PARTITION BY CATEGORY)
(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","
......@@ -2712,9 +2729,9 @@ INTERVAL '11:12.123' MINUTE TO SECOND
"Other Grammar","Interval","
intervalYear | intervalMonth | intervalDay | intervalHour | intervalMinute
| intervalSecond | intervalYearToMonth | intervalDayToHour
| intervalDayToMinute | intervalDayToSecond | intervalHourToMinute
| intervalHourToSecond | intervalMinuteToSecond
| intervalSecond | intervalYearToMonth | intervalDayToHour
| intervalDayToMinute | intervalDayToSecond | intervalHourToMinute
| intervalHourToSecond | intervalMinuteToSecond
","
An interval literal.
","
......@@ -2723,7 +2740,7 @@ INTERVAL '1-2' YEAR TO MONTH
"Other Grammar","Value","
string | dollarQuotedString | numeric | dateAndTime | boolean | bytes
| interval | array | null
| interval | array | null
","
A literal value of any data type, or null.
","
......@@ -5138,6 +5155,58 @@ SELECT CUME_DIST() OVER (ORDER BY ID), * FROM TEST;
SELECT CUME_DIST() OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST;
"
"Functions (Window)","FIRST_VALUE","
FIRST_VALUE(value) [{RESPECT|IGNORE} NULLS] OVER windowSpecification
","
Returns the first value in a window.
If IGNORE NULLS is specified null values are skipped and the function returns first non-null value, if any.
Window functions are currently experimental in H2 and should be used with caution.
They also may require a lot of memory for large queries.
","
SELECT FIRST_VALUE(X) OVER (ORDER BY ID), * FROM TEST;
SELECT FIRST_VALUE(X) IGNORE NULLS OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST;
"
"Functions (Window)","LAST_VALUE","
LAST_VALUE(value) [{RESPECT|IGNORE} NULLS] OVER windowSpecification
","
Returns the last value in a window.
If IGNORE NULLS is specified null values are skipped and the function returns last non-null value before them, if any;
if there is no non-null value it returns NULL.
Note that the last value is actually a value in the current row if window frame is not specified.
Window functions are currently experimental in H2 and should be used with caution.
They also may require a lot of memory for large queries.
","
SELECT LAST_VALUE(X) OVER (ORDER BY ID), * FROM TEST;
SELECT LAST_VALUE(X) IGNORE NULLS OVER (
PARTITION BY CATEGORY ORDER BY ID
RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
), * FROM TEST;
"
"Functions (Window)","NTH_VALUE","
NTH_VALUE(value, nInt) [FROM {FIRST|LAST}] [{RESPECT|IGNORE} NULLS]
OVER windowSpecification
","
Returns the value in a row with a specified relative number in a window.
Relative row number must be positive.
If FROM LAST is specified rows a counted backwards from the last row.
If IGNORE NULLS is specified rows with null values in selected expression are skipped.
If number of considered rows is less than specified relative number this function returns NULL.
Note that the last row is actually a current row if window frame is not specified.
Window functions are currently experimental in H2 and should be used with caution.
They also may require a lot of memory for large queries.
","
SELECT NTH_VALUE(X) OVER (ORDER BY ID), * FROM TEST;
SELECT NTH_VALUE(X) IGNORE NULLS OVER (
PARTITION BY CATEGORY ORDER BY ID
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
), * FROM TEST;
"
"System Tables","Information Schema","
INFORMATION_SCHEMA
","
......
......@@ -21,6 +21,16 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>PR #1454: Add FIRST_VALUE(), LAST_VALUE(), and NTH_VALUE()
</li>
<li>PR #1453, Issue #1161: Add ROW_NUMBER(), RANK(), DENSE_RANK(), PERCENT_RANK(), and CUME_DIST() window functions
</li>
<li>PR #1452: Reset aggregates before reuse
</li>
<li>PR #1451: Add experimental support for aggregates with OVER (ORDER BY *)
</li>
<li>PR #1450: Evaluate window aggregates only once for each partition
</li>
<li>PR #1449: Move more code from Aggregate and JavaAggregate to AbstractAggregate
</li>
<li>PR #1448: Add experimental implementation of grouped window queries
......@@ -33,6 +43,8 @@ Change Log
</li>
<li>PR #1444: Add experimental unoptimized support for OVER ([PARTITION BY ...]) in aggregates
</li>
<li>PR #1442: Bugfix - Release MVStore lock and file resources rightly even if errors when compacting database
</li>
<li>PR #1441: Add GEOMETRY type subtypes with type and SRID constraints
</li>
<li>PR #1434: Add support for ENUM in CAST and other changes
......
......@@ -60,8 +60,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
DBMS_METADATA.GET_DDL / GET_DEPENDENT_DDL.
</li><li>Clustering: support mixed clustering mode (one embedded, others in server mode).
</li><li>Clustering: reads should be randomly distributed (optional) or to a designated database on RAM (parameter: READ_FROM=3).
</li><li>Window functions: RANK() and DENSE_RANK(), partition using OVER().
select *, count(*) over() as fullCount from ... limit 4;
</li><li>PostgreSQL catalog: use BEFORE SELECT triggers instead of views over metadata tables.
</li><li>Test very large databases and LOBs (up to 256 GB).
</li><li>Store all temp files in the temp directory.
......@@ -87,7 +85,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Index usage for (ID, NAME)=(1, 'Hi'); document.
</li><li>Set a connection read only (Connection.setReadOnly) or using a connection parameter.
</li><li>Access rights: finer grained access control (grant access for specific functions).
</li><li>ROW_NUMBER() OVER([PARTITION BY columnName][ORDER BY columnName]).
</li><li>Version check: docs / web console (using Javascript), and maybe in the library (using TCP/IP).
</li><li>Web server classloader: override findResource / getResourceFrom.
</li><li>Cost for embedded temporary view is calculated wrong, if result is constant.
......@@ -120,7 +117,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>The HELP information schema can be directly exposed in the Console.
</li><li>Maybe use the 0x1234 notation for binary fields, see MS SQL Server.
</li><li>Support Oracle CONNECT BY in some way: http://www.adp-gmbh.ch/ora/sql/connect_by.html http://philip.greenspun.com/sql/trees.html
</li><li>SQL Server 2005, Oracle: support COUNT(*) OVER(). See http://www.orafusion.com/art_anlytc.htm
</li><li>SQL 2003: http://www.wiscorp.com/sql_2003_standard.zip
</li><li>Version column (number/sequence and timestamp based).
</li><li>Test and document UPDATE TEST SET (ID, NAME) = (SELECT ID*10, NAME || '!' FROM TEST T WHERE T.ID=TEST.ID).
......@@ -413,7 +409,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Issue 196: Function based indexes
</li><li>Fix the disk space leak (killing the process at the exact right moment will increase
the disk space usage; this space is not re-used). See TestDiskSpaceLeak.java
</li><li>ROWNUM: Oracle compatibility when used within a subquery. Issue 198.
</li><li>Allow to access the database over HTTP (possibly using port 80) and a servlet in a REST way.
</li><li>ODBC: encrypted databases are not supported because the ;CIPHER= can not be set.
</li><li>Support CLOB and BLOB update, specially conn.createBlob().setBinaryStream(1);
......
......@@ -175,6 +175,9 @@ 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.WindowFrame;
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.WindowFunctionType;
import org.h2.index.Index;
......@@ -3061,8 +3064,24 @@ public class Parser {
} else if (!isAggregate) {
orderBy = new ArrayList<>(0);
}
WindowFrame 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 = new WindowFrame(SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW,
WindowFrameExclusion.EXCLUDE_NO_OTHERS);
}
} 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 +3091,49 @@ public class Parser {
}
}
private WindowFrame readWindowFrame() {
SimpleExtent extent;
WindowFrameExclusion exclusion = WindowFrameExclusion.EXCLUDE_NO_OTHERS;
if (readIf("RANGE")) {
read("BETWEEN");
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("AND");
read("UNBOUNDED");
read("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 {
extent = SimpleExtent.RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW;
}
return new WindowFrame(extent, exclusion);
}
private AggregateType getAggregateType(String name) {
if (!identifiersToUpper) {
// if not yet converted to uppercase, do it now
......@@ -3270,12 +3332,51 @@ public class Parser {
if (currentSelect == null) {
throw getSyntaxError();
}
int numArgs = WindowFunction.getArgumentCount(type);
Expression[] args = null;
if (numArgs > 0) {
args = new Expression[numArgs];
for (int i = 0; i < numArgs; i++) {
if (i > 0) {
read(COMMA);
}
args[i] = readExpression();
}
}
read(CLOSE_PAREN);
WindowFunction function = new WindowFunction(type, currentSelect);
WindowFunction function = new WindowFunction(type, currentSelect, args);
if (type == WindowFunctionType.NTH_VALUE) {
readFromFirstOrLast(function);
}
switch (type) {
case FIRST_VALUE:
case LAST_VALUE:
case NTH_VALUE:
readRespectOrIgnoreNulls(function);
//$FALL-THROUGH$
default:
// Avoid warning
}
readFilterAndOver(function);
return function;
}
private void readFromFirstOrLast(WindowFunction function) {
if (readIf(FROM) && !readIf("FIRST")) {
read("LAST");
function.setFromLast(true);
}
}
private void readRespectOrIgnoreNulls(WindowFunction function) {
if (readIf("RESPECT")) {
read("NULLS");
} else if (readIf("IGNORE")) {
read("NULLS");
function.setIgnoreNulls(true);
}
}
private Expression readFunctionWithoutParameters(String name) {
if (database.isAllowBuiltinAliasOverride()) {
FunctionAlias functionAlias = database.getSchema(session.getCurrentSchemaName()).findFunction(name);
......
......@@ -8,6 +8,7 @@ package org.h2.expression.aggregate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import org.h2.api.ErrorCode;
import org.h2.command.dml.Select;
......@@ -113,6 +114,7 @@ public abstract class AbstractAggregate extends Expression {
@Override
public Expression optimize(Session session) {
if (over != null) {
over.optimize(session);
ArrayList<SelectOrderBy> orderBy = over.getOrderBy();
if (orderBy != null) {
overOrderBySort = createOrder(session, orderBy, getNumExpressions());
......@@ -423,11 +425,33 @@ public abstract class AbstractAggregate extends Expression {
*/
protected void getOrderedResultLoop(Session session, HashMap<Integer, Value> result, ArrayList<Value[]> ordered,
int rowIdColumn) {
WindowFrame frame = over.getWindowFrame();
if (frame.isDefault()) {
Object aggregateData = createAggregateData();
for (Value[] row : ordered) {
updateFromExpressions(session, aggregateData, row);
result.put(row[rowIdColumn].getInt(), getAggregatedValue(session, aggregateData));
}
} else if (frame.isFullPartition()) {
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);
}
} else {
int size = ordered.size();
for (int i = 0; i < size; i++) {
Object aggregateData = createAggregateData();
for (Iterator<Value[]> iter = frame.iterator(ordered, getOverOrderBySort(), i, false); iter
.hasNext();) {
updateFromExpressions(session, aggregateData, iter.next());
}
result.put(ordered.get(i)[rowIdColumn].getInt(), getAggregatedValue(session, aggregateData));
}
}
}
protected StringBuilder appendTailConditions(StringBuilder builder) {
......
......@@ -26,6 +26,8 @@ public final class Window {
private final ArrayList<SelectOrderBy> orderBy;
private final WindowFrame frame;
/**
* @param builder
* string builder
......@@ -34,7 +36,10 @@ public final class Window {
*/
static void appendOrderBy(StringBuilder builder, ArrayList<SelectOrderBy> orderBy) {
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++) {
SelectOrderBy o = orderBy.get(i);
if (i > 0) {
......@@ -53,10 +58,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, WindowFrame frame) {
this.partitionBy = partitionBy;
this.orderBy = orderBy;
this.frame = frame;
}
/**
......@@ -81,6 +89,25 @@ public final class Window {
}
}
/**
* Try to optimize the window conditions.
*
* @param session
* the session
*/
public void optimize(Session session) {
if (partitionBy != null) {
for (int i = 0; i < partitionBy.size(); i++) {
partitionBy.set(i, partitionBy.get(i).optimize(session));
}
}
if (orderBy != null) {
for (SelectOrderBy o : orderBy) {
o.expression = o.expression.optimize(session);
}
}
}
/**
* Tell the expression columns whether the table filter can return values
* now. This is used when optimizing the query.
......@@ -113,6 +140,15 @@ public final class Window {
return orderBy;
}
/**
* Returns window frame.
*
* @return window frame
*/
public WindowFrame getWindowFrame() {
return frame;
}
/**
* Returns the key for the current group.
*
......@@ -155,6 +191,9 @@ public final class Window {
}
}
appendOrderBy(builder, orderBy);
if (!frame.isDefault()) {
builder.append(' ').append(frame.getSQL());
}
return builder.append(')').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 java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.h2.message.DbException;
import org.h2.result.SortOrder;
import org.h2.value.Value;
/**
* Window frame clause.
*/
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[]> {
final ArrayList<Value[]> orderedRows;
Itr(ArrayList<Value[]> orderedRows) {
this.orderedRows = orderedRows;
}
@Override
public final void remove() {
throw new UnsupportedOperationException();
}
}
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
public boolean hasNext() {
return cursor <= endIndex;
}
@Override
public Value[] next() {
if (cursor > endIndex) {
throw new NoSuchElementException();
}
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
public boolean hasNext() {
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;
/**
* 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
* ordered rows
* @param sortOrder
* sort order
* @param currentRow
* index of the current row
* @param reverse
* whether iterator should iterate in reverse order
* @return iterator
*/
public Iterator<Value[]> iterator(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow,
boolean reverse) {
int size = orderedRows.size();
final int startIndex, endIndex;
switch (extent) {
case RANGE_BETWEEN_UNBOUNDED_PRECEDING_AND_CURRENT_ROW:
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;
endIndex = size - 1;
break;
default:
throw DbException.getUnsupportedException("window frame extent =" + extent);
}
if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
return complexIterator(orderedRows, sortOrder, currentRow, startIndex, endIndex, reverse);
}
return reverse ? new PlainReverseItr(orderedRows, startIndex, endIndex)
: new PlainItr(orderedRows, startIndex, endIndex);
}
private Iterator<Value[]> complexIterator(ArrayList<Value[]> orderedRows, SortOrder sortOrder, int currentRow,
int startIndex, int endIndex, boolean reverse) {
int size = orderedRows.size();
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--;
}
int exEnd = currentRow;
while (exEnd < endIndex && sortOrder.compare(row, orderedRows.get(exEnd + 1)) == 0) {
exEnd++;
}
set.clear(exStart, exEnd + 1);
if (exclusion == WindowFrameExclusion.EXCLUDE_TIES) {
set.set(currentRow);
}
}
//$FALL-THROUGH$
default:
}
if (set.isEmpty()) {
return Collections.emptyIterator();
}
return reverse ? new BitSetReverseItr(orderedRows, set) : new BitSetItr(orderedRows, set);
}
/**
* Returns SQL representation.
*
* @return SQL representation.
* @see org.h2.expression.Expression#getSQL()
*/
public String getSQL() {
String sql = extent.getSQL();
if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
sql = sql + ' ' + exclusion.getSQL();
}
return sql;
}
}
......@@ -7,13 +7,18 @@ package org.h2.expression.aggregate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import org.h2.command.dml.Select;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.message.DbException;
import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
import org.h2.value.Value;
import org.h2.value.ValueDouble;
import org.h2.value.ValueInt;
import org.h2.value.ValueNull;
/**
* A window function.
......@@ -50,6 +55,21 @@ public class WindowFunction extends AbstractAggregate {
*/
CUME_DIST,
/**
* The type for FIRST_VALUE() window function.
*/
FIRST_VALUE,
/**
* The type for LAST_VALUE() window function.
*/
LAST_VALUE,
/**
* The type for NTH_VALUE() window function.
*/
NTH_VALUE,
;
/**
......@@ -62,15 +82,21 @@ public class WindowFunction extends AbstractAggregate {
public static WindowFunctionType get(String name) {
switch (name) {
case "ROW_NUMBER":
return WindowFunctionType.ROW_NUMBER;
return ROW_NUMBER;
case "RANK":
return RANK;
case "DENSE_RANK":
return WindowFunctionType.DENSE_RANK;
return DENSE_RANK;
case "PERCENT_RANK":
return WindowFunctionType.PERCENT_RANK;
return PERCENT_RANK;
case "CUME_DIST":
return WindowFunctionType.CUME_DIST;
return CUME_DIST;
case "FIRST_VALUE":
return FIRST_VALUE;
case "LAST_VALUE":
return LAST_VALUE;
case "NTH_VALUE":
return NTH_VALUE;
default:
return null;
}
......@@ -80,6 +106,46 @@ public class WindowFunction extends AbstractAggregate {
private final WindowFunctionType type;
private final Expression[] args;
private boolean fromLast;
private boolean ignoreNulls;
/**
* Returns number of arguments for the specified type.
*
* @param type
* the type of a window function
* @return number of arguments
*/
public static int getArgumentCount(WindowFunctionType type) {
switch (type) {
case FIRST_VALUE:
case LAST_VALUE:
return 1;
case NTH_VALUE:
return 2;
default:
return 0;
}
}
private static Value getNthValue(Iterator<Value[]> iterator, int number, boolean ignoreNulls) {
Value v = ValueNull.INSTANCE;
int cnt = 0;
while (iterator.hasNext()) {
Value t = iterator.next()[0];
if (!ignoreNulls || t != ValueNull.INSTANCE) {
if (cnt++ == number) {
v = t;
break;
}
}
}
return v;
}
/**
* Creates new instance of a window function.
*
......@@ -87,10 +153,42 @@ public class WindowFunction extends AbstractAggregate {
* the type
* @param select
* the select statement
* @param args
* arguments, or null
*/
public WindowFunction(WindowFunctionType type, Select select) {
public WindowFunction(WindowFunctionType type, Select select, Expression[] args) {
super(select, false);
this.type = type;
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.
*
* @param fromLast
* whether FROM LAST clause was specified.
*/
public void setFromLast(boolean fromLast) {
this.fromLast = fromLast;
}
/**
* Sets RESPECT NULLS or IGNORE NULLS clause value.
*
* @param ignoreNulls
* whether IGNORE NULLS clause was specified
*/
public void setIgnoreNulls(boolean ignoreNulls) {
this.ignoreNulls = ignoreNulls;
}
@Override
......@@ -105,17 +203,24 @@ public class WindowFunction extends AbstractAggregate {
@Override
protected void updateGroupAggregates(Session session, int stage) {
// Nothing to do
if (args != null) {
for (Expression expr : args) {
expr.updateAggregate(session, stage);
}
}
}
@Override
protected int getNumExpressions() {
return 0;
return getArgumentCount(type);
}
@Override
protected void rememberExpressions(Session session, Value[] array) {
// Nothing to do
int cnt = getNumExpressions();
for (int i = 0; i < cnt; i++) {
array[i] = args[i].getValue(session);
}
}
@Override
......@@ -131,9 +236,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;
......@@ -200,11 +312,74 @@ 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++) {
WindowFrame frame = over.getWindowFrame();
Value[] row = ordered.get(i);
int rowId = row[rowIdColumn].getInt();
Value v;
switch (type) {
case FIRST_VALUE: {
v = getNthValue(frame.iterator(ordered, getOverOrderBySort(), i, false), 0, ignoreNulls);
break;
}
case LAST_VALUE:
v = getNthValue(frame.iterator(ordered, getOverOrderBySort(), i, true), 0, ignoreNulls);
break;
case NTH_VALUE: {
int n = row[1].getInt();
if (n <= 0) {
throw DbException.getInvalidValueException("nth row", n);
}
n--;
Iterator<Value[]> iter = frame.iterator(ordered, getOverOrderBySort(), i, fromLast);
v = getNthValue(iter, n, 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");
}
@Override
public void mapColumns(ColumnResolver resolver, int level) {
if (args != null) {
for (Expression arg : args) {
arg.mapColumns(resolver, level);
}
}
super.mapColumns(resolver, level);
}
@Override
public Expression optimize(Session session) {
super.optimize(session);
if (args != null) {
for (int i = 0; i < args.length; i++) {
args[i] = args[i].optimize(session);
}
}
return this;
}
@Override
public void setEvaluatable(TableFilter tableFilter, boolean b) {
if (args != null) {
for (Expression e : args) {
e.setEvaluatable(tableFilter, b);
}
}
super.setEvaluatable(tableFilter, b);
}
@Override
public int getType() {
switch (type) {
......@@ -215,6 +390,10 @@ public class WindowFunction extends AbstractAggregate {
case PERCENT_RANK:
case CUME_DIST:
return Value.DOUBLE;
case FIRST_VALUE:
case LAST_VALUE:
case NTH_VALUE:
return args[0].getType();
default:
throw DbException.throwInternalError("type=" + type);
}
......@@ -222,8 +401,15 @@ public class WindowFunction extends AbstractAggregate {
@Override
public int getScale() {
switch (type) {
case FIRST_VALUE:
case LAST_VALUE:
case NTH_VALUE:
return args[0].getScale();
default:
return 0;
}
}
@Override
public long getPrecision() {
......@@ -235,6 +421,10 @@ public class WindowFunction extends AbstractAggregate {
case PERCENT_RANK:
case CUME_DIST:
return ValueDouble.PRECISION;
case FIRST_VALUE:
case LAST_VALUE:
case NTH_VALUE:
return args[0].getPrecision();
default:
throw DbException.throwInternalError("type=" + type);
}
......@@ -250,6 +440,10 @@ public class WindowFunction extends AbstractAggregate {
case PERCENT_RANK:
case CUME_DIST:
return ValueDouble.DISPLAY_SIZE;
case FIRST_VALUE:
case LAST_VALUE:
case NTH_VALUE:
return args[0].getDisplaySize();
default:
throw DbException.throwInternalError("type=" + type);
}
......@@ -258,6 +452,7 @@ public class WindowFunction extends AbstractAggregate {
@Override
public String getSQL() {
String text;
int numArgs = 0;
switch (type) {
case ROW_NUMBER:
text = "ROW_NUMBER";
......@@ -274,16 +469,46 @@ public class WindowFunction extends AbstractAggregate {
case CUME_DIST:
text = "CUME_DIST";
break;
case FIRST_VALUE:
text = "FIRST_VALUE";
numArgs = 1;
break;
case LAST_VALUE:
text = "LAST_VALUE";
numArgs = 1;
break;
case NTH_VALUE:
text = "NTH_VALUE";
numArgs = 2;
break;
default:
throw DbException.throwInternalError("type=" + type);
}
StringBuilder builder = new StringBuilder().append(text).append("()");
StringBuilder builder = new StringBuilder().append(text).append('(');
for (int i = 0; i < numArgs; i++) {
if (i > 0) {
builder.append(", ");
}
builder.append(args[i].getSQL());
}
builder.append(')');
if (fromLast && type == WindowFunctionType.NTH_VALUE) {
builder.append(" FROM LAST");
}
if (ignoreNulls && (type == WindowFunctionType.FIRST_VALUE || type == WindowFunctionType.LAST_VALUE)) {
builder.append(" IGNORE NULLS");
}
return appendTailConditions(builder).toString();
}
@Override
public int getCost() {
int cost = 1;
if (args != null) {
for (Expression expr : args) {
cost += expr.getCost();
}
}
return cost;
}
......
......@@ -179,7 +179,7 @@ public class TestScript extends TestDb {
"parsedatetime", "quarter", "second", "truncate", "week", "year", "date_trunc" }) {
testScript("functions/timeanddate/" + s + ".sql");
}
for (String s : new String[] { "row_number" }) {
for (String s : new String[] { "row_number", "nth_value" }) {
testScript("functions/window/" + s + ".sql");
}
......
......@@ -233,8 +233,8 @@ SELECT
> rows: 6
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, 2)
> (1, 2, 3)
......@@ -245,3 +245,33 @@ SELECT ARRAY_AGG(SUM(ID)) OVER(ORDER /**/ BY ID) FROM TEST GROUP BY ID;
DROP TABLE TEST;
> 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
......@@ -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
-- 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
--
SELECT FIRST_VALUE(1) OVER (PARTITION BY ID);
> exception COLUMN_NOT_FOUND_1
SELECT FIRST_VALUE(1) OVER (ORDER BY ID);
> exception COLUMN_NOT_FOUND_1
CREATE TABLE TEST (ID INT PRIMARY KEY, CATEGORY INT, VALUE INT);
> ok
INSERT INTO TEST VALUES
(1, 1, NULL),
(2, 1, 12),
(3, 1, NULL),
(4, 1, 13),
(5, 1, NULL),
(6, 1, 13),
(7, 2, 21),
(8, 2, 22),
(9, 3, 31),
(10, 3, 32),
(11, 3, 33),
(12, 4, 41),
(13, 4, NULL);
> update count: 13
SELECT *,
FIRST_VALUE(VALUE) OVER (ORDER BY ID) FIRST,
FIRST_VALUE(VALUE) RESPECT NULLS OVER (ORDER BY ID) FIRST_N,
FIRST_VALUE(VALUE) IGNORE NULLS OVER (ORDER BY ID) FIRST_NN,
LAST_VALUE(VALUE) OVER (ORDER BY ID) LAST,
LAST_VALUE(VALUE) RESPECT NULLS OVER (ORDER BY ID) LAST_N,
LAST_VALUE(VALUE) IGNORE NULLS OVER (ORDER BY ID) LAST_NN
FROM TEST FETCH FIRST 6 ROWS ONLY;
> ID CATEGORY VALUE FIRST FIRST_N FIRST_NN LAST LAST_N LAST_NN
> -- -------- ----- ----- ------- -------- ---- ------ -------
> 1 1 null null null null null null null
> 2 1 12 null null 12 12 12 12
> 3 1 null null null 12 null null 12
> 4 1 13 null null 12 13 13 13
> 5 1 null null null 12 null null 13
> 6 1 13 null null 12 13 13 13
> rows (ordered): 6
SELECT *,
FIRST_VALUE(VALUE) OVER (ORDER BY ID) FIRST,
FIRST_VALUE(VALUE) RESPECT NULLS OVER (ORDER BY ID) FIRST_N,
FIRST_VALUE(VALUE) IGNORE NULLS OVER (ORDER BY ID) FIRST_NN,
LAST_VALUE(VALUE) OVER (ORDER BY ID) LAST,
LAST_VALUE(VALUE) RESPECT NULLS OVER (ORDER BY ID) LAST_N,
LAST_VALUE(VALUE) IGNORE NULLS OVER (ORDER BY ID) LAST_NN
FROM TEST WHERE ID > 1 FETCH FIRST 3 ROWS ONLY;
> ID CATEGORY VALUE FIRST FIRST_N FIRST_NN LAST LAST_N LAST_NN
> -- -------- ----- ----- ------- -------- ---- ------ -------
> 2 1 12 12 12 12 12 12 12
> 3 1 null 12 12 12 null null 12
> 4 1 13 12 12 12 13 13 13
> rows (ordered): 3
SELECT *,
NTH_VALUE(VALUE, 2) OVER (ORDER BY ID) NTH,
NTH_VALUE(VALUE, 2) FROM FIRST OVER (ORDER BY ID) NTH_FF,
NTH_VALUE(VALUE, 2) FROM LAST OVER (ORDER BY ID) NTH_FL,
NTH_VALUE(VALUE, 2) RESPECT NULLS OVER (ORDER BY ID) NTH_N,
NTH_VALUE(VALUE, 2) FROM FIRST RESPECT NULLS OVER (ORDER BY ID) NTH_FF_N,
NTH_VALUE(VALUE, 2) FROM LAST RESPECT NULLS OVER (ORDER BY ID) NTH_FL_N,
NTH_VALUE(VALUE, 2) IGNORE NULLS OVER (ORDER BY ID) NTH_NN,
NTH_VALUE(VALUE, 2) FROM FIRST IGNORE NULLS OVER (ORDER BY ID) NTH_FF_NN,
NTH_VALUE(VALUE, 2) FROM LAST IGNORE NULLS OVER (ORDER BY ID) NTH_FL_NN
FROM TEST FETCH FIRST 6 ROWS ONLY;
> ID CATEGORY VALUE NTH NTH_FF NTH_FL NTH_N NTH_FF_N NTH_FL_N NTH_NN NTH_FF_NN NTH_FL_NN
> -- -------- ----- ---- ------ ------ ----- -------- -------- ------ --------- ---------
> 1 1 null null null null null null null null null null
> 2 1 12 12 12 null 12 12 null null null null
> 3 1 null 12 12 12 12 12 12 null null null
> 4 1 13 12 12 null 12 12 null 13 13 12
> 5 1 null 12 12 13 12 12 13 13 13 12
> 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
SELECT *,
FIRST_VALUE(VALUE) OVER (PARTITION BY CATEGORY ORDER BY ID) FIRST,
LAST_VALUE(VALUE) OVER (PARTITION BY CATEGORY ORDER BY ID) LAST,
NTH_VALUE(VALUE, 2) OVER (PARTITION BY CATEGORY ORDER BY ID) NTH
FROM TEST;
> ID CATEGORY VALUE FIRST LAST NTH
> -- -------- ----- ----- ---- ----
> 1 1 null null null null
> 2 1 12 null 12 12
> 3 1 null null null 12
> 4 1 13 null 13 12
> 5 1 null null null 12
> 6 1 13 null 13 12
> 7 2 21 21 21 null
> 8 2 22 21 22 22
> 9 3 31 31 31 null
> 10 3 32 31 32 32
> 11 3 33 31 33 32
> 12 4 41 41 41 null
> 13 4 null 41 null null
> 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;
> ok
......@@ -796,4 +796,4 @@ interior envelopes multilinestring multipoint packed exterior normalization awkw
xym normalizes coord setz xyzm geometrycollection multipolygon mixup rings polygons rejection finite
pointzm pointz pointm dimensionality redefine forum measures
mpg casted pzm mls constrained subtypes complains
ranks rno dro rko precede cume
ranks rno dro rko precede cume reopens preceding unbounded rightly itr
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论