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

Merge pull request #1367 from katzyn/limit

Assorted changes with SELECT output limitation clauses
......@@ -10,10 +10,10 @@ FROM tableExpression [,...] [ WHERE expression ]
[ GROUP BY expression [,...] ] [ HAVING expression ]
[ { UNION [ ALL ] | MINUS | EXCEPT | INTERSECT } select ]
[ ORDER BY order [,...] ]
[ { LIMIT expression [ OFFSET expression ] [ SAMPLE_SIZE rowCountInt ] }
| { [ OFFSET expression { ROW | ROWS } ]
[ FETCH { FIRST | NEXT } [ [ expression ] PERCENT ] { ROW | ROWS }
{ ONLY | WITH TIES } ] } ]
[ { { LIMIT expression [ OFFSET expression ] }
| { [ OFFSET expression { ROW | ROWS } ]
[ FETCH { FIRST | NEXT } [ expression [ PERCENT ] ] { ROW | ROWS }
{ ONLY | WITH TIES } ] } } [ SAMPLE_SIZE rowCountInt ] ]
[ FOR UPDATE ]
","
Selects data from a table or multiple tables.
......@@ -22,9 +22,16 @@ HAVING filter rows after grouping.
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.
Number of rows in output can be limited either with standard OFFSET / FETCH,
with non-standard LIMIT / OFFSET, or with non-standard TOP clauses.
Different clauses cannot be used together.
FETCH FIRST/NEXT, LIMIT or TOP limits the number of rows returned by the query (no limit if null or smaller than zero).
OFFSET specified how many rows to skip.
Please note using high offset values should be avoided because it can cause performance problems.
If PERCENT is specified number of rows is specified as a percent of the total number of rows
and should be an integer value between 0 and 100 inclusive.
WITH TIES can be used only together with ORDER BY and means that all additional rows that have the same sorting position
as the last row will be also returned.
OFFSET specifies how many rows to skip.
Please note that queries with high offset values can be slow.
SAMPLE_SIZE limits the number of rows read for aggregate queries.
Multiple set operators (UNION, INTERSECT, MINUS, EXCEPT) are evaluated
......@@ -41,7 +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 LIMIT 1000;
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 (SELECT ID, COUNT(*) FROM TEST
GROUP BY ID UNION SELECT NULL, COUNT(*) FROM TEST)
ORDER BY 1 NULLS LAST;
......
......@@ -2358,55 +2358,57 @@ public class Parser {
command.setOrder(orderList);
currentSelect = oldSelect;
}
// make sure aggregate functions will not work here
Select temp = currentSelect;
currentSelect = null;
// http://sqlpro.developpez.com/SQL2008/
if (readIf(OFFSET)) {
command.setOffset(readExpression().optimize(session));
if (!readIf("ROW")) {
readIf("ROWS");
}
}
if (readIf(FETCH)) {
if (!readIf("FIRST")) {
read("NEXT");
}
if (readIf("ROW") || readIf("ROWS")) {
command.setLimit(ValueExpression.get(ValueInt.get(1)));
} else {
Expression limit = readExpression().optimize(session);
command.setLimit(limit);
if (readIf("PERCENT")) {
command.setFetchPercent(true);
}
if (command.getLimit() == null) {
// make sure aggregate functions will not work here
Select temp = currentSelect;
currentSelect = null;
boolean hasOffsetOrFetch = false;
// Standard SQL OFFSET / FETCH
if (readIf(OFFSET)) {
hasOffsetOrFetch = true;
command.setOffset(readExpression().optimize(session));
if (!readIf("ROW")) {
read("ROWS");
readIf("ROWS");
}
}
if (readIf(WITH)) {
read("TIES");
command.setWithTies(true);
} else {
read("ONLY");
if (readIf(FETCH)) {
hasOffsetOrFetch = true;
if (!readIf("FIRST")) {
read("NEXT");
}
if (readIf("ROW") || readIf("ROWS")) {
command.setLimit(ValueExpression.get(ValueInt.get(1)));
} else {
Expression limit = readExpression().optimize(session);
command.setLimit(limit);
if (readIf("PERCENT")) {
command.setFetchPercent(true);
}
if (!readIf("ROW")) {
read("ROWS");
}
}
if (readIf(WITH)) {
read("TIES");
command.setWithTies(true);
} else {
read("ONLY");
}
}
}
currentSelect = temp;
if (readIf(LIMIT)) {
temp = currentSelect;
// make sure aggregate functions will not work here
currentSelect = null;
Expression limit = readExpression().optimize(session);
command.setLimit(limit);
if (readIf(OFFSET)) {
Expression offset = readExpression().optimize(session);
command.setOffset(offset);
} else if (readIf(COMMA)) {
// MySQL: [offset, ] rowcount
Expression offset = limit;
limit = readExpression().optimize(session);
command.setOffset(offset);
// MySQL-style LIMIT / OFFSET
if (!hasOffsetOrFetch && readIf(LIMIT)) {
Expression limit = readExpression().optimize(session);
command.setLimit(limit);
if (readIf(OFFSET)) {
Expression offset = readExpression().optimize(session);
command.setOffset(offset);
} else if (readIf(COMMA)) {
// MySQL: [offset, ] rowcount
Expression offset = limit;
limit = readExpression().optimize(session);
command.setOffset(offset);
command.setLimit(limit);
}
}
if (readIf("SAMPLE_SIZE")) {
Expression sampleSize = readExpression().optimize(session);
......
......@@ -115,14 +115,13 @@ public class Analyze extends DefineCommand {
}
buff.append(" FROM ").append(table.getSQL());
if (sample > 0) {
buff.append(" LIMIT ? SAMPLE_SIZE ? ");
buff.append(" FETCH NEXT ROW ONLY SAMPLE_SIZE ? ");
}
String sql = buff.toString();
Prepared command = session.prepare(sql);
if (sample > 0) {
ArrayList<Parameter> params = command.getParameters();
params.get(0).setValue(ValueInt.get(1));
params.get(1).setValue(ValueInt.get(sample));
params.get(0).setValue(ValueInt.get(sample));
}
ResultInterface result = command.query(0);
result.next();
......
......@@ -691,25 +691,22 @@ public abstract class Query extends Prepared {
}
void appendLimitToSQL(StringBuilder buff) {
if (offsetExpr != null) {
String count = StringUtils.unEnclose(offsetExpr.getSQL());
buff.append("\nOFFSET ").append(count).append("1".equals(count) ? " ROW" : " ROWS");
}
if (limitExpr != null) {
if (fetchPercent || withTies) {
if (offsetExpr != null) {
buff.append("\nOFFSET ").append(StringUtils.unEnclose(offsetExpr.getSQL())).append(" ROWS");
}
buff.append("\nFETCH NEXT ").append(StringUtils.unEnclose(limitExpr.getSQL()));
buff.append("\nFETCH ").append(offsetExpr != null ? "NEXT" : "FIRST");
String count = StringUtils.unEnclose(limitExpr.getSQL());
boolean withCount = fetchPercent || !"1".equals(count);
if (withCount) {
buff.append(' ').append(count);
if (fetchPercent) {
buff.append(" PERCENT");
}
buff.append(" ROWS");
if (withTies) {
buff.append(" WITH TIES");
}
} else {
buff.append("\nLIMIT ").append(StringUtils.unEnclose(limitExpr.getSQL()));
if (offsetExpr != null) {
buff.append("\nOFFSET ").append(StringUtils.unEnclose(offsetExpr.getSQL()));
}
}
buff.append(!withCount ? " ROW" : " ROWS")
.append(withTies ? " WITH TIES" : " ONLY");
}
}
......
......@@ -62,7 +62,7 @@ EXPLAIN SELECT DISTINCT TYPE FROM TEST ORDER BY TYPE LIMIT 3;
--> FROM PUBLIC.TEST
--> /* PUBLIC.IDX_TEST_TYPE */
--> ORDER BY 1
--> LIMIT 3
--> FETCH FIRST 3 ROWS ONLY
--> /* distinct */
--> /* index sorted */
;
......@@ -144,7 +144,7 @@ FROM (SELECT DISTINCT TYPE FROM TEST) T ORDER BY TYPE;
--> /* PUBLIC.IDX_TEST_TYPE_VALUE: TYPE = T.TYPE */
--> WHERE T.TYPE = T2.TYPE
--> ORDER BY =TYPE, 1
--> LIMIT 1
--> FETCH FIRST ROW ONLY
--> /* index sorted */) AS MIN
--> FROM (
--> SELECT DISTINCT
......@@ -194,7 +194,7 @@ EXPLAIN SELECT VALUE FROM TEST ORDER BY VALUE LIMIT 10;
--> FROM PUBLIC.TEST
--> /* PUBLIC.IDX_TEST_VALUE */
--> ORDER BY 1
--> LIMIT 10
--> FETCH FIRST 10 ROWS ONLY
--> /* index sorted */
;
......@@ -215,7 +215,7 @@ EXPLAIN SELECT VALUE FROM TEST ORDER BY VALUE DESC LIMIT 10;
--> FROM PUBLIC.TEST
--> /* PUBLIC.IDX_TEST_VALUE_D */
--> ORDER BY 1 DESC
--> LIMIT 10
--> FETCH FIRST 10 ROWS ONLY
--> /* index sorted */
;
......
......@@ -213,7 +213,7 @@ SELECT * FROM TEST FETCH FIRST 1 ROW WITH TIES;
> exception WITH_TIES_WITHOUT_ORDER_BY
EXPLAIN SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES;
>> SELECT TEST.A, TEST.B, TEST.C FROM PUBLIC.TEST /* PUBLIC.TEST_A_B_IDX */ ORDER BY 1, 2 OFFSET 3 ROWS FETCH NEXT 1 ROWS WITH TIES /* index sorted */
>> SELECT TEST.A, TEST.B, TEST.C FROM PUBLIC.TEST /* PUBLIC.TEST_A_B_IDX */ ORDER BY 1, 2 OFFSET 3 ROWS FETCH NEXT ROW WITH TIES /* index sorted */
EXPLAIN SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 PERCENT ROWS WITH TIES;
>> SELECT TEST.A, TEST.B, TEST.C FROM PUBLIC.TEST /* PUBLIC.TEST_A_B_IDX */ ORDER BY 1, 2 OFFSET 3 ROWS FETCH NEXT 1 PERCENT ROWS WITH TIES /* index sorted */
......@@ -267,3 +267,25 @@ DROP TABLE TEST1;
DROP TABLE TEST2;
> ok
-- Disallowed mixed OFFSET/FETCH/LIMIT/TOP clauses
CREATE TABLE TEST (ID BIGINT);
> ok
SELECT TOP 1 ID FROM TEST OFFSET 1 ROW;
> exception SYNTAX_ERROR_1
SELECT TOP 1 ID FROM TEST FETCH NEXT ROW ONLY;
> exception SYNTAX_ERROR_1
SELECT TOP 1 ID FROM TEST LIMIT 1;
> exception SYNTAX_ERROR_1
SELECT ID FROM TEST OFFSET 1 ROW LIMIT 1;
> exception SYNTAX_ERROR_1
SELECT ID FROM TEST FETCH NEXT ROW ONLY LIMIT 1;
> exception SYNTAX_ERROR_1
DROP TABLE TEST;
> ok
......@@ -484,8 +484,8 @@ explain insert into test(id) direct sorted select x from system_range(1, 100);
>> INSERT INTO PUBLIC.TEST(ID) DIRECT SORTED SELECT X FROM SYSTEM_RANGE(1, 100) /* PUBLIC.RANGE_INDEX */
explain select * from test limit 10 sample_size 10;
#+mvStore#>> SELECT TEST.ID FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ LIMIT 10 SAMPLE_SIZE 10
#-mvStore#>> SELECT TEST.ID FROM PUBLIC.TEST /* PUBLIC.PRIMARY_KEY_2 */ LIMIT 10 SAMPLE_SIZE 10
#+mvStore#>> SELECT TEST.ID FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ FETCH FIRST 10 ROWS ONLY SAMPLE_SIZE 10
#-mvStore#>> SELECT TEST.ID FROM PUBLIC.TEST /* PUBLIC.PRIMARY_KEY_2 */ FETCH FIRST 10 ROWS ONLY SAMPLE_SIZE 10
drop table test;
> ok
......@@ -4991,7 +4991,7 @@ SELECT LIMIT (1+0) (2+0) NAME, -ID, ID _ID_ FROM TEST ORDER BY _ID_;
> rows (ordered): 2
EXPLAIN SELECT LIMIT (1+0) (2+0) * FROM TEST ORDER BY ID;
>> SELECT TEST.ID, TEST.NAME FROM PUBLIC.TEST /* PUBLIC.PRIMARY_KEY_2 */ ORDER BY 1 LIMIT 2 OFFSET 1 /* index sorted */
>> SELECT TEST.ID, TEST.NAME FROM PUBLIC.TEST /* PUBLIC.PRIMARY_KEY_2 */ ORDER BY 1 OFFSET 1 ROW FETCH NEXT 2 ROWS ONLY /* index sorted */
SELECT * FROM TEST ORDER BY ID LIMIT 2+0 OFFSET 1+0;
> ID NAME
......@@ -5039,7 +5039,7 @@ SELECT * FROM (SELECT ID FROM TEST GROUP BY ID);
> rows: 5
EXPLAIN SELECT * FROM TEST UNION ALL SELECT * FROM TEST ORDER BY ID LIMIT 2+0 OFFSET 1+0;
>> (SELECT TEST.ID, TEST.NAME FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */) UNION ALL (SELECT TEST.ID, TEST.NAME FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */) ORDER BY 1 LIMIT 2 OFFSET 1
>> (SELECT TEST.ID, TEST.NAME FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */) UNION ALL (SELECT TEST.ID, TEST.NAME FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */) ORDER BY 1 OFFSET 1 ROW FETCH NEXT 2 ROWS ONLY
EXPLAIN DELETE FROM TEST WHERE ID=1;
>> DELETE FROM PUBLIC.TEST /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ WHERE ID = 1
......@@ -5223,7 +5223,7 @@ EXPLAIN SELECT * FROM TEST WHERE 1=0;
>> SELECT TEST.ID, TEST.NAME FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan: FALSE */ WHERE FALSE
EXPLAIN SELECT TOP 1 * FROM TEST FOR UPDATE;
>> SELECT TEST.ID, TEST.NAME FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ LIMIT 1 FOR UPDATE
>> SELECT TEST.ID, TEST.NAME FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ FETCH FIRST ROW ONLY FOR UPDATE
EXPLAIN SELECT COUNT(NAME) FROM TEST WHERE ID=1;
>> SELECT COUNT(NAME) FROM PUBLIC.TEST /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ WHERE ID = 1
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论