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

Add QUALIFY clause

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