提交 143ae74b authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Add QUALIFY clause

上级 bb20e77b
...@@ -8,6 +8,7 @@ SELECT [ TOP term [ PERCENT ] [ WITH TIES ] ] ...@@ -8,6 +8,7 @@ SELECT [ TOP term [ PERCENT ] [ WITH TIES ] ]
selectExpression [,...] selectExpression [,...]
FROM tableExpression [,...] [ WHERE expression ] FROM tableExpression [,...] [ WHERE expression ]
[ GROUP BY expression [,...] ] [ HAVING expression ] [ GROUP BY expression [,...] ] [ HAVING expression ]
[ QUALIFY expression ]
[ WINDOW { { windowName AS windowSpecification } [,...] } ] [ WINDOW { { windowName AS windowSpecification } [,...] } ]
[ { UNION [ ALL ] | MINUS | EXCEPT | INTERSECT } select ] [ { UNION [ ALL ] | MINUS | EXCEPT | INTERSECT } select ]
[ ORDER BY order [,...] ] [ ORDER BY order [,...] ]
...@@ -19,7 +20,8 @@ FROM tableExpression [,...] [ WHERE expression ] ...@@ -19,7 +20,8 @@ FROM tableExpression [,...] [ WHERE expression ]
"," ","
Selects data from a table or multiple tables. Selects data from a table or multiple tables.
GROUP BY groups the result by the given expression(s). GROUP BY groups the result by the given expression(s).
HAVING filters rows after grouping. HAVING filters rows after grouping and evaluation of aggregate functions.
QUALIFY filters rows after evaluation of window functions.
ORDER BY sorts the result by the given column(s) or expression(s). ORDER BY sorts the result by the given column(s) or expression(s).
UNION combines the result of this query with the results of another query. UNION combines the result of this query with the results of another query.
......
...@@ -480,9 +480,9 @@ CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, DISTINCT, EXCEPT, ...@@ -480,9 +480,9 @@ CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, DISTINCT, EXCEPT,
EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, HAVING, EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, HAVING,
IF, INNER, INTERSECT, INTERSECTS, INTERVAL, IS, JOIN, LIKE, IF, INNER, INTERSECT, INTERSECTS, INTERVAL, IS, JOIN, LIKE,
LIMIT, LOCALTIME, LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL, LIMIT, LOCALTIME, LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL,
OFFSET, ON, ORDER, PRIMARY, ROW, ROWNUM, SELECT, SYSDATE, OFFSET, ON, ORDER, PRIMARY, QUALIFY, ROW, ROWNUM, SELECT,
SYSTIME, SYSTIMESTAMP, TABLE, TODAY, TOP, TRUE, UNION, UNIQUE, SYSDATE, SYSTIME, SYSTIMESTAMP, TABLE, TODAY, TOP, TRUE, UNION,
VALUES, WHERE, WINDOW, WITH UNIQUE, VALUES, WHERE, WINDOW, WITH
</code> </code>
</p><p> </p><p>
Certain words of this list are keywords because they are functions that can be used without '()', Certain words of this list are keywords because they are functions that can be used without '()',
......
...@@ -49,6 +49,7 @@ import static org.h2.util.ParserUtil.OFFSET; ...@@ -49,6 +49,7 @@ import static org.h2.util.ParserUtil.OFFSET;
import static org.h2.util.ParserUtil.ON; import static org.h2.util.ParserUtil.ON;
import static org.h2.util.ParserUtil.ORDER; import static org.h2.util.ParserUtil.ORDER;
import static org.h2.util.ParserUtil.PRIMARY; import static org.h2.util.ParserUtil.PRIMARY;
import static org.h2.util.ParserUtil.QUALIFY;
import static org.h2.util.ParserUtil.ROW; import static org.h2.util.ParserUtil.ROW;
import static org.h2.util.ParserUtil.ROWNUM; import static org.h2.util.ParserUtil.ROWNUM;
import static org.h2.util.ParserUtil.SELECT; import static org.h2.util.ParserUtil.SELECT;
...@@ -507,6 +508,8 @@ public class Parser { ...@@ -507,6 +508,8 @@ public class Parser {
"ORDER", "ORDER",
// PRIMARY // PRIMARY
"PRIMARY", "PRIMARY",
// QUALIFY
"QUALIFY",
// ROW // ROW
"ROW", "ROW",
// ROWNUM // ROWNUM
...@@ -2748,6 +2751,11 @@ public class Parser { ...@@ -2748,6 +2751,11 @@ public class Parser {
Expression condition = readExpression(); Expression condition = readExpression();
command.setHaving(condition); command.setHaving(condition);
} }
if (readIf(QUALIFY)) {
command.setWindowQuery();
Expression condition = readExpression();
command.setQualify(condition);
}
if (readIf(WINDOW)) { if (readIf(WINDOW)) {
do { do {
int index = parseIndex; int index = parseIndex;
......
...@@ -77,9 +77,21 @@ public class Select extends Query { ...@@ -77,9 +77,21 @@ public class Select extends Query {
private final ArrayList<TableFilter> filters = Utils.newSmallArrayList(); private final ArrayList<TableFilter> filters = Utils.newSmallArrayList();
private final ArrayList<TableFilter> topFilters = Utils.newSmallArrayList(); private final ArrayList<TableFilter> topFilters = Utils.newSmallArrayList();
private Expression having; /**
* WHERE condition.
*/
private Expression condition; private Expression condition;
/**
* HAVING condition.
*/
private Expression having;
/**
* QUALIFY condition.
*/
private Expression qualify;
/** /**
* The visible columns (the ones required in the result). * The visible columns (the ones required in the result).
*/ */
...@@ -112,6 +124,8 @@ public class Select extends Query { ...@@ -112,6 +124,8 @@ public class Select extends Query {
private int havingIndex; private int havingIndex;
private int qualifyIndex;
private int[] groupByCopies; private int[] groupByCopies;
/** /**
...@@ -523,6 +537,9 @@ public class Select extends Query { ...@@ -523,6 +537,9 @@ public class Select extends Query {
if (withHaving && isHavingNullOrFalse(row)) { if (withHaving && isHavingNullOrFalse(row)) {
continue; continue;
} }
if (qualifyIndex >= 0 && !row[qualifyIndex].getBoolean()) {
continue;
}
if (quickOffset && offset > 0) { if (quickOffset && offset > 0) {
offset--; offset--;
continue; continue;
...@@ -1064,6 +1081,13 @@ public class Select extends Query { ...@@ -1064,6 +1081,13 @@ public class Select extends Query {
} else { } else {
havingIndex = -1; havingIndex = -1;
} }
if (qualify != null) {
expressions.add(qualify);
qualifyIndex = expressions.size() - 1;
qualify = null;
} else {
qualifyIndex = -1;
}
if (withTies && !hasOrder()) { if (withTies && !hasOrder()) {
throw DbException.get(ErrorCode.WITH_TIES_WITHOUT_ORDER_BY); throw DbException.get(ErrorCode.WITH_TIES_WITHOUT_ORDER_BY);
...@@ -1131,12 +1155,17 @@ public class Select extends Query { ...@@ -1131,12 +1155,17 @@ public class Select extends Query {
for (TableFilter f : filters) { for (TableFilter f : filters) {
mapColumns(f, 0); mapColumns(f, 0);
} }
if (havingIndex >= 0) { mapCondition(havingIndex);
Expression expr = expressions.get(havingIndex); mapCondition(qualifyIndex);
checkInit = true;
}
private void mapCondition(int index) {
if (index >= 0) {
Expression expr = expressions.get(index);
SelectListColumnResolver res = new SelectListColumnResolver(this); SelectListColumnResolver res = new SelectListColumnResolver(this);
expr.mapColumns(res, 0, Expression.MAP_INITIAL); expr.mapColumns(res, 0, Expression.MAP_INITIAL);
} }
checkInit = true;
} }
private int mergeGroupByExpressions(Database db, int index, ArrayList<String> expressionSQL, boolean scanPrevious) private int mergeGroupByExpressions(Database db, int index, ArrayList<String> expressionSQL, boolean scanPrevious)
...@@ -1505,18 +1534,8 @@ public class Select extends Query { ...@@ -1505,18 +1534,8 @@ public class Select extends Query {
g.getUnenclosedSQL(buff.builder()); g.getUnenclosedSQL(buff.builder());
} }
} }
if (having != null) { getFilterSQL(buff, "\nHAVING ", exprList, having, havingIndex);
// could be set in addGlobalCondition getFilterSQL(buff, "\nQUALIFY ", exprList, qualify, qualifyIndex);
// in this case the query is not run directly, just getPlanSQL is
// called
Expression h = having;
buff.append("\nHAVING ");
h.getUnenclosedSQL(buff.builder());
} else if (havingIndex >= 0) {
Expression h = exprList[havingIndex];
buff.append("\nHAVING ");
h.getUnenclosedSQL(buff.builder());
}
if (sort != null) { if (sort != null) {
buff.append("\nORDER BY ").append( buff.append("\nORDER BY ").append(
sort.getSQL(exprList, visibleColumnCount)); sort.getSQL(exprList, visibleColumnCount));
...@@ -1555,6 +1574,17 @@ public class Select extends Query { ...@@ -1555,6 +1574,17 @@ public class Select extends Query {
return buff.toString(); return buff.toString();
} }
private static void getFilterSQL(StatementBuilder buff, String sql, Expression[] exprList, Expression condition,
int conditionIndex) {
if (condition != null) {
buff.append(sql);
condition.getUnenclosedSQL(buff.builder());
} else if (conditionIndex >= 0) {
buff.append(sql);
exprList[conditionIndex].getUnenclosedSQL(buff.builder());
}
}
public void setHaving(Expression having) { public void setHaving(Expression having) {
this.having = having; this.having = having;
} }
...@@ -1563,6 +1593,14 @@ public class Select extends Query { ...@@ -1563,6 +1593,14 @@ public class Select extends Query {
return having; return having;
} }
public void setQualify(Expression qualify) {
this.qualify = qualify;
}
public Expression getQualify() {
return qualify;
}
@Override @Override
public int getColumnCount() { public int getColumnCount() {
return visibleColumnCount; return visibleColumnCount;
...@@ -1697,6 +1735,9 @@ public class Select extends Query { ...@@ -1697,6 +1735,9 @@ public class Select extends Query {
if (having != null) { if (having != null) {
having.updateAggregate(s, stage); having.updateAggregate(s, stage);
} }
if (qualify != null) {
qualify.updateAggregate(s, stage);
}
} }
@Override @Override
...@@ -1748,6 +1789,9 @@ public class Select extends Query { ...@@ -1748,6 +1789,9 @@ public class Select extends Query {
if (having != null && !having.isEverything(v2)) { if (having != null && !having.isEverything(v2)) {
return false; return false;
} }
if (qualify != null && !qualify.isEverything(v2)) {
return false;
}
return true; return true;
} }
......
...@@ -1542,7 +1542,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements ...@@ -1542,7 +1542,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements
* table/column/index name, in addition to the SQL-2003 keywords. The list * table/column/index name, in addition to the SQL-2003 keywords. The list
* returned is: * returned is:
* <pre> * <pre>
* INTERSECTS,LIMIT,MINUS,OFFSET,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP * INTERSECTS,LIMIT,MINUS,OFFSET,QUALIFY,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP
* </pre> * </pre>
* The complete list of keywords (including SQL-2003 keywords) is: * The complete list of keywords (including SQL-2003 keywords) is:
* <pre> * <pre>
...@@ -1551,9 +1551,9 @@ public class JdbcDatabaseMetaData extends TraceObject implements ...@@ -1551,9 +1551,9 @@ public class JdbcDatabaseMetaData extends TraceObject implements
* EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, HAVING, * EXISTS, FALSE, FETCH, FOR, FOREIGN, FROM, FULL, GROUP, HAVING,
* IF, INNER, INTERSECT, INTERSECTS, INTERVAL, IS, JOIN, LIKE, * IF, INNER, INTERSECT, INTERSECTS, INTERVAL, IS, JOIN, LIKE,
* LIMIT, LOCALTIME, LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL, * LIMIT, LOCALTIME, LOCALTIMESTAMP, MINUS, NATURAL, NOT, NULL,
* OFFSET, ON, ORDER, PRIMARY, ROW, ROWNUM, SELECT, SYSDATE, * OFFSET, ON, ORDER, PRIMARY, QUALIFY, ROW, ROWNUM, SELECT,
* SYSTIME, SYSTIMESTAMP, TABLE, TODAY, TOP, TRUE, UNION, UNIQUE, * SYSDATE, SYSTIME, SYSTIMESTAMP, TABLE, TODAY, TOP, TRUE, UNION,
* VALUES, WHERE, WINDOW, WITH * UNIQUE, VALUES, WHERE, WINDOW, WITH
* </pre> * </pre>
* *
* @return a list of additional the keywords * @return a list of additional the keywords
...@@ -1561,7 +1561,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements ...@@ -1561,7 +1561,7 @@ public class JdbcDatabaseMetaData extends TraceObject implements
@Override @Override
public String getSQLKeywords() { public String getSQLKeywords() {
debugCodeCall("getSQLKeywords"); debugCodeCall("getSQLKeywords");
return "IF,INTERSECTS,LIMIT,MINUS,OFFSET,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP"; return "IF,INTERSECTS,LIMIT,MINUS,OFFSET,QUALIFY,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP";
} }
/** /**
......
...@@ -217,10 +217,15 @@ public class ParserUtil { ...@@ -217,10 +217,15 @@ public class ParserUtil {
*/ */
public static final int PRIMARY = ORDER + 1; public static final int PRIMARY = ORDER + 1;
/**
* The token "QUALIFY".
*/
public static final int QUALIFY = PRIMARY + 1;
/** /**
* The token "ROW". * The token "ROW".
*/ */
public static final int ROW = PRIMARY + 1; public static final int ROW = QUALIFY + 1;
/** /**
* The token "ROWNUM". * The token "ROWNUM".
...@@ -476,6 +481,11 @@ public class ParserUtil { ...@@ -476,6 +481,11 @@ public class ParserUtil {
return PRIMARY; return PRIMARY;
} }
return IDENTIFIER; return IDENTIFIER;
case 'Q':
if (eq("QUALIFY", s, ignoreCase, start, end)) {
return QUALIFY;
}
return IDENTIFIER;
case 'R': case 'R':
if (eq("ROW", s, ignoreCase, start, end)) { if (eq("ROW", s, ignoreCase, start, end)) {
return ROW; return ROW;
......
...@@ -463,7 +463,7 @@ public class TestMetaData extends TestDb { ...@@ -463,7 +463,7 @@ public class TestMetaData extends TestDb {
assertEquals("schema", meta.getSchemaTerm()); assertEquals("schema", meta.getSchemaTerm());
assertEquals("\\", meta.getSearchStringEscape()); assertEquals("\\", meta.getSearchStringEscape());
assertEquals("IF,INTERSECTS,LIMIT,MINUS,OFFSET,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP", assertEquals("IF,INTERSECTS,LIMIT,MINUS,OFFSET,QUALIFY,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY,TOP",
meta.getSQLKeywords()); meta.getSQLKeywords());
assertTrue(meta.getURL().startsWith("jdbc:h2:")); assertTrue(meta.getURL().startsWith("jdbc:h2:"));
......
...@@ -168,6 +168,22 @@ SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM ...@@ -168,6 +168,22 @@ SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM
> [[4, 5, 6]] c > [[4, 5, 6]] c
> rows: 3 > rows: 3
SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM TEST
WHERE ID <> 5
GROUP BY NAME HAVING ARRAY_AGG(ID ORDER BY ID)[1] > 1
QUALIFY ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) <> ARRAY[ARRAY[3]];
> ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) NAME
> ------------------------------------------------------------- ----
> [[4, 6]] c
> rows: 1
EXPLAIN
SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM TEST
WHERE ID <> 5
GROUP BY NAME HAVING ARRAY_AGG(ID ORDER BY ID)[1] > 1
QUALIFY ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) <> ARRAY[ARRAY[3]];
>> SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ WHERE ID <> 5 GROUP BY NAME HAVING ARRAY_GET(ARRAY_AGG(ID ORDER BY ID), 1) > 1 QUALIFY ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) <> ARRAY [ARRAY [3]]
SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM TEST SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM TEST
GROUP BY NAME ORDER BY NAME OFFSET 1 ROW; GROUP BY NAME ORDER BY NAME OFFSET 1 ROW;
> ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) NAME > ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) NAME
......
...@@ -83,6 +83,33 @@ SELECT *, ...@@ -83,6 +83,33 @@ SELECT *,
> 9 4 41 1 1 1 0.0 1.0 > 9 4 41 1 1 1 0.0 1.0
> rows: 9 > rows: 9
SELECT *,
ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID) RN,
RANK() OVER (PARTITION BY CATEGORY ORDER BY ID) RK,
DENSE_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID) DR,
ROUND(PERCENT_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID), 2) PR,
ROUND(CUME_DIST() OVER (PARTITION BY CATEGORY ORDER BY ID), 2) CD
FROM TEST QUALIFY ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID) = 2;
> ID CATEGORY VALUE RN RK DR PR CD
> -- -------- ----- -- -- -- --- ----
> 2 1 12 2 2 2 0.5 0.67
> 5 2 22 2 2 2 1.0 1.0
> 7 3 32 2 2 2 0.5 0.67
> rows: 3
SELECT *,
ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID) RN,
RANK() OVER (PARTITION BY CATEGORY ORDER BY ID) RK,
DENSE_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID) DR,
ROUND(PERCENT_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID), 2) PR,
ROUND(CUME_DIST() OVER (PARTITION BY CATEGORY ORDER BY ID), 2) CD
FROM TEST QUALIFY RN = 3;
> ID CATEGORY VALUE RN RK DR PR CD
> -- -------- ----- -- -- -- --- ---
> 3 1 13 3 3 3 1.0 1.0
> 8 3 33 3 3 3 1.0 1.0
> rows: 2
SELECT SELECT
ROW_NUMBER() OVER (ORDER BY CATEGORY) RN, ROW_NUMBER() OVER (ORDER BY CATEGORY) RN,
RANK() OVER (ORDER BY CATEGORY) RK, RANK() OVER (ORDER BY CATEGORY) RK,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论