You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ja...@apache.org on 2020/04/15 11:44:24 UTC

[incubator-iotdb] 01/01: support left open and right close range in group by

This is an automated email from the ASF dual-hosted git repository.

jackietien pushed a commit to branch TyGroupBy
in repository https://gitbox.apache.org/repos/asf/incubator-iotdb.git

commit fb3f8c0115db28c5ae7cce475493d1173df56e9e
Author: JackieTien97 <Ja...@foxmail.com>
AuthorDate: Wed Apr 15 19:43:56 2020 +0800

    support left open and right close range in group by
---
 .../2-DML Data Manipulation Language.md            | 35 +++++++--
 .../5-Operation Manual/4-SQL Reference.md          |  5 +-
 .../2-DML Data Manipulation Language.md            | 40 ++++++++---
 .../5-Operation Manual/4-SQL Reference.md          | 11 +--
 .../org/apache/iotdb/db/qp/strategy/SqlBase.g4     |  1 +
 .../iotdb/db/qp/logical/crud/QueryOperator.java    | 10 +++
 .../iotdb/db/qp/physical/crud/GroupByPlan.java     | 11 +++
 .../iotdb/db/qp/strategy/LogicalGenerator.java     |  4 +-
 .../iotdb/db/qp/strategy/PhysicalGenerator.java    |  2 +
 .../dataset/groupby/GroupByEngineDataSet.java      |  8 ++-
 .../groupby/GroupByWithValueFilterDataSet.java     |  7 +-
 .../groupby/GroupByWithoutValueFilterDataSet.java  |  7 +-
 .../iotdb/db/query/executor/QueryRouter.java       |  5 ++
 .../iotdb/db/integration/IoTDBGroupByFillIT.java   | 82 +++++++++++++++++++++-
 14 files changed, 203 insertions(+), 25 deletions(-)

diff --git a/docs/UserGuide/5-Operation Manual/2-DML Data Manipulation Language.md b/docs/UserGuide/5-Operation Manual/2-DML Data Manipulation Language.md
index e8fe509..8d062bf 100644
--- a/docs/UserGuide/5-Operation Manual/2-DML Data Manipulation Language.md	
+++ b/docs/UserGuide/5-Operation Manual/2-DML Data Manipulation Language.md	
@@ -177,13 +177,13 @@ and value filtering conditions specified.
 The SQL statement is:
 
 ```
-select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01T00:00:00, 2017-11-07T23:00:00],1d);
+select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01T00:00:00, 2017-11-07T23:00:00),1d);
 ```
 which means:
 
 Since the user does not specify the sliding step length, the GROUP BY statement will by default set the sliding step same as the time interval which is `1d`.
 
-The fist parameter of the GROUP BY statement above is the display window parameter, which determines the final display range is [2017-11-01T00:00:00, 2017-11-07T23:00:00].
+The fist parameter of the GROUP BY statement above is the display window parameter, which determines the final display range is [2017-11-01T00:00:00, 2017-11-07T23:00:00).
 
 The second parameter of the GROUP BY statement above is the time interval for dividing the time axis. Taking this parameter (1d) as time interval and startTime of the display window as the dividing origin, the time axis is divided into several continuous intervals, which are [0,1d), [1d, 2d), [2d, 3d), etc.
 
@@ -198,7 +198,7 @@ Since there is data for each time period in the result range to be displayed, th
 The SQL statement is:
 
 ```
-select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01 00:00:00, 2017-11-07 23:00:00], 3h, 1d);
+select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01 00:00:00, 2017-11-07 23:00:00), 3h, 1d);
 ```
 
 which means:
@@ -207,7 +207,7 @@ Since the user specifies the sliding step parameter as 1d, the GROUP BY statemen
 
 That means we want to fetch all the data of 00:00:00 to 02:59:59 every day from 2017-11-01 to 2017-11-07.
 
-The first parameter of the GROUP BY statement above is the display window parameter, which determines the final display range is [2017-11-01T00:00:00, 2017-11-07T23:00:00].
+The first parameter of the GROUP BY statement above is the display window parameter, which determines the final display range is [2017-11-01T00:00:00, 2017-11-07T23:00:00).
 
 The second parameter of the GROUP BY statement above is the time interval for dividing the time axis. Taking this parameter (3h) as time interval and the startTime of the display window as the dividing origin, the time axis is divided into several continuous intervals, which are [2017-11-01T00:00:00, 2017-11-01T03:00:00), [2017-11-02T00:00:00, 2017-11-02T03:00:00), [2017-11-03T00:00:00, 2017-11-03T03:00:00), etc.
 
@@ -224,13 +224,13 @@ Since there is data for each time period in the result range to be displayed, th
 The SQL statement is:
 
 ```
-select count(status), max_value(temperature) from root.ln.wf01.wt01 where time > 2017-11-01T01:00:00 and temperature > 20 group by([2017-11-01T00:00:00, 2017-11-07T23:00:00], 3h, 1d);
+select count(status), max_value(temperature) from root.ln.wf01.wt01 where time > 2017-11-01T01:00:00 and temperature > 20 group by([2017-11-01T00:00:00, 2017-11-07T23:00:00), 3h, 1d);
 ```
 which means:
 
 Since the user specifies the sliding step parameter as 1d, the GROUP BY statement will move the time interval `1 day` long instead of `3 hours` as default.
 
-The first parameter of the GROUP BY statement above is the display window parameter, which determines the final display range is [2017-11-01T00:00:00, 2017-11-07T23:00:00].
+The first parameter of the GROUP BY statement above is the display window parameter, which determines the final display range is [2017-11-01T00:00:00, 2017-11-07T23:00:00).
 
 The second parameter of the GROUP BY statement above is the time interval for dividing the time axis. Taking this parameter (3h) as time interval and the startTime of the display window as the dividing origin, the time axis is divided into several continuous intervals, which are [2017-11-01T00:00:00, 2017-11-01T03:00:00), [2017-11-02T00:00:00, 2017-11-02T03:00:00), [2017-11-03T00:00:00, 2017-11-03T03:00:00), etc.
 
@@ -240,6 +240,29 @@ Then the system will use the time and value filtering condition in the WHERE cla
 
 <center><img style="width:100%; max-width:800px; max-height:600px; margin-left:auto; margin-right:auto; display:block;" src="https://user-images.githubusercontent.com/16079446/69116088-001e2780-0ac6-11ea-9a01-dc45271d1dad.png"></center>
 
+#### Left Open And Right Close Range
+
+The SQL statement is:
+
+```
+select count(status) from root.ln.wf01.wt01 group by((5, 40], 1ms);
+```
+
+In this sql, the time interval is left open and right close, so we won't include the value of timestamp 5 and instead we will include the value of timestamp 40.
+
+We will get the result like following:
+
+| Time   | count(root.ln.wf01.wt01.status) |
+| ------ | ------------------------------- |
+| 10     | 1                               |
+| 15     | 2                               |
+| 20     | 3                               |
+| 25     | 4                               |
+| 30     | 4                               |
+| 35     | 3                               |
+| 40     | 5                               |
+
+
 #### Down-Frequency Aggregate Query with Fill Clause
 
 In group by fill, sliding step is not supported in group by clause
diff --git a/docs/UserGuide/5-Operation Manual/4-SQL Reference.md b/docs/UserGuide/5-Operation Manual/4-SQL Reference.md
index 627892a..d8a7237 100644
--- a/docs/UserGuide/5-Operation Manual/4-SQL Reference.md	
+++ b/docs/UserGuide/5-Operation Manual/4-SQL Reference.md	
@@ -271,12 +271,14 @@ RelativeTimeDurationUnit = Integer ('Y'|'MO'|'W'|'D'|'H'|'M'|'S'|'MS'|'US'|'NS')
 RelativeTime : (now() | <TimeValue>) [(+|-) RelativeTimeDurationUnit]+
 SensorExpr : (<Timeseries> | <Path>) PrecedenceEqualOperator <PointValue>
 GroupByClause : LPAREN <TimeInterval> COMMA <TimeUnit> (COMMA <TimeUnit>)? RPAREN
-TimeInterval: LBRACKET <TimeValue> COMMA <TimeValue> RBRACKET
+TimeInterval: LSBRACKET <TimeValue> COMMA <TimeValue> RRBRACKET | LRBRACKET <TimeValue> COMMA <TimeValue> RSBRACKET
 TimeUnit : Integer <DurationUnit>
 DurationUnit : "ms" | "s" | "m" | "h" | "d" | "w"
 Eg: SELECT COUNT(status), COUNT(temperature) FROM root.ln.wf01.wt01 where temperature < 24 GROUP BY([1509465720000, 1509466380000), 5m)
+Eg: SELECT COUNT(status), COUNT(temperature) FROM root.ln.wf01.wt01 where temperature < 24 GROUP BY((1509465720000, 1509466380000], 5m)
 Eg. SELECT COUNT (status), MAX_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE time < 1509466500000 GROUP BY([1509465720000, 1509466380000), 5m, 10m)
 Eg. SELECT MIN_TIME(status), MIN_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE temperature < 25 GROUP BY ([1509466140000, 1509466380000), 3m, 5ms)
+Eg. SELECT MIN_TIME(status), MIN_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE temperature < 25 GROUP BY ((1509466140000, 1509466380000], 3m, 5ms)
 Note: the statement needs to satisfy this constraint: <Path>(SelectClause) + <PrefixPath>(FromClause) = <Timeseries>
 Note: If the <SensorExpr>(WhereClause) is started with <Path> and not with ROOT, the statement needs to satisfy this constraint: <PrefixPath>(FromClause) + <Path>(SensorExpr) = <Timeseries>
 Note: <TimeValue>(TimeInterval) needs to be greater than 0
@@ -331,6 +333,7 @@ TextClause: TEXT LBRACKET (<PreviousUntilLastClause> | <PreviousClause>)  RBRACK
 PreviousClause : PREVIOUS
 PreviousUntilLastClause : PREVIOUSUNTILLAST
 Eg: SELECT last_value(temperature) FROM root.ln.wf01.wt01 GROUP BY([20, 100), 5m) FILL (float[PREVIOUS])
+Eg: SELECT last_value(temperature) FROM root.ln.wf01.wt01 GROUP BY((15, 100], 5m) FILL (float[PREVIOUS])
 Eg: SELECT last_value(power) FROM root.ln.wf01.wt01 GROUP BY([20, 100), 5m) FILL (int32[PREVIOUSUNTILLAST])
 Eg: SELECT last_value(temperature), last_value(power) FROM root.ln.wf01.wt01 GROUP BY([20, 100), 5m) FILL (ALL[PREVIOUS])
 Note: In group by fill, sliding step is not supported in group by clause
diff --git a/docs/zh/UserGuide/5-Operation Manual/2-DML Data Manipulation Language.md b/docs/zh/UserGuide/5-Operation Manual/2-DML Data Manipulation Language.md
index 1571bee..2d15984 100644
--- a/docs/zh/UserGuide/5-Operation Manual/2-DML Data Manipulation Language.md	
+++ b/docs/zh/UserGuide/5-Operation Manual/2-DML Data Manipulation Language.md	
@@ -201,17 +201,17 @@ GROUP BY语句为用户提供三类指定参数:
 对应的SQL语句是:
 
 ```
-select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01T00:00:00, 2017-11-07T23:00:00],1d);
+select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01T00:00:00, 2017-11-07T23:00:00),1d);
 ```
 这条查询的含义是:
 
 由于用户没有指定滑动步长,滑动步长将会被默认设置为跟时间间隔参数相同,也就是`1d`。
 
-上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是[2017-11-01T00:00:00, 2017-11-07T23:00:00]。
+上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是[2017-11-01T00:00:00, 2017-11-07T23:00:00)。
 
 上面这个例子的第二个参数是划分时间轴的时间间隔参数,将`1d`当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[0,1d), [1d, 2d), [2d, 3d)等等。
 
-然后系统将会用WHERE子句中的时间和值过滤条件以及GROUP BY语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在[2017-11-01T00:00:00, 2017-11-07 T23:00:00]这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从2017-11-01T00:00:00到2017-11-07T23:00:00:00的每一天)
+然后系统将会用WHERE子句中的时间和值过滤条件以及GROUP BY语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在[2017-11-01T00:00:00, 2017-11-07 T23:00:00)这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从2017-11-01T00:00:00到2017-11-07T23:00:00:00的每一天)
 
 每个时间间隔窗口内都有数据,SQL执行后的结果集如下所示:
 
@@ -222,7 +222,7 @@ select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2
 对应的SQL语句是:
 
 ```
-select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01 00:00:00, 2017-11-07 23:00:00], 3h, 1d);
+select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01 00:00:00, 2017-11-07 23:00:00), 3h, 1d);
 ```
 
 这条查询的含义是:
@@ -231,13 +231,13 @@ select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2
 
 也就意味着,我们想要取从2017-11-01到2017-11-07每一天的凌晨0点到凌晨3点的数据。
 
-上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是[2017-11-01T00:00:00, 2017-11-07T23:00:00]。
+上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是[2017-11-01T00:00:00, 2017-11-07T23:00:00)。
 
 上面这个例子的第二个参数是划分时间轴的时间间隔参数,将`3h`当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[2017-11-01T00:00:00, 2017-11-01T03:00:00), [2017-11-02T00:00:00, 2017-11-02T03:00:00), [2017-11-03T00:00:00, 2017-11-03T03:00:00)等等。
 
 上面这个例子的第三个参数是每次时间间隔的滑动步长。
 
-然后系统将会用WHERE子句中的时间和值过滤条件以及GROUP BY语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在[2017-11-01T00:00:00, 2017-11-07 T23:00:00]这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从2017-11-01T00:00:00到2017-11-07T23:00:00:00的每一天的凌晨0点到凌晨3点)
+然后系统将会用WHERE子句中的时间和值过滤条件以及GROUP BY语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在[2017-11-01T00:00:00, 2017-11-07 T23:00:00)这个时间范围的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从2017-11-01T00:00:00到2017-11-07T23:00:00:00的每一天的凌晨0点到凌晨3点)
 
 每个时间间隔窗口内都有数据,SQL执行后的结果集如下所示:
 
@@ -248,7 +248,7 @@ select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2
 对应的SQL语句是:
 
 ```
-select count(status), max_value(temperature) from root.ln.wf01.wt01 where time > 2017-11-01T01:00:00 and temperature > 20 group by([2017-11-01T00:00:00, 2017-11-07T23:00:00], 3h, 1d);
+select count(status), max_value(temperature) from root.ln.wf01.wt01 where time > 2017-11-01T01:00:00 and temperature > 20 group by([2017-11-01T00:00:00, 2017-11-07T23:00:00), 3h, 1d);
 ```
 
 这条查询的含义是:
@@ -257,13 +257,13 @@ select count(status), max_value(temperature) from root.ln.wf01.wt01 where time >
 
 也就意味着,我们想要取从2017-11-01到2017-11-07每一天的凌晨0点到凌晨3点的数据。
 
-上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是[2017-11-01T00:00:00, 2017-11-07T23:00:00]。
+上面这个例子的第一个参数是显示窗口参数,决定了最终的显示范围是[2017-11-01T00:00:00, 2017-11-07T23:00:00)。
 
 上面这个例子的第二个参数是划分时间轴的时间间隔参数,将`3h`当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[2017-11-01T00:00:00, 2017-11-01T03:00:00), [2017-11-02T00:00:00, 2017-11-02T03:00:00), [2017-11-03T00:00:00, 2017-11-03T03:00:00)等等。
 
 上面这个例子的第三个参数是每次时间间隔的滑动步长。
 
-然后系统将会用WHERE子句中的时间和值过滤条件以及GROUP BY语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在[2017-11-01T00:00:00, 2017-11-07 T23:00:00]这个时间范围的并且满足root.ln.wf01.wt01.temperature > 20的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从2017-11-01T00:00:00到2017-11-07T23:00:00:00的每一天的凌晨0点到凌晨3点)
+然后系统将会用WHERE子句中的时间和值过滤条件以及GROUP BY语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据(在这个例子里是在[2017-11-01T00:00:00, 2017-11-07 T23:00:00)这个时间范围的并且满足root.ln.wf01.wt01.temperature > 20的数据),并把这些数据映射到之前分割好的时间轴中(这个例子里是从2017-11-01T00:00:00到2017-11-07T23:00:00:00的每一天的凌晨0点到凌晨3点)
 
 每个时间间隔窗口内都有数据,SQL执行后的结果集如下所示:
 
@@ -273,6 +273,28 @@ GROUP BY的SELECT子句里的查询路径必须是聚合函数,否则系统将
 
 <center><img style="width:100%; max-width:800px; max-height:600px; margin-left:auto; margin-right:auto; display:block;" src="https://user-images.githubusercontent.com/16079446/69116099-0b715300-0ac6-11ea-8074-84e04797b8c7.png"></center>
 
+#### 左开右闭区间
+
+对应的SQL语句是:
+
+```
+select count(status) from root.ln.wf01.wt01 group by((5, 40], 1ms);
+```
+
+这条查询语句的时间区间是左开右闭的,结果中不会包含时间点5的数据,但是会包含时间点40的数据。
+
+SQL执行后的结果集如下所示:
+
+| Time   | count(root.ln.wf01.wt01.status) |
+| ------ | ------------------------------- |
+| 10     | 1                               |
+| 15     | 2                               |
+| 20     | 3                               |
+| 25     | 4                               |
+| 30     | 4                               |
+| 35     | 3                               |
+| 40     | 5                               |
+
 #### 降频聚合查询补空值
 
 降频聚合出的各个时间段的结果,支持使用前值补空。
diff --git a/docs/zh/UserGuide/5-Operation Manual/4-SQL Reference.md b/docs/zh/UserGuide/5-Operation Manual/4-SQL Reference.md
index 1a55d08..1cf7a53 100644
--- a/docs/zh/UserGuide/5-Operation Manual/4-SQL Reference.md	
+++ b/docs/zh/UserGuide/5-Operation Manual/4-SQL Reference.md	
@@ -263,12 +263,14 @@ RelativeTimeDurationUnit = Integer ('Y'|'MO'|'W'|'D'|'H'|'M'|'S'|'MS'|'US'|'NS')
 RelativeTime : (now() | <TimeValue>) [(+|-) RelativeTimeDurationUnit]+
 SensorExpr : (<Timeseries> | <Path>) PrecedenceEqualOperator <PointValue>
 GroupByClause : LPAREN <TimeInterval> COMMA <TimeUnit> (COMMA <TimeUnit>)? RPAREN
-TimeInterval: LBRACKET <TimeValue> COMMA <TimeValue> RBRACKET
+TimeInterval: LSBRACKET <TimeValue> COMMA <TimeValue> RRBRACKET | LRBRACKET <TimeValue> COMMA <TimeValue> RSBRACKET
 TimeUnit : Integer <DurationUnit>
 DurationUnit : "ms" | "s" | "m" | "h" | "d" | "w"
-Eg: SELECT COUNT(status), COUNT(temperature) FROM root.ln.wf01.wt01 where temperature < 24 GROUP BY([1509465720000, 1509466380000], 5m)
-Eg. SELECT COUNT (status), MAX_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE time < 1509466500000 GROUP BY([1509465720000, 1509466380000], 5m, 10m)
-Eg. SELECT MIN_TIME(status), MIN_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE temperature < 25 GROUP BY ([1509466140000, 1509466380000], 3m, 5ms)
+Eg: SELECT COUNT(status), COUNT(temperature) FROM root.ln.wf01.wt01 where temperature < 24 GROUP BY([1509465720000, 1509466380000), 5m)
+Eg: SELECT COUNT(status), COUNT(temperature) FROM root.ln.wf01.wt01 where temperature < 24 GROUP BY((1509465720000, 1509466380000], 5m)
+Eg. SELECT COUNT (status), MAX_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE time < 1509466500000 GROUP BY([1509465720000, 1509466380000), 5m, 10m)
+Eg. SELECT MIN_TIME(status), MIN_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE temperature < 25 GROUP BY ([1509466140000, 1509466380000), 3m, 5ms)
+Eg. SELECT MIN_TIME(status), MIN_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE temperature < 25 GROUP BY ((1509466140000, 1509466380000], 3m, 5ms)
 Note: the statement needs to satisfy this constraint: <Path>(SelectClause) + <PrefixPath>(FromClause) = <Timeseries>
 Note: If the <SensorExpr>(WhereClause) is started with <Path> and not with ROOT, the statement needs to satisfy this constraint: <PrefixPath>(FromClause) + <Path>(SensorExpr) = <Timeseries>
 Note: <TimeValue>(TimeInterval) needs to be greater than 0
@@ -323,6 +325,7 @@ TextClause: TEXT LBRACKET (<PreviousUntilLastClause> | <PreviousClause>)  RBRACK
 PreviousClause : PREVIOUS
 PreviousUntilLastClause : PREVIOUSUNTILLAST
 Eg: SELECT last_value(temperature) FROM root.ln.wf01.wt01 GROUP BY([20, 100), 5m) FILL (float[PREVIOUS])
+Eg: SELECT last_value(temperature) FROM root.ln.wf01.wt01 GROUP BY((15, 100], 5m) FILL (float[PREVIOUS])
 Eg: SELECT last_value(power) FROM root.ln.wf01.wt01 GROUP BY([20, 100), 5m) FILL (int32[PREVIOUSUNTILLAST])
 Eg: SELECT last_value(temperature), last_value(power) FROM root.ln.wf01.wt01 GROUP BY([20, 100), 5m) FILL (ALL[PREVIOUS])
 Note: In group by fill, sliding step is not supported in group by clause
diff --git a/server/src/main/antlr4/org/apache/iotdb/db/qp/strategy/SqlBase.g4 b/server/src/main/antlr4/org/apache/iotdb/db/qp/strategy/SqlBase.g4
index 114bc5f..28c3441 100644
--- a/server/src/main/antlr4/org/apache/iotdb/db/qp/strategy/SqlBase.g4
+++ b/server/src/main/antlr4/org/apache/iotdb/db/qp/strategy/SqlBase.g4
@@ -274,6 +274,7 @@ rootOrId
 
 timeInterval
     : LS_BRACKET startTime=timeValue COMMA endTime=timeValue RR_BRACKET
+    | LR_BRACKET startTime=timeValue COMMA endTime=timeValue RS_BRACKET
     ;
 
 timeValue
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/QueryOperator.java b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/QueryOperator.java
index 204fffe..a2c18cf 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/QueryOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/logical/crud/QueryOperator.java
@@ -36,6 +36,8 @@ public class QueryOperator extends SFWOperator {
   // sliding step
   private long slidingStep;
   private boolean isGroupByTime = false;
+  // if it is left close and right open interval
+  private boolean leftCRightO;
 
   private Map<TSDataType, IFill> fillTypes;
   private boolean isFill = false;
@@ -77,6 +79,14 @@ public class QueryOperator extends SFWOperator {
     this.isGroupByTime = isGroupBy;
   }
 
+  public boolean isLeftCRightO() {
+    return leftCRightO;
+  }
+
+  public void setLeftCRightO(boolean leftCRightO) {
+    this.leftCRightO = leftCRightO;
+  }
+
   public int getRowLimit() {
     return rowLimit;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByPlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByPlan.java
index f2690ac..0afcbeb 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByPlan.java
@@ -30,6 +30,9 @@ public class GroupByPlan extends AggregationPlan {
   // sliding step
   private long slidingStep;
 
+  // if it is left close and right open interval
+  private boolean leftCRightO;
+
   public GroupByPlan() {
     super();
     setOperatorType(Operator.OperatorType.GROUPBY);
@@ -66,4 +69,12 @@ public class GroupByPlan extends AggregationPlan {
   public void setSlidingStep(long slidingStep) {
     this.slidingStep = slidingStep;
   }
+
+  public boolean isLeftCRightO() {
+    return leftCRightO;
+  }
+
+  public void setLeftCRightO(boolean leftCRightO) {
+    this.leftCRightO = leftCRightO;
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
index 358fbbf..45c7775 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/LogicalGenerator.java
@@ -563,6 +563,8 @@ public class LogicalGenerator extends SqlBaseBaseListener {
     super.enterGroupByFillClause(ctx);
     queryOp.setGroupBy(true);
     queryOp.setFill(true);
+    queryOp.setLeftCRightO(ctx.timeInterval().LS_BRACKET() != null);
+
 
     // parse timeUnit
     queryOp.setUnit(parseDuration(ctx.DURATION().getText()));
@@ -623,7 +625,7 @@ public class LogicalGenerator extends SqlBaseBaseListener {
   public void enterGroupByClause(GroupByClauseContext ctx) {
     super.enterGroupByClause(ctx);
     queryOp.setGroupBy(true);
-
+    queryOp.setLeftCRightO(ctx.timeInterval().LS_BRACKET() != null);
     // parse timeUnit
     queryOp.setUnit(parseDuration(ctx.DURATION(0).getText()));
     queryOp.setSlidingStep(queryOp.getUnit());
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java b/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java
index 96d7e8c..817dcd3 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/strategy/PhysicalGenerator.java
@@ -184,6 +184,7 @@ public class PhysicalGenerator {
       ((GroupByFillPlan) queryPlan).setSlidingStep(queryOperator.getSlidingStep());
       ((GroupByFillPlan) queryPlan).setStartTime(queryOperator.getStartTime());
       ((GroupByFillPlan) queryPlan).setEndTime(queryOperator.getEndTime());
+      ((GroupByFillPlan) queryPlan).setLeftCRightO(queryOperator.isLeftCRightO());
       ((GroupByFillPlan) queryPlan)
               .setAggregations(queryOperator.getSelectOperator().getAggregations());
       for (String aggregation : queryPlan.getAggregations()) {
@@ -198,6 +199,7 @@ public class PhysicalGenerator {
       ((GroupByPlan) queryPlan).setSlidingStep(queryOperator.getSlidingStep());
       ((GroupByPlan) queryPlan).setStartTime(queryOperator.getStartTime());
       ((GroupByPlan) queryPlan).setEndTime(queryOperator.getEndTime());
+      ((GroupByPlan) queryPlan).setLeftCRightO(queryOperator.isLeftCRightO());
       ((GroupByPlan) queryPlan)
           .setAggregations(queryOperator.getSelectOperator().getAggregations());
     } else if (queryOperator.isFill()) {
diff --git a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByEngineDataSet.java b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByEngineDataSet.java
index a377597..8164708 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByEngineDataSet.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByEngineDataSet.java
@@ -42,6 +42,8 @@ public abstract class GroupByEngineDataSet extends QueryDataSet {
   protected int usedIndex;
   protected boolean hasCachedTimeInterval;
 
+  protected boolean leftCRightO;
+
   public GroupByEngineDataSet() {
   }
 
@@ -55,7 +57,7 @@ public abstract class GroupByEngineDataSet extends QueryDataSet {
     this.slidingStep = groupByPlan.getSlidingStep();
     this.startTime = groupByPlan.getStartTime();
     this.endTime = groupByPlan.getEndTime();
-
+    this.leftCRightO = groupByPlan.isLeftCRightO();
     // init group by time partition
     this.usedIndex = 0;
     this.hasCachedTimeInterval = false;
@@ -75,6 +77,10 @@ public abstract class GroupByEngineDataSet extends QueryDataSet {
     if (curStartTime < endTime) {
       hasCachedTimeInterval = true;
       curEndTime = Math.min(curStartTime + interval, endTime);
+      if (!leftCRightO) {
+        curStartTime++;
+        curEndTime++;
+      }
       return true;
     } else {
       return false;
diff --git a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithValueFilterDataSet.java b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithValueFilterDataSet.java
index 47a056f..ce1a39e 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithValueFilterDataSet.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithValueFilterDataSet.java
@@ -182,7 +182,12 @@ public class GroupByWithValueFilterDataSet extends GroupByEngineDataSet {
   }
 
   private RowRecord constructRowRecord(List<AggregateResult> aggregateResultList) {
-    RowRecord record = new RowRecord(curStartTime);
+    RowRecord record;
+    if (leftCRightO) {
+      record = new RowRecord(curStartTime);
+    } else {
+      record = new RowRecord(curEndTime-1);
+    }
     for (int i = 0; i < paths.size(); i++) {
       AggregateResult aggregateResult = aggregateResultList.get(i);
       record.addField(aggregateResult.getResult(), aggregateResult.getResultDataType());
diff --git a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithoutValueFilterDataSet.java b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithoutValueFilterDataSet.java
index 2ee8df5..264ba42 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithoutValueFilterDataSet.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByWithoutValueFilterDataSet.java
@@ -104,7 +104,12 @@ public class GroupByWithoutValueFilterDataSet extends GroupByEngineDataSet {
               + "in GroupByWithoutValueFilterDataSet.");
     }
     hasCachedTimeInterval = false;
-    RowRecord record = new RowRecord(curStartTime);
+    RowRecord record;
+    if (leftCRightO) {
+      record = new RowRecord(curStartTime);
+    } else {
+      record = new RowRecord(curEndTime-1);
+    }
 
     AggregateResult[] fields = new AggregateResult[paths.size()];
 
diff --git a/server/src/main/java/org/apache/iotdb/db/query/executor/QueryRouter.java b/server/src/main/java/org/apache/iotdb/db/query/executor/QueryRouter.java
index 9a4aaf2..036302b 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/executor/QueryRouter.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/executor/QueryRouter.java
@@ -119,6 +119,11 @@ public class QueryRouter implements IQueryRouter {
     long startTime = groupByPlan.getStartTime();
     long endTime = groupByPlan.getEndTime();
 
+    if (!groupByPlan.isLeftCRightO()) {
+      startTime++;
+      endTime++;
+    }
+
     IExpression expression = groupByPlan.getExpression();
     List<Path> selectedSeries = groupByPlan.getDeduplicatedPaths();
 
diff --git a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBGroupByFillIT.java b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBGroupByFillIT.java
index 94825fc..039dd56 100644
--- a/server/src/test/java/org/apache/iotdb/db/integration/IoTDBGroupByFillIT.java
+++ b/server/src/test/java/org/apache/iotdb/db/integration/IoTDBGroupByFillIT.java
@@ -124,7 +124,6 @@ public class IoTDBGroupByFillIT {
                       + "root.ln.wf01.wt01 "
                       + "GROUP BY ([17, 48), 5ms) FILL(int32[previous])");
     } catch (IoTDBSQLException e) {
-      System.out.println("error message: " + e.getMessage());
       assertTrue(e.getMessage().contains("Group By Fill only support last_value function"));
     } catch (Exception e) {
       e.printStackTrace();
@@ -219,6 +218,46 @@ public class IoTDBGroupByFillIT {
   }
 
   @Test
+  public void leftORightCPreviousTest() {
+    String[] retArray = new String[] {
+            "10,21",
+            "15,25",
+            "20,25",
+            "25,25",
+            "30,26",
+            "35,26",
+            "40,40",
+    };
+
+    try (Connection connection = DriverManager.
+            getConnection("jdbc:iotdb://127.0.0.1:6667/", "root", "root");
+         Statement statement = connection.createStatement()) {
+      boolean hasResultSet = statement.execute(
+              "select last_value(temperature) from "
+                      + "root.ln.wf01.wt01 "
+                      + "GROUP BY ((5, 40], 5ms) FILL(int32[previous])");
+
+      assertTrue(hasResultSet);
+      int cnt;
+      try (ResultSet resultSet = statement.getResultSet()) {
+        cnt = 0;
+        while (resultSet.next()) {
+          String ans = resultSet.getString(TIMESTAMP_STR) + "," + resultSet
+                  .getString(last_value("root.ln.wf01.wt01.temperature"));
+          assertEquals(retArray[cnt], ans);
+          cnt++;
+        }
+        assertEquals(retArray.length, cnt);
+      }
+
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+
+  }
+
+  @Test
   public void previousAllTest() {
     String[] retArray = new String[] {
             "2,null,null",
@@ -406,6 +445,47 @@ public class IoTDBGroupByFillIT {
   }
 
   @Test
+  public void leftORightCPreviousUntilLastTest() {
+    String[] retArray = new String[] {
+            "9,null",
+            "14,25",
+            "19,25",
+            "24,25",
+            "29,26",
+            "34,26",
+            "39,40",
+            "44,null"
+    };
+
+    try (Connection connection = DriverManager.
+            getConnection("jdbc:iotdb://127.0.0.1:6667/", "root", "root");
+         Statement statement = connection.createStatement()) {
+      boolean hasResultSet = statement.execute(
+              "select last_value(temperature) from "
+                      + "root.ln.wf01.wt01 "
+                      + "GROUP BY ((4, 44], 5ms) FILL(int32[previousUntilLast])");
+
+      assertTrue(hasResultSet);
+      int cnt;
+      try (ResultSet resultSet = statement.getResultSet()) {
+        cnt = 0;
+        while (resultSet.next()) {
+          String ans = resultSet.getString(TIMESTAMP_STR) + "," + resultSet
+                  .getString(last_value("root.ln.wf01.wt01.temperature"));
+          assertEquals(retArray[cnt], ans);
+          cnt++;
+        }
+        assertEquals(retArray.length, cnt);
+      }
+
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+
+  }
+
+  @Test
   public void previousUntilLastAllTest() {
     String[] retArray = new String[] {
             "2,null,null",