You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by qi...@apache.org on 2020/06/06 11:29:05 UTC

[incubator-iotdb] 02/02: [IOTDB-622] split group by level to two parts

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

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

commit 2e5d6820fa5930dd19ac8feba3e3831462d443f1
Author: mychaow <94...@qq.com>
AuthorDate: Sun May 24 17:19:07 2020 +0800

    [IOTDB-622] split group by level to two parts
---
 docs/SystemDesign/DataQuery/AggregationQuery.md    |  22 +++
 docs/SystemDesign/DataQuery/GroupByQuery.md        |   2 +-
 .../DML Data Manipulation Language.md              |  65 ++++---
 docs/UserGuide/Operation Manual/SQL Reference.md   |   5 +-
 docs/zh/SystemDesign/DataQuery/AggregationQuery.md |  20 ++
 docs/zh/SystemDesign/DataQuery/GroupByQuery.md     |   2 +-
 .../DML Data Manipulation Language.md              |  73 +++++---
 .../zh/UserGuide/Operation Manual/SQL Reference.md |   5 +-
 .../org/apache/iotdb/db/qp/strategy/SqlBase.g4     |   6 +-
 .../org/apache/iotdb/db/metadata/MetaUtils.java    |   2 +-
 .../apache/iotdb/db/qp/executor/PlanExecutor.java  |   4 +-
 .../iotdb/db/qp/physical/crud/AggregationPlan.java |  10 +
 ...yFillTimePlan.java => GroupByTimeFillPlan.java} |   4 +-
 .../iotdb/db/qp/physical/crud/GroupByTimePlan.java |   9 -
 .../iotdb/db/qp/physical/crud/QueryPlan.java       |  10 -
 .../iotdb/db/qp/strategy/LogicalGenerator.java     |  45 +++--
 .../iotdb/db/qp/strategy/PhysicalGenerator.java    |  31 +--
 .../iotdb/db/query/dataset/SingleDataSet.java      |   4 +-
 .../query/dataset/groupby/GroupByFillDataSet.java  |  10 +-
 .../query/dataset/groupby/GroupByLevelDataSet.java | 208 ---------------------
 .../query/dataset/groupby/GroupByTimeDataSet.java  |  96 ++++++++++
 .../db/query/executor/AggregationExecutor.java     |  30 ++-
 .../iotdb/db/query/executor/IQueryRouter.java      |   2 +-
 .../iotdb/db/query/executor/QueryRouter.java       |  85 +++++----
 .../org/apache/iotdb/db/service/TSServiceImpl.java |  14 +-
 .../org/apache/iotdb/db/utils/FilePathUtils.java   |  45 ++++-
 .../apache/iotdb/db/qp/plan/PhysicalPlanTest.java  |  14 +-
 .../db/query/dataset/GroupByLevelDataSetTest.java  |  64 +++----
 ...ataSetTest.java => GroupByTimeDataSetTest.java} |  57 +++---
 29 files changed, 486 insertions(+), 458 deletions(-)

diff --git a/docs/SystemDesign/DataQuery/AggregationQuery.md b/docs/SystemDesign/DataQuery/AggregationQuery.md
index 486f8de..4d096d1 100644
--- a/docs/SystemDesign/DataQuery/AggregationQuery.md
+++ b/docs/SystemDesign/DataQuery/AggregationQuery.md
@@ -112,3 +112,25 @@ while (timestampGenerator.hasNext()) {
     }
   }
 ```
+
+## Aggregated query with level
+
+After aggregated query, we could also to count the total number of points of
+
+each node at the given level in current Metadata Tree.
+
+The logic is in the `AggregationExecutor` class.
+
+1. In the beginning, get the final paths group by level and the origin path index to final path.
+    > For example, we could get final path `root.sg1` by `root.sg1.d1.s0,root.sg1.d2.s1` and `level=1`.
+
+2. Then, get the aggregated query result: RowRecord.
+
+3. Finally, merge each RowRecord to NewRecord, which has fields like <final path, count>.
+
+    > For example, we will get new RowRecord `<root.sg1,7>` by `<root.sg1.d1.s0, 3>, <root.sg1.d2.s1, 4>` and level=1.
+
+
+> Attention:
+> 1. only support count aggregation
+> 2. root's level == 0
\ No newline at end of file
diff --git a/docs/SystemDesign/DataQuery/GroupByQuery.md b/docs/SystemDesign/DataQuery/GroupByQuery.md
index b11eaae..7ec7173 100644
--- a/docs/SystemDesign/DataQuery/GroupByQuery.md
+++ b/docs/SystemDesign/DataQuery/GroupByQuery.md
@@ -266,7 +266,7 @@ After down-frequency query, we could also to count the total number of points of
 
 each node at the given level in current Metadata Tree.
 
-The logic is in the `GroupByLevelDataSet` class.
+The logic is in the `GroupByTimeDataSet` class.
 
 1. In the beginning, get the final paths group by level and the origin path index to final path.
     > For example, we could get final path `root.sg1` by `root.sg1.d1.s0,root.sg1.d2.s1` and `level=1`.
diff --git a/docs/UserGuide/Operation Manual/DML Data Manipulation Language.md b/docs/UserGuide/Operation Manual/DML Data Manipulation Language.md
index 113a6ca..27afcb7 100644
--- a/docs/UserGuide/Operation Manual/DML Data Manipulation Language.md	
+++ b/docs/UserGuide/Operation Manual/DML Data Manipulation Language.md	
@@ -146,6 +146,46 @@ The selected timeseries are "the power supply status of ln group wf01 plant wt01
 The execution result of this SQL statement is as follows:
 <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/13203019/51577450-dcfe0800-1ef4-11e9-9399-4ba2b2b7fb73.jpg"></center>
 
+### Aggregate Query
+This section mainly introduces the related examples of aggregate query.
+
+#### Count Points
+
+```
+select count(status) from root.ln.wf01.wt01;
+```
+
+| count(root.ln.wf01.wt01.status) |
+| -------------- |
+| 4              |
+
+
+##### Count Points By Level
+
+Level could be defined to show count the number of points of each node at the given level in current Metadata Tree.
+
+This could be used to query the number of points under each device.
+
+The SQL statement is:
+
+```
+select count(status) from root.ln.wf01.wt01 group by level=1;
+```
+
+
+| Time   | count(root.ln) |
+| ------ | -------------- |
+| 0      | 7              |
+
+
+```
+select count(status) from root.ln.wf01.wt01 group by level=2;
+```
+
+| Time   | count(root.ln.wf01) | count(root.ln.wf02) |
+| ------ | ------------------- | ------------------- |
+| 0      | 4                   | 3                   |
+
 ### Down-Frequency Aggregate Query
 
 This section mainly introduces the related examples of down-frequency aggregation query, 
@@ -263,7 +303,7 @@ We will get the result like following:
 | 40     | 5                               |
 
 
-### Down-Frequency Aggregate Query with Level Clause
+#### Down-Frequency Aggregate Query with Level Clause
 
 Level could be defined to show count the number of points of each node at the given level in current Metadata Tree.
 
@@ -271,28 +311,7 @@ This could be used to query the number of points under each device.
 
 The SQL statement is:
 
-This means aggregate query only with path by level.
-
-```
-select count(status) from root.ln.wf01.wt01 group by level=1;
-```
-
-
-| Time   | count(root.ln) |
-| ------ | -------------- |
-| 0      | 7              |
-
-
-```
-select count(status) from root.ln.wf01.wt01 group by level=2;
-```
-
-| Time   | count(root.ln.wf01) | count(root.ln.wf02) |
-| ------ | ------------------- | ------------------- |
-| 0      | 4                   | 3                   |
-
-
-We can also get down-frequency agggate query by level.
+Get down-frequency aggregate query by level.
 
 ```
 select count(status) from root.ln.wf01.wt01 group by ([0,20),3ms), level=1;
diff --git a/docs/UserGuide/Operation Manual/SQL Reference.md b/docs/UserGuide/Operation Manual/SQL Reference.md
index 53eadd7..3e15816 100644
--- a/docs/UserGuide/Operation Manual/SQL Reference.md	
+++ b/docs/UserGuide/Operation Manual/SQL Reference.md	
@@ -338,6 +338,7 @@ Eg. IoTDB > SELECT MIN_TIME(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf
 Eg. IoTDB > SELECT MAX_TIME(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature > 24
 Eg. IoTDB > SELECT MIN_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature > 23
 Eg. IoTDB > SELECT MAX_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature < 25
+Eg. IoTDB > SELECT COUNT(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature < 25 GROUP BY LEVEL=1
 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: In Version 0.7.0, if <WhereClause> includes `OR`, time filter can not be used.
@@ -347,7 +348,7 @@ Note: There must be a space on both sides of the plus and minus operator appeari
 * Group By Statement
 
 ```
-SELECT <SelectClause> FROM <FromClause> WHERE  <WhereClause> GROUP BY <GroupByClause>
+SELECT <SelectClause> FROM <FromClause> WHERE  <WhereClause> GROUP BY <GroupByTimeClause>
 SelectClause : <Function> [COMMA < Function >]*
 Function : <AggregationFunction> LPAREN <Path> RPAREN
 FromClause : <PrefixPath>
@@ -358,7 +359,7 @@ TimeExpr : TIME PrecedenceEqualOperator (<TimeValue> | <RelativeTime>)
 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
+GroupByTimeClause : LPAREN <TimeInterval> COMMA <TimeUnit> (COMMA <TimeUnit>)? RPAREN
 TimeInterval: LSBRACKET <TimeValue> COMMA <TimeValue> RRBRACKET | LRBRACKET <TimeValue> COMMA <TimeValue> RSBRACKET
 TimeUnit : Integer <DurationUnit>
 DurationUnit : "ms" | "s" | "m" | "h" | "d" | "w"
diff --git a/docs/zh/SystemDesign/DataQuery/AggregationQuery.md b/docs/zh/SystemDesign/DataQuery/AggregationQuery.md
index 56b2589..519d147 100644
--- a/docs/zh/SystemDesign/DataQuery/AggregationQuery.md
+++ b/docs/zh/SystemDesign/DataQuery/AggregationQuery.md
@@ -112,3 +112,23 @@ while (timestampGenerator.hasNext()) {
     }
   }
 ```
+
+## 使用Level来统计点数
+
+对于count聚合查询,我们也可以使用level关键字来进一步汇总点数。
+
+这个逻辑在 `AggregationExecutor`类里。
+
+1. 首先,把所有涉及到的时序按level来进行汇集,最后的路径。
+    > 例如把root.sg1.d1.s0,root.sg1.d2.s1按level=1汇集成root.sg1。
+
+2. 然后调用上述的聚合逻辑求出所有时序的总点数信息,这个会返回RowRecord数据结构。
+
+3. 最后,把聚合查询返回的RowRecord按上述的final paths,进行累加,组合成新的RowRecord。
+
+    > 例如,把《root.sg1.d1.s0,3》,《root.sg1.d2.s1,4》聚合成《root.sg1,7》
+
+
+> 注意:
+> 1. 这里只支持count操作
+> 2. root的层级level=0
\ No newline at end of file
diff --git a/docs/zh/SystemDesign/DataQuery/GroupByQuery.md b/docs/zh/SystemDesign/DataQuery/GroupByQuery.md
index 0c12398..2b32473 100644
--- a/docs/zh/SystemDesign/DataQuery/GroupByQuery.md
+++ b/docs/zh/SystemDesign/DataQuery/GroupByQuery.md
@@ -271,7 +271,7 @@ for (int cnt = 1; cnt < timeStampFetchSize && timestampGenerator.hasNext(); cnt+
 
 降采样后,我们也可以使用level关键字来进一步汇总点数。
 
-这个逻辑在 `GroupByLevelDataSet`类里。
+这个逻辑在 `GroupByTimeDataSet`类里。
 
 1. 首先,把所有涉及到的时序按level来进行汇集,最后的路径。
     > 例如把root.sg1.d1.s0,root.sg1.d2.s1按level=1汇集成root.sg1。
diff --git a/docs/zh/UserGuide/Operation Manual/DML Data Manipulation Language.md b/docs/zh/UserGuide/Operation Manual/DML Data Manipulation Language.md
index 87cf119..21a9828 100644
--- a/docs/zh/UserGuide/Operation Manual/DML Data Manipulation Language.md	
+++ b/docs/zh/UserGuide/Operation Manual/DML Data Manipulation Language.md	
@@ -170,6 +170,48 @@ select s1,s2 from root.sg1.* GROUP BY DEVICE
 
 'disable align' 意味着每条时序就有3列存在。更多语法请参照 SQL REFERENCE.
 
+### 聚合查询
+本章节主要介绍聚合查询的相关示例,
+主要使用的是IoTDB SELECT语句的聚合查询函数。
+
+#### 统计总点数
+
+
+```
+select count(status) from root.ln.wf01.wt01;
+```
+
+| count(root.ln.wf01.wt01.status) |
+| -------------- |
+| 4              |
+
+
+##### 按层级统计
+通过定义LEVEL来统计指定层级下的数据点个数。
+
+这可以用来查询不同层级下的数据点总个数
+
+语法是:
+
+这个可以用来查询某个路径下的总数据点数
+
+```
+select count(status) from root.ln.wf01.wt01 group by level=1;
+```
+
+
+| Time   | count(root.ln) |
+| ------ | -------------- |
+| 0      | 7              |
+
+
+```
+select count(status) from root.ln.wf01.wt01 group by level=2;
+```
+
+| Time   | count(root.ln.wf01) | count(root.ln.wf02) |
+| ------ | ------------------- | ------------------- |
+| 0      | 4                   | 3                   |
 
 ### 降频聚合查询
 
@@ -295,36 +337,13 @@ SQL执行后的结果集如下所示:
 | 35     | 3                               |
 | 40     | 5                               |
 
-### 降采样后按Level聚合查询
+#### 降采样后按Level聚合查询
 
 除此之外,还可以通过定义LEVEL来统计指定层级下的数据点个数。
 
-这可以用来查询不同层级下的数据点总个数
-
-语法是:
-
-这个可以用来查询某个路径下的总数据点数
-
-```
-select count(status) from root.ln.wf01.wt01 group by level=1;
-```
-
-
-| Time   | count(root.ln) |
-| ------ | -------------- |
-| 0      | 7              |
-
-
-```
-select count(status) from root.ln.wf01.wt01 group by level=2;
-```
-
-| Time   | count(root.ln.wf01) | count(root.ln.wf02) |
-| ------ | ------------------- | ------------------- |
-| 0      | 4                   | 3                   |
-
+例如:
 
-也可以用来统计降采样后的数据点个数
+统计降采样后的数据点个数
 
 ```
 select count(status) from root.ln.wf01.wt01 group by ([0,20),3ms), level=1;
@@ -717,7 +736,7 @@ SQL语句将不会执行,并且相应的错误提示如下:
 
 <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/13203019/51577867-577b5780-1ef6-11e9-978c-e02c1294bcc5.jpg"></center>
 
-#### Row and Column Control over Query Results
+#### 控制查询结果的行和列
 
 除了对查询结果进行行或列控制之外,IoTDB还允许用户控制查询结果的行和列。 这是同时包含LIMIT子句和SLIMIT子句的完整示例。
 
diff --git a/docs/zh/UserGuide/Operation Manual/SQL Reference.md b/docs/zh/UserGuide/Operation Manual/SQL Reference.md
index add2f04..6a6fb5a 100644
--- a/docs/zh/UserGuide/Operation Manual/SQL Reference.md	
+++ b/docs/zh/UserGuide/Operation Manual/SQL Reference.md	
@@ -329,6 +329,7 @@ Eg. IoTDB > SELECT MIN_TIME(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf
 Eg. IoTDB > SELECT MAX_TIME(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature > 24
 Eg. IoTDB > SELECT MIN_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature > 23
 Eg. IoTDB > SELECT MAX_VALUE(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature < 25
+Eg. IoTDB > SELECT COUNT(temperature) FROM root.ln.wf01.wt01 WHERE root.ln.wf01.wt01.temperature < 25 GROUP BY LEVEL=1
 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: In Version 0.7.0, if <WhereClause> includes `OR`, time filter can not be used.
@@ -338,7 +339,7 @@ Note: There must be a space on both sides of the plus and minus operator appeari
 * Group By 语句
 
 ```
-SELECT <SelectClause> FROM <FromClause> WHERE  <WhereClause> GROUP BY <GroupByClause>
+SELECT <SelectClause> FROM <FromClause> WHERE  <WhereClause> GROUP BY <GroupByTimeClause>
 SelectClause : <Function> [COMMA < Function >]*
 Function : <AggregationFunction> LPAREN <Path> RPAREN
 FromClause : <PrefixPath>
@@ -349,7 +350,7 @@ TimeExpr : TIME PrecedenceEqualOperator (<TimeValue> | <RelativeTime>)
 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
+GroupByTimeClause : LPAREN <TimeInterval> COMMA <TimeUnit> (COMMA <TimeUnit>)? RPAREN
 TimeInterval: LSBRACKET <TimeValue> COMMA <TimeValue> RRBRACKET | LRBRACKET <TimeValue> COMMA <TimeValue> RSBRACKET
 TimeUnit : Integer <DurationUnit>
 DurationUnit : "ms" | "s" | "m" | "h" | "d" | "w"
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 b9c30ea..a7b8904 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
@@ -196,6 +196,7 @@ specialClause
     | groupByFillClause
     | fillClause slimitClause? alignByDeviceClauseOrDisableAlign?
     | alignByDeviceClauseOrDisableAlign
+    | groupByLevelClause specialLimit?
     ;
 
 specialLimit
@@ -246,7 +247,6 @@ groupByTimeClause
       COMMA DURATION
       (COMMA DURATION)?
       RR_BRACKET
-    | GROUP BY LEVEL OPERATOR_EQ INT
     | GROUP BY LR_BRACKET
             timeInterval
             COMMA DURATION
@@ -263,6 +263,10 @@ groupByFillClause
       FILL LR_BRACKET typeClause (COMMA typeClause)* RR_BRACKET
      ;
 
+groupByLevelClause
+    : GROUP BY LEVEL OPERATOR_EQ INT
+    ;
+
 typeClause
     : dataType LS_BRACKET linearClause RS_BRACKET
     | dataType LS_BRACKET previousClause RS_BRACKET
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/MetaUtils.java b/server/src/main/java/org/apache/iotdb/db/metadata/MetaUtils.java
index de3e9c1..0e1d953 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/MetaUtils.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/MetaUtils.java
@@ -24,7 +24,7 @@ import org.apache.iotdb.db.exception.metadata.MetadataException;
 
 import static org.apache.iotdb.db.conf.IoTDBConstant.PATH_WILDCARD;
 
-class MetaUtils {
+public class MetaUtils {
 
   public static final String PATH_SEPARATOR = "\\.";
 
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java b/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
index dd84071..8c6b228 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/executor/PlanExecutor.java
@@ -292,8 +292,8 @@ public class PlanExecutor implements IPlanExecutor {
       if (queryPlan.getPaths() == null || queryPlan.getPaths().isEmpty()) {
         // no time series are selected, return EmptyDataSet
         return new EmptyDataSet();
-      } else if (queryPlan instanceof GroupByFillTimePlan) {
-        GroupByFillTimePlan groupByFillPlan = (GroupByFillTimePlan) queryPlan;
+      } else if (queryPlan instanceof GroupByTimeFillPlan) {
+        GroupByTimeFillPlan groupByFillPlan = (GroupByTimeFillPlan) queryPlan;
         return queryRouter.groupByFill(groupByFillPlan, context);
       } else if (queryPlan instanceof GroupByTimePlan) {
         GroupByTimePlan groupByTimePlan = (GroupByTimePlan) queryPlan;
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AggregationPlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AggregationPlan.java
index 1f06dbf..972be84 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AggregationPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/AggregationPlan.java
@@ -31,6 +31,8 @@ public class AggregationPlan extends RawDataQueryPlan {
   private List<String> aggregations = new ArrayList<>();
   private List<String> deduplicatedAggregations = new ArrayList<>();
 
+  private int level = -1;
+
   public AggregationPlan() {
     super();
     setOperatorType(Operator.OperatorType.AGGREGATION);
@@ -56,4 +58,12 @@ public class AggregationPlan extends RawDataQueryPlan {
   public void setDeduplicatedAggregations(List<String> deduplicatedAggregations) {
     this.deduplicatedAggregations = deduplicatedAggregations;
   }
+
+  public int getLevel() {
+    return level;
+  }
+
+  public void setLevel(int level) {
+    this.level = level;
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByFillTimePlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByTimeFillPlan.java
similarity index 93%
rename from server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByFillTimePlan.java
rename to server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByTimeFillPlan.java
index 55400f7..c5d741b 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByFillTimePlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByTimeFillPlan.java
@@ -24,11 +24,11 @@ import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 
 import java.util.Map;
 
-public class GroupByFillTimePlan extends GroupByTimePlan {
+public class GroupByTimeFillPlan extends GroupByTimePlan {
 
   private Map<TSDataType, IFill> fillTypes;
 
-  public GroupByFillTimePlan() {
+  public GroupByTimeFillPlan() {
     super();
     setOperatorType(Operator.OperatorType.GROUP_BY_FILL);
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByTimePlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByTimePlan.java
index cc76465..7d00bdd 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByTimePlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/GroupByTimePlan.java
@@ -33,8 +33,6 @@ public class GroupByTimePlan extends AggregationPlan {
   // if it is left close and right open interval
   private boolean leftCRightO = true;
 
-  private boolean byTime = false;
-
   public GroupByTimePlan() {
     super();
     setOperatorType(Operator.OperatorType.GROUPBYTIME);
@@ -80,11 +78,4 @@ public class GroupByTimePlan extends AggregationPlan {
     this.leftCRightO = leftCRightO;
   }
 
-  public boolean isByTime() {
-    return byTime;
-  }
-
-  public void setByTime(boolean isByTime) {
-    this.byTime = isByTime;
-  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/QueryPlan.java b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/QueryPlan.java
index 7f3b9bd..58b4c13 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/QueryPlan.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/physical/crud/QueryPlan.java
@@ -36,8 +36,6 @@ public abstract class QueryPlan extends PhysicalPlan {
   private int rowLimit = 0;
   private int rowOffset = 0;
 
-  private int level = -1;
-
   private Map<String, Integer> pathToIndex = new HashMap<>();
 
   public QueryPlan() {
@@ -101,12 +99,4 @@ public abstract class QueryPlan extends PhysicalPlan {
   public Map<String, Integer> getPathToIndex() {
     return pathToIndex;
   }
-
-  public int getLevel() {
-    return level;
-  }
-
-  public void setLevel(int level) {
-    this.level = level;
-  }
 }
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 41dfd6a..dda397d 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
@@ -96,7 +96,7 @@ import org.apache.iotdb.db.qp.strategy.SqlBaseParser.GrantRoleContext;
 import org.apache.iotdb.db.qp.strategy.SqlBaseParser.GrantRoleToUserContext;
 import org.apache.iotdb.db.qp.strategy.SqlBaseParser.GrantUserContext;
 import org.apache.iotdb.db.qp.strategy.SqlBaseParser.GrantWatermarkEmbeddingContext;
-import org.apache.iotdb.db.qp.strategy.SqlBaseParser.GroupByClauseContext;
+import org.apache.iotdb.db.qp.strategy.SqlBaseParser.GroupByTimeClauseContext;
 import org.apache.iotdb.db.qp.strategy.SqlBaseParser.InClauseContext;
 import org.apache.iotdb.db.qp.strategy.SqlBaseParser.InsertColumnSpecContext;
 import org.apache.iotdb.db.qp.strategy.SqlBaseParser.InsertStatementContext;
@@ -164,6 +164,8 @@ import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
 import org.apache.iotdb.tsfile.read.common.Path;
 import org.apache.iotdb.tsfile.utils.StringContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * This class is a listener and you can get an operator which is a logical plan.
@@ -788,9 +790,8 @@ public class LogicalGenerator extends SqlBaseBaseListener {
   @Override
   public void enterGroupByFillClause(SqlBaseParser.GroupByFillClauseContext ctx) {
     super.enterGroupByFillClause(ctx);
-    queryOp.setGroupByLevel(true);
-    queryOp.setFill(true);
     queryOp.setGroupByTime(true);
+    queryOp.setFill(true);
     queryOp.setLeftCRightO(ctx.timeInterval().LS_BRACKET() != null);
 
 
@@ -864,33 +865,37 @@ public class LogicalGenerator extends SqlBaseBaseListener {
   @Override
   public void enterGroupByTimeClause(GroupByTimeClauseContext ctx) {
     super.enterGroupByTimeClause(ctx);
-    queryOp.setGroupByLevel(true);
 
-    if (ctx.timeInterval() != null) {
-      queryOp.setGroupByTime(true);
-      queryOp.setLeftCRightO(ctx.timeInterval().LS_BRACKET() != null);
-      // parse timeUnit
-      queryOp.setUnit(parseDuration(ctx.DURATION(0).getText()));
-      queryOp.setSlidingStep(queryOp.getUnit());
-      // parse sliding step
-      if (ctx.DURATION().size() == 2) {
-        queryOp.setSlidingStep(parseDuration(ctx.DURATION(1).getText()));
-        if (queryOp.getSlidingStep() < queryOp.getUnit()) {
-          throw new SQLParserException(
-            "The third parameter sliding step shouldn't be smaller than the second parameter time interval.");
-        }
+    queryOp.setGroupByTime(true);
+    queryOp.setLeftCRightO(ctx.timeInterval().LS_BRACKET() != null);
+    // parse timeUnit
+    queryOp.setUnit(parseDuration(ctx.DURATION(0).getText()));
+    queryOp.setSlidingStep(queryOp.getUnit());
+    // parse sliding step
+    if (ctx.DURATION().size() == 2) {
+      queryOp.setSlidingStep(parseDuration(ctx.DURATION(1).getText()));
+      if (queryOp.getSlidingStep() < queryOp.getUnit()) {
+        throw new SQLParserException(
+          "The third parameter sliding step shouldn't be smaller than the second parameter time interval.");
       }
-
-      parseTimeInterval(ctx.timeInterval());
     }
 
+    parseTimeInterval(ctx.timeInterval());
+
     if (ctx.INT() != null) {
-      logger.debug("group by level:" + ctx.INT().getText() );
       queryOp.setLevel(Integer.parseInt(ctx.INT().getText()));
     }
   }
 
   @Override
+  public void enterGroupByLevelClause(SqlBaseParser.GroupByLevelClauseContext ctx) {
+    super.enterGroupByLevelClause(ctx);
+    queryOp.setGroupByLevel(true);
+
+    queryOp.setLevel(Integer.parseInt(ctx.INT().getText()));
+  }
+
+  @Override
   public void enterFillClause(FillClauseContext ctx) {
     super.enterFillClause(ctx);
     FilterOperator filterOperator = queryOp.getFilterOperator();
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 33d7e33..f0024fd 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
@@ -283,11 +283,11 @@ public class PhysicalGenerator {
   private PhysicalPlan transformQuery(QueryOperator queryOperator) throws QueryProcessException {
     QueryPlan queryPlan;
 
-    if (queryOperator.isGroupByLevel() && queryOperator.isFill()) {
-      queryPlan = new GroupByFillTimePlan();
-      ((GroupByFillTimePlan) queryPlan).setInterval(queryOperator.getUnit());
-      ((GroupByFillTimePlan) queryPlan).setSlidingStep(queryOperator.getSlidingStep());
-      ((GroupByFillTimePlan) queryPlan).setLeftCRightO(queryOperator.isLeftCRightO());
+    if (queryOperator.isGroupByTime() && queryOperator.isFill()) {
+      queryPlan = new GroupByTimeFillPlan();
+      ((GroupByTimeFillPlan) queryPlan).setInterval(queryOperator.getUnit());
+      ((GroupByTimeFillPlan) queryPlan).setSlidingStep(queryOperator.getSlidingStep());
+      ((GroupByTimeFillPlan) queryPlan).setLeftCRightO(queryOperator.isLeftCRightO());
       if (!queryOperator.isLeftCRightO()) {
         ((GroupByTimePlan) queryPlan).setStartTime(queryOperator.getStartTime() + 1);
         ((GroupByTimePlan) queryPlan).setEndTime(queryOperator.getEndTime() + 1);
@@ -295,16 +295,15 @@ public class PhysicalGenerator {
         ((GroupByTimePlan) queryPlan).setStartTime(queryOperator.getStartTime());
         ((GroupByTimePlan) queryPlan).setEndTime(queryOperator.getEndTime());
       }
-      ((GroupByFillTimePlan) queryPlan)
+      ((GroupByTimeFillPlan) queryPlan)
           .setAggregations(queryOperator.getSelectOperator().getAggregations());
       for (String aggregation : queryPlan.getAggregations()) {
         if (!SQLConstant.LAST_VALUE.equals(aggregation)) {
           throw new QueryProcessException("Group By Fill only support last_value function");
         }
       }
-      ((GroupByFillTimePlan) queryPlan).setFillType(queryOperator.getFillTypes());
-      ((GroupByFillTimePlan) queryPlan).setByTime(queryOperator.isGroupByTime());
-    } else if (queryOperator.isGroupByLevel()) {
+      ((GroupByTimeFillPlan) queryPlan).setFillType(queryOperator.getFillTypes());
+    } else if (queryOperator.isGroupByTime()) {
       queryPlan = new GroupByTimePlan();
       ((GroupByTimePlan) queryPlan).setInterval(queryOperator.getUnit());
       ((GroupByTimePlan) queryPlan).setSlidingStep(queryOperator.getSlidingStep());
@@ -319,12 +318,11 @@ public class PhysicalGenerator {
       ((GroupByTimePlan) queryPlan)
           .setAggregations(queryOperator.getSelectOperator().getAggregations());
       ((GroupByTimePlan) queryPlan).setLevel(queryOperator.getLevel());
-      ((GroupByTimePlan) queryPlan).setByTime(queryOperator.isGroupByTime());
 
       if (queryOperator.getLevel() >= 0) {
         for (int i = 0; i < queryOperator.getSelectOperator().getAggregations().size(); i++) {
           if (!SQLConstant.COUNT.equals(queryOperator.getSelectOperator().getAggregations().get(i))) {
-            throw new QueryProcessException("group by level only support count");
+            throw new QueryProcessException("group by level only support count now.");
           }
         }
       }
@@ -339,8 +337,16 @@ public class PhysicalGenerator {
       ((FillQueryPlan) queryPlan).setFillType(queryOperator.getFillTypes());
     } else if (queryOperator.hasAggregation()) {
       queryPlan = new AggregationPlan();
+      ((AggregationPlan)queryPlan).setLevel(queryOperator.getLevel());
       ((AggregationPlan) queryPlan)
           .setAggregations(queryOperator.getSelectOperator().getAggregations());
+      if (queryOperator.getLevel() >= 0) {
+        for (int i = 0; i < queryOperator.getSelectOperator().getAggregations().size(); i++) {
+          if (!SQLConstant.COUNT.equals(queryOperator.getSelectOperator().getAggregations().get(i))) {
+            throw new QueryProcessException("group by level only support count now.");
+          }
+        }
+      }
     } else if (queryOperator.isLastQuery()) {
       queryPlan = new LastQueryPlan();
     } else {
@@ -361,6 +367,9 @@ public class PhysicalGenerator {
       } else if (queryPlan instanceof FillQueryPlan) {
         alignByDevicePlan.setFillQueryPlan((FillQueryPlan) queryPlan);
       } else if (queryPlan instanceof AggregationPlan) {
+        if (((AggregationPlan)queryPlan).getLevel() >= 0) {
+          throw new QueryProcessException("group by level does not support align by device now.");
+        }
         alignByDevicePlan.setAggregationPlan((AggregationPlan) queryPlan);
       }
 
diff --git a/server/src/main/java/org/apache/iotdb/db/query/dataset/SingleDataSet.java b/server/src/main/java/org/apache/iotdb/db/query/dataset/SingleDataSet.java
index 668b28f..9580b63 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/dataset/SingleDataSet.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/dataset/SingleDataSet.java
@@ -39,12 +39,12 @@ public class SingleDataSet extends QueryDataSet {
   }
 
   @Override
-  protected boolean hasNextWithoutConstraint() throws IOException {
+  public boolean hasNextWithoutConstraint() throws IOException {
     return i == 0;
   }
 
   @Override
-  protected RowRecord nextWithoutConstraint() throws IOException {
+  public RowRecord nextWithoutConstraint() throws IOException {
     i++;
     return record;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByFillDataSet.java b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByFillDataSet.java
index d826f23..697cd3b 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByFillDataSet.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByFillDataSet.java
@@ -21,7 +21,7 @@ package org.apache.iotdb.db.query.dataset.groupby;
 import org.apache.iotdb.db.conf.IoTDBDescriptor;
 import org.apache.iotdb.db.exception.StorageEngineException;
 import org.apache.iotdb.db.exception.query.QueryProcessException;
-import org.apache.iotdb.db.qp.physical.crud.GroupByFillTimePlan;
+import org.apache.iotdb.db.qp.physical.crud.GroupByTimeFillPlan;
 import org.apache.iotdb.db.query.context.QueryContext;
 import org.apache.iotdb.db.query.executor.LastQueryExecutor;
 import org.apache.iotdb.db.query.executor.fill.IFill;
@@ -49,7 +49,7 @@ public class GroupByFillDataSet extends QueryDataSet {
 
   public GroupByFillDataSet(List<Path> paths, List<TSDataType> dataTypes,
       GroupByEngineDataSet groupByEngineDataSet,
-      Map<TSDataType, IFill> fillTypes, QueryContext context, GroupByFillTimePlan groupByFillPlan)
+      Map<TSDataType, IFill> fillTypes, QueryContext context, GroupByTimeFillPlan groupByFillPlan)
       throws StorageEngineException, IOException, QueryProcessException {
     super(paths, dataTypes);
     this.groupByEngineDataSet = groupByEngineDataSet;
@@ -57,8 +57,8 @@ public class GroupByFillDataSet extends QueryDataSet {
     initPreviousParis(context, groupByFillPlan);
     initLastTimeArray(context, groupByFillPlan);
   }
-  
-  private void initPreviousParis(QueryContext context, GroupByFillTimePlan groupByFillPlan)
+
+  private void initPreviousParis(QueryContext context, GroupByTimeFillPlan groupByFillPlan)
           throws StorageEngineException, IOException, QueryProcessException {
     previousValue = new Object[paths.size()];
     for (int i = 0; i < paths.size(); i++) {
@@ -85,7 +85,7 @@ public class GroupByFillDataSet extends QueryDataSet {
     }
   }
 
-  private void initLastTimeArray(QueryContext context, GroupByFillTimePlan groupByFillPlan)
+  private void initLastTimeArray(QueryContext context, GroupByTimeFillPlan groupByFillPlan)
       throws IOException, StorageEngineException, QueryProcessException {
     lastTimeArray = new long[paths.size()];
     Arrays.fill(lastTimeArray, Long.MAX_VALUE);
diff --git a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByLevelDataSet.java b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByLevelDataSet.java
deleted file mode 100644
index b24c610..0000000
--- a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByLevelDataSet.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.iotdb.db.query.dataset.groupby;
-
-import org.apache.iotdb.db.exception.StorageEngineException;
-import org.apache.iotdb.db.exception.query.QueryProcessException;
-import org.apache.iotdb.db.qp.physical.crud.GroupByTimePlan;
-import org.apache.iotdb.db.query.aggregation.AggregateResult;
-import org.apache.iotdb.db.query.context.QueryContext;
-import org.apache.iotdb.db.query.factory.AggregateResultFactory;
-import org.apache.iotdb.db.query.filter.TsFileFilter;
-import org.apache.iotdb.db.utils.FilePathUtils;
-import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
-import org.apache.iotdb.tsfile.read.common.Field;
-import org.apache.iotdb.tsfile.read.common.Path;
-import org.apache.iotdb.tsfile.read.common.RowRecord;
-import org.apache.iotdb.tsfile.read.filter.basic.Filter;
-import org.apache.iotdb.tsfile.read.query.dataset.QueryDataSet;
-import org.apache.iotdb.tsfile.utils.Binary;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.*;
-
-import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_COLUMN;
-import static org.apache.iotdb.db.conf.IoTDBConstant.COLUMN_COUNT;
-
-public class GroupByLevelDataSet extends QueryDataSet {
-
-  private static final Logger logger = LoggerFactory
-    .getLogger(GroupByLevelDataSet.class);
-
-  private List<RowRecord> records = new ArrayList<>();
-  private int index = 0;
-
-  protected long queryId;
-  private GroupByTimePlan groupByTimePlan;
-  private QueryContext context;
-
-  private Map<Path, GroupByExecutor> pathExecutors = new HashMap<>();
-  private Map<Path, List<Integer>> resultIndexes = new HashMap<>();
-
-  public GroupByLevelDataSet(QueryContext context, GroupByTimePlan plan, GroupByEngineDataSet dataSet)
-    throws QueryProcessException, StorageEngineException, IOException {
-    this.queryId = context.getQueryId();
-    this.paths = plan.getPaths();
-    this.dataTypes = plan.getDataTypes();
-    this.groupByTimePlan = plan;
-    this.context = context;
-
-    if (logger.isDebugEnabled()) {
-      logger.debug("paths " + this.paths + " level:" + plan.getLevel());
-    }
-
-    Map<Integer, String> pathIndex = new HashMap<>();
-    Map<String, Long> finalPaths = FilePathUtils.getPathByLevel(plan.getPaths(), plan.getLevel(), pathIndex);
-
-    if (!plan.isByTime()) {
-      // does not has time interval,
-      // so we could group by time interval [MIN_VALUE, MAX_VALUE] to get the total number
-      initGroupByLevel();
-      RowRecord record = mergeRecordByPath(getRecordWithoutTimeInterval(), finalPaths, pathIndex);
-      if (record != null) {
-        records.add(record);
-      }
-    } else {
-      // get all records from GroupByDataSet, then we merge every record
-      if (logger.isDebugEnabled()) {
-        logger.debug("only group by level, paths:" + groupByTimePlan.getPaths());
-      }
-      while (dataSet != null && dataSet.hasNextWithoutConstraint()) {
-        RowRecord curRecord = mergeRecordByPath(dataSet.nextWithoutConstraint(), finalPaths, pathIndex);
-        if (curRecord != null) {
-          records.add(curRecord);
-        }
-      }
-    }
-
-    this.dataTypes = new ArrayList<>();
-    this.paths = new ArrayList<>();
-    for (int i = 0; i < finalPaths.size(); i++) {
-      this.dataTypes.add(TSDataType.INT64);
-    }
-  }
-
-  @Override
-  protected boolean hasNextWithoutConstraint() throws IOException {
-    return index < records.size();
-  }
-
-  @Override
-  protected RowRecord nextWithoutConstraint() {
-    return records.get(index++);
-  }
-
-  private void initGroupByLevel()
-    throws QueryProcessException, StorageEngineException {
-    // get all aggregation results, then we package them to one record
-    for (int i = 0; i < paths.size(); i++) {
-      Path path = paths.get(i);
-      if (!pathExecutors.containsKey(path)) {
-        //init GroupByExecutor
-        pathExecutors.put(path,
-          getGroupByExecutor(path, groupByTimePlan.getAllMeasurementsInDevice(path.getDevice()), dataTypes.get(i), this.context, null, null));
-        resultIndexes.put(path, new ArrayList<>());
-      } else {
-        throw new QueryProcessException("duplicated path found, path:" + path);
-      }
-      resultIndexes.get(path).add(i);
-      AggregateResult aggrResult = AggregateResultFactory
-        .getAggrResultByName(groupByTimePlan.getDeduplicatedAggregations().get(i), dataTypes.get(i));
-      pathExecutors.get(path).addAggregateResult(aggrResult);
-    }
-  }
-
-  private GroupByExecutor getGroupByExecutor(Path path, Set<String> allSensors, TSDataType dataType,
-                                             QueryContext context, Filter timeFilter, TsFileFilter fileFilter)
-    throws StorageEngineException, QueryProcessException {
-    return new LocalGroupByExecutor(path, allSensors, dataType, context, timeFilter, fileFilter);
-  }
-
-  private RowRecord getRecordWithoutTimeInterval()
-    throws IOException {
-    RowRecord record = new RowRecord(0);
-    AggregateResult[] fields = new AggregateResult[paths.size()];
-
-    try {
-      for (Map.Entry<Path, GroupByExecutor> pathToExecutorEntry : pathExecutors.entrySet()) {
-        GroupByExecutor executor = pathToExecutorEntry.getValue();
-        List<AggregateResult> aggregations = executor.calcResult(Long.MIN_VALUE, Long.MAX_VALUE);
-        for (int i = 0; i < aggregations.size(); i++) {
-          int resultIndex = resultIndexes.get(pathToExecutorEntry.getKey()).get(i);
-          fields[resultIndex] = aggregations.get(i);
-        }
-      }
-    } catch (QueryProcessException e) {
-      logger.error("GroupByWithoutValueFilterDataSet execute has error", e);
-      throw new IOException(e.getMessage(), e);
-    }
-
-    for (AggregateResult res : fields) {
-      if (res == null) {
-        record.addField(null);
-        continue;
-      }
-      record.addField(res.getResult(), res.getResultDataType());
-    }
-    return record;
-  }
-
-  /**
-   * merge the raw record by level, for example
-   * raw record [timestamp, root.sg1.d1.s0, root.sg1.d1.s1, root.sg1.d2.s2], level=1
-   *  and newRecord data is [100, 1, 2]
-   * return [100, 3]
-   * @param newRecord
-   * @param finalPaths
-   * @param pathIndex
-   * @return
-   */
-  private RowRecord mergeRecordByPath(RowRecord newRecord,
-                                      Map<String, Long> finalPaths,
-                                      Map<Integer, String> pathIndex) {
-    if (paths.size() != newRecord.getFields().size()) {
-      logger.error("bad record, result size not equal path size");
-      return null;
-    }
-
-    // reset final paths
-    for (Map.Entry<String, Long> entry : finalPaths.entrySet()) {
-      entry.setValue(0L);
-    }
-
-    RowRecord tmpRecord = new RowRecord(newRecord.getTimestamp());
-
-    for (int i = 0; i < newRecord.getFields().size(); i++) {
-      if (newRecord.getFields().get(i) != null) {
-        finalPaths.put(pathIndex.get(i),
-          finalPaths.get(pathIndex.get(i)) + newRecord.getFields().get(i).getLongV());
-      }
-    }
-
-    for (Map.Entry<String, Long> entry : finalPaths.entrySet()) {
-      tmpRecord.addField(Field.getField(entry.getValue(), TSDataType.INT64));
-    }
-
-    return tmpRecord;
-  }
-
-}
diff --git a/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByTimeDataSet.java b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByTimeDataSet.java
new file mode 100644
index 0000000..b655709
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/query/dataset/groupby/GroupByTimeDataSet.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.query.dataset.groupby;
+
+import org.apache.iotdb.db.exception.StorageEngineException;
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.qp.physical.crud.GroupByTimePlan;
+import org.apache.iotdb.db.query.aggregation.AggregateResult;
+import org.apache.iotdb.db.query.context.QueryContext;
+import org.apache.iotdb.db.query.factory.AggregateResultFactory;
+import org.apache.iotdb.db.query.filter.TsFileFilter;
+import org.apache.iotdb.db.utils.FilePathUtils;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.read.common.Field;
+import org.apache.iotdb.tsfile.read.common.Path;
+import org.apache.iotdb.tsfile.read.common.RowRecord;
+import org.apache.iotdb.tsfile.read.filter.basic.Filter;
+import org.apache.iotdb.tsfile.read.query.dataset.QueryDataSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.*;
+
+public class GroupByTimeDataSet extends QueryDataSet {
+
+  private static final Logger logger = LoggerFactory
+    .getLogger(GroupByTimeDataSet.class);
+
+  private List<RowRecord> records = new ArrayList<>();
+  private int index = 0;
+
+  protected long queryId;
+  private GroupByTimePlan groupByTimePlan;
+  private QueryContext context;
+
+  public GroupByTimeDataSet(QueryContext context, GroupByTimePlan plan, GroupByEngineDataSet dataSet)
+    throws QueryProcessException, StorageEngineException, IOException {
+    this.queryId = context.getQueryId();
+    this.paths = plan.getDeduplicatedPaths();
+    this.dataTypes = plan.getDeduplicatedDataTypes();
+    this.groupByTimePlan = plan;
+    this.context = context;
+
+    if (logger.isDebugEnabled()) {
+      logger.debug("paths " + this.paths + " level:" + plan.getLevel());
+    }
+
+    Map<Integer, String> pathIndex = new HashMap<>();
+    Map<String, Long> finalPaths = FilePathUtils.getPathByLevel(plan.getPaths(), plan.getLevel(), pathIndex);
+
+    // get all records from GroupByDataSet, then we merge every record
+    if (logger.isDebugEnabled()) {
+      logger.debug("only group by level, paths:" + groupByTimePlan.getPaths());
+    }
+    while (dataSet != null && dataSet.hasNextWithoutConstraint()) {
+      RowRecord curRecord = FilePathUtils.mergeRecordByPath(dataSet.nextWithoutConstraint(), finalPaths, pathIndex);
+      if (curRecord != null) {
+        records.add(curRecord);
+      }
+    }
+
+    this.dataTypes = new ArrayList<>();
+    this.paths = new ArrayList<>();
+    for (int i = 0; i < finalPaths.size(); i++) {
+      this.dataTypes.add(TSDataType.INT64);
+    }
+  }
+
+  @Override
+  protected boolean hasNextWithoutConstraint() throws IOException {
+    return index < records.size();
+  }
+
+  @Override
+  protected RowRecord nextWithoutConstraint() {
+    return records.get(index++);
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/query/executor/AggregationExecutor.java b/server/src/main/java/org/apache/iotdb/db/query/executor/AggregationExecutor.java
index 04e80f2..e7f51cd 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/executor/AggregationExecutor.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/executor/AggregationExecutor.java
@@ -36,6 +36,7 @@ import org.apache.iotdb.db.query.reader.series.IReaderByTimestamp;
 import org.apache.iotdb.db.query.reader.series.SeriesAggregateReader;
 import org.apache.iotdb.db.query.reader.series.SeriesReaderByTimestamp;
 import org.apache.iotdb.db.query.timegenerator.ServerTimeGenerator;
+import org.apache.iotdb.db.utils.FilePathUtils;
 import org.apache.iotdb.db.utils.QueryUtils;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.file.metadata.statistics.Statistics;
@@ -96,7 +97,7 @@ public class AggregationExecutor {
       }
     }
 
-    return constructDataSet(Arrays.asList(aggregateResultList));
+    return constructDataSet(Arrays.asList(aggregateResultList), aggregationPlan);
   }
 
   /**
@@ -273,7 +274,7 @@ public class AggregationExecutor {
       aggregateResults.add(result);
     }
     aggregateWithValueFilter(aggregateResults, timestampGenerator, readersOfSelectedSeries);
-    return constructDataSet(aggregateResults);
+    return constructDataSet(aggregateResults, queryPlan);
   }
 
   protected TimeGenerator getTimeGenerator(QueryContext context, RawDataQueryPlan queryPlan) throws StorageEngineException {
@@ -318,15 +319,34 @@ public class AggregationExecutor {
    *
    * @param aggregateResultList aggregate result list
    */
-  private QueryDataSet constructDataSet(List<AggregateResult> aggregateResultList) {
+  private QueryDataSet constructDataSet(List<AggregateResult> aggregateResultList, RawDataQueryPlan plan) {
     RowRecord record = new RowRecord(0);
     for (AggregateResult resultData : aggregateResultList) {
       TSDataType dataType = resultData.getResultDataType();
       record.addField(resultData.getResult(), dataType);
     }
 
-    SingleDataSet dataSet = new SingleDataSet(selectedSeries, dataTypes);
-    dataSet.setRecord(record);
+    SingleDataSet dataSet = null;
+    if (((AggregationPlan)plan).getLevel() >= 0) {
+      // current only support count operation
+      Map<Integer, String> pathIndex = new HashMap<>();
+      Map<String, Long> finalPaths = FilePathUtils.getPathByLevel(plan.getDeduplicatedPaths(), ((AggregationPlan)plan).getLevel(), pathIndex);
+
+      RowRecord curRecord = FilePathUtils.mergeRecordByPath(record, finalPaths, pathIndex);
+
+      List<Path> paths = new ArrayList<>();
+      List<TSDataType> dataTypes = new ArrayList<>();
+      for (int i = 0; i < finalPaths.size(); i++) {
+        dataTypes.add(TSDataType.INT64);
+      }
+
+      dataSet = new SingleDataSet(paths, dataTypes);
+      dataSet.setRecord(curRecord);
+    } else {
+      dataSet = new SingleDataSet(selectedSeries, dataTypes);
+      dataSet.setRecord(record);
+    }
+
     return dataSet;
   }
 
diff --git a/server/src/main/java/org/apache/iotdb/db/query/executor/IQueryRouter.java b/server/src/main/java/org/apache/iotdb/db/query/executor/IQueryRouter.java
index 9082900..d04a24f 100644
--- a/server/src/main/java/org/apache/iotdb/db/query/executor/IQueryRouter.java
+++ b/server/src/main/java/org/apache/iotdb/db/query/executor/IQueryRouter.java
@@ -58,7 +58,7 @@ public interface IQueryRouter {
   /**
    * Execute group by fill query
    */
-  QueryDataSet groupByFill(GroupByFillTimePlan groupByFillPlan, QueryContext context)
+  QueryDataSet groupByFill(GroupByTimeFillPlan groupByFillPlan, QueryContext context)
       throws QueryFilterOptimizationException, StorageEngineException,
       QueryProcessException, IOException;
 
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 4823cc6..4d2070f 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
@@ -23,6 +23,7 @@ import org.apache.iotdb.db.exception.StorageEngineException;
 import org.apache.iotdb.db.exception.query.QueryProcessException;
 import org.apache.iotdb.db.qp.physical.crud.*;
 import org.apache.iotdb.db.query.context.QueryContext;
+import org.apache.iotdb.db.query.dataset.SingleDataSet;
 import org.apache.iotdb.db.query.dataset.groupby.*;
 import org.apache.iotdb.db.query.executor.fill.IFill;
 import org.apache.iotdb.tsfile.exception.filter.QueryFilterOptimizationException;
@@ -88,6 +89,13 @@ public class QueryRouter implements IQueryRouter {
       throws QueryFilterOptimizationException, StorageEngineException, QueryProcessException,
       IOException {
 
+    if (logger.isDebugEnabled()) {
+      logger.debug("paths:" + aggregationPlan.getPaths()
+        + " level:" + aggregationPlan.getLevel()
+        + " duplicatePaths:" + aggregationPlan.getDeduplicatedPaths()
+        + " deduplicatePaths:" + aggregationPlan.getDeduplicatedAggregations());
+    }
+
     IExpression expression = aggregationPlan.getExpression();
     List<Path> deduplicatedPaths = aggregationPlan.getDeduplicatedPaths();
 
@@ -100,12 +108,16 @@ public class QueryRouter implements IQueryRouter {
 
     AggregationExecutor engineExecutor = getAggregationExecutor(aggregationPlan);
 
+    QueryDataSet dataSet = null;
+
     if (optimizedExpression != null
         && optimizedExpression.getType() != ExpressionType.GLOBAL_TIME) {
-      return engineExecutor.executeWithValueFilter(context, aggregationPlan);
+      dataSet =  engineExecutor.executeWithValueFilter(context, aggregationPlan);
+    } else {
+      dataSet = engineExecutor.executeWithoutValueFilter(context, aggregationPlan);
     }
 
-    return engineExecutor.executeWithoutValueFilter(context, aggregationPlan);
+    return dataSet;
   }
 
   protected AggregationExecutor getAggregationExecutor(AggregationPlan aggregationPlan) {
@@ -116,39 +128,42 @@ public class QueryRouter implements IQueryRouter {
   public QueryDataSet groupBy(GroupByTimePlan groupByTimePlan, QueryContext context)
     throws QueryFilterOptimizationException, StorageEngineException, QueryProcessException, IOException {
 
-    logger.debug("paths:" + groupByTimePlan.getPaths() + " level:" + groupByTimePlan.getLevel() + " byTime:" + groupByTimePlan.isByTime());
+    if (logger.isDebugEnabled()) {
+      logger.debug("paths:" + groupByTimePlan.getPaths() + " level:" + groupByTimePlan.getLevel());
+    }
 
     GroupByEngineDataSet dataSet = null;
-    if (groupByTimePlan.isByTime()) {
-      long unit = groupByTimePlan.getInterval();
-      long slidingStep = groupByTimePlan.getSlidingStep();
-      long startTime = groupByTimePlan.getStartTime();
-      long endTime = groupByTimePlan.getEndTime();
-
-      IExpression expression = groupByTimePlan.getExpression();
-      List<Path> selectedSeries = groupByTimePlan.getDeduplicatedPaths();
-
-      GlobalTimeExpression timeExpression = new GlobalTimeExpression(
-        new GroupByFilter(unit, slidingStep, startTime, endTime));
-
-      if (expression == null) {
-        expression = timeExpression;
-      } else {
-        expression = BinaryExpression.and(expression, timeExpression);
-      }
-
-      // optimize expression to an executable one
-      IExpression optimizedExpression = ExpressionOptimizer.getInstance()
-        .optimize(expression, selectedSeries);
-      groupByTimePlan.setExpression(optimizedExpression);
-
-      if (optimizedExpression.getType() == ExpressionType.GLOBAL_TIME) {
-        dataSet = getGroupByWithoutValueFilterDataSet(context, groupByTimePlan);
-      } else {
-        dataSet = getGroupByWithValueFilterDataSet(context, groupByTimePlan);
-      }
+    long unit = groupByTimePlan.getInterval();
+    long slidingStep = groupByTimePlan.getSlidingStep();
+    long startTime = groupByTimePlan.getStartTime();
+    long endTime = groupByTimePlan.getEndTime();
+
+    IExpression expression = groupByTimePlan.getExpression();
+    List<Path> selectedSeries = groupByTimePlan.getDeduplicatedPaths();
+
+    GlobalTimeExpression timeExpression = new GlobalTimeExpression(
+      new GroupByFilter(unit, slidingStep, startTime, endTime));
+
+    if (expression == null) {
+      expression = timeExpression;
+    } else {
+      expression = BinaryExpression.and(expression, timeExpression);
+    }
+
+    // optimize expression to an executable one
+    IExpression optimizedExpression = ExpressionOptimizer.getInstance()
+      .optimize(expression, selectedSeries);
+    groupByTimePlan.setExpression(optimizedExpression);
+
+    if (optimizedExpression.getType() == ExpressionType.GLOBAL_TIME) {
+      dataSet = getGroupByWithoutValueFilterDataSet(context, groupByTimePlan);
+    } else {
+      dataSet = getGroupByWithValueFilterDataSet(context, groupByTimePlan);
     }
 
+    // we support group by level for count operation
+    // details at https://issues.apache.org/jira/browse/IOTDB-622
+    // and UserGuide/Operation Manual/DML
     if (groupByTimePlan.getLevel() >= 0) {
       return groupByLevelWithoutTimeIntervalDataSet(context, groupByTimePlan, dataSet);
     }
@@ -165,10 +180,10 @@ public class QueryRouter implements IQueryRouter {
     return new GroupByWithValueFilterDataSet(context, plan);
   }
 
-  protected GroupByLevelDataSet groupByLevelWithoutTimeIntervalDataSet(QueryContext context, GroupByTimePlan plan,
-                                                                          GroupByEngineDataSet dataSet)
+  protected GroupByTimeDataSet groupByLevelWithoutTimeIntervalDataSet(QueryContext context, GroupByTimePlan plan,
+                                                                      GroupByEngineDataSet dataSet)
     throws StorageEngineException, QueryProcessException, IOException {
-      return new GroupByLevelDataSet(context, plan, dataSet);
+      return new GroupByTimeDataSet(context, plan, dataSet);
   }
 
   @Override
@@ -192,7 +207,7 @@ public class QueryRouter implements IQueryRouter {
   }
 
   @Override
-  public QueryDataSet groupByFill(GroupByFillTimePlan groupByFillPlan, QueryContext context)
+  public QueryDataSet groupByFill(GroupByTimeFillPlan groupByFillPlan, QueryContext context)
           throws QueryFilterOptimizationException, StorageEngineException, QueryProcessException, IOException {
     GroupByEngineDataSet groupByEngineDataSet = (GroupByEngineDataSet) groupBy(groupByFillPlan, context);
     return new GroupByFillDataSet(groupByFillPlan.getDeduplicatedPaths(), groupByFillPlan.getDeduplicatedDataTypes(),
diff --git a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
index 0acb9de..0d5db6b 100644
--- a/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/service/TSServiceImpl.java
@@ -19,7 +19,6 @@
 package org.apache.iotdb.db.service;
 
 import static org.apache.iotdb.db.conf.IoTDBConfig.PATH_PATTERN;
-import static org.apache.iotdb.db.qp.logical.Operator.OperatorType.GROUPBYTIME;
 import static org.apache.iotdb.db.qp.physical.sys.ShowPlan.ShowContentType.TIMESERIES;
 
 import java.io.IOException;
@@ -54,13 +53,8 @@ import org.apache.iotdb.db.qp.executor.IPlanExecutor;
 import org.apache.iotdb.db.qp.executor.PlanExecutor;
 import org.apache.iotdb.db.qp.logical.Operator.OperatorType;
 import org.apache.iotdb.db.qp.physical.PhysicalPlan;
-import org.apache.iotdb.db.qp.physical.crud.AlignByDevicePlan;
+import org.apache.iotdb.db.qp.physical.crud.*;
 import org.apache.iotdb.db.qp.physical.crud.AlignByDevicePlan.MeasurementType;
-import org.apache.iotdb.db.qp.physical.crud.DeletePlan;
-import org.apache.iotdb.db.qp.physical.crud.InsertPlan;
-import org.apache.iotdb.db.qp.physical.crud.InsertTabletPlan;
-import org.apache.iotdb.db.qp.physical.crud.LastQueryPlan;
-import org.apache.iotdb.db.qp.physical.crud.QueryPlan;
 import org.apache.iotdb.db.qp.physical.sys.AuthorPlan;
 import org.apache.iotdb.db.qp.physical.sys.CreateTimeSeriesPlan;
 import org.apache.iotdb.db.qp.physical.sys.DeleteStorageGroupPlan;
@@ -544,7 +538,7 @@ public class TSServiceImpl implements TSIService.Iface, ServerContext {
         if (plan.getOperatorType() == OperatorType.FILL) {
           throw new QueryProcessException("Fill doesn't support disable align clause.");
         }
-        if (plan.getOperatorType() == GROUPBYTIME) {
+        if (plan.getOperatorType() == OperatorType.GROUPBYTIME) {
           throw new QueryProcessException("Group by doesn't support disable align clause.");
         }
       }
@@ -693,8 +687,8 @@ public class TSServiceImpl implements TSIService.Iface, ServerContext {
       // Last Query should return different respond instead of the static one
       // because the query dataset and query id is different although the header of last query is same.
       return StaticResps.LAST_RESP.deepCopy();
-    } else if (plan.getOperatorType() == GROUPBYTIME && plan.getLevel() >= 0) {
-      Map<String, Long> finalPaths = FilePathUtils.getPathByLevel(plan.getPaths(), plan.getLevel(), null);
+    } else if (plan instanceof AggregationPlan && ((AggregationPlan)plan).getLevel() >= 0) {
+      Map<String, Long> finalPaths = FilePathUtils.getPathByLevel(((AggregationPlan)plan).getDeduplicatedPaths(), ((AggregationPlan)plan).getLevel(), null);
       for (Map.Entry<String, Long> entry : finalPaths.entrySet()) {
         respColumns.add("count(" + entry.getKey() + ")");
         columnsTypes.add(TSDataType.INT64.toString());
diff --git a/server/src/main/java/org/apache/iotdb/db/utils/FilePathUtils.java b/server/src/main/java/org/apache/iotdb/db/utils/FilePathUtils.java
index 3c54541..bb4ff27 100644
--- a/server/src/main/java/org/apache/iotdb/db/utils/FilePathUtils.java
+++ b/server/src/main/java/org/apache/iotdb/db/utils/FilePathUtils.java
@@ -24,7 +24,11 @@ import java.util.Map;
 import java.util.TreeMap;
 
 import org.apache.iotdb.db.engine.storagegroup.TsFileResource;
+import org.apache.iotdb.db.metadata.MetaUtils;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.read.common.Field;
 import org.apache.iotdb.tsfile.read.common.Path;
+import org.apache.iotdb.tsfile.read.common.RowRecord;
 
 public class FilePathUtils {
 
@@ -65,7 +69,7 @@ public class FilePathUtils {
 
     int i = 0;
     for (Path value : rawPaths) {
-      String[] tmpPath = value.getFullPath().split("\\.");
+      String[] tmpPath = MetaUtils.getNodeNames(value.getFullPath());
 
       String key;
       if (tmpPath.length <= level) {
@@ -90,4 +94,43 @@ public class FilePathUtils {
     return finalPaths;
   }
 
+  /**
+   * merge the raw record by level, for example
+   * raw record [timestamp, root.sg1.d1.s0, root.sg1.d1.s1, root.sg1.d2.s2], level=1
+   * and newRecord data is [100, 1, 1, 1]
+   * return [100, 3]
+   *
+   * @param newRecord
+   * @param finalPaths
+   * @param pathIndex
+   * @return
+   */
+  public static RowRecord mergeRecordByPath(RowRecord newRecord,
+                                      Map<String, Long> finalPaths,
+                                      Map<Integer, String> pathIndex) {
+    if (newRecord.getFields().size() < finalPaths.size()) {
+      return null;
+    }
+
+    // reset final paths
+    for (Map.Entry<String, Long> entry : finalPaths.entrySet()) {
+      entry.setValue(0L);
+    }
+
+    RowRecord tmpRecord = new RowRecord(newRecord.getTimestamp());
+
+    for (int i = 0; i < newRecord.getFields().size(); i++) {
+      if (newRecord.getFields().get(i) != null) {
+        finalPaths.put(pathIndex.get(i),
+          finalPaths.get(pathIndex.get(i)) + newRecord.getFields().get(i).getLongV());
+      }
+    }
+
+    for (Map.Entry<String, Long> entry : finalPaths.entrySet()) {
+      tmpRecord.addField(Field.getField(entry.getValue(), TSDataType.INT64));
+    }
+
+    return tmpRecord;
+  }
+
 }
diff --git a/server/src/test/java/org/apache/iotdb/db/qp/plan/PhysicalPlanTest.java b/server/src/test/java/org/apache/iotdb/db/qp/plan/PhysicalPlanTest.java
index c424292..acd55a1 100644
--- a/server/src/test/java/org/apache/iotdb/db/qp/plan/PhysicalPlanTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/qp/plan/PhysicalPlanTest.java
@@ -239,10 +239,10 @@ public class PhysicalPlanTest {
       if (!plan.isQuery()) {
         fail();
       }
-      if (!(plan instanceof GroupByFillTimePlan)) {
+      if (!(plan instanceof GroupByTimeFillPlan)) {
         fail();
       }
-      GroupByFillTimePlan groupByFillPlan = (GroupByFillTimePlan) plan;
+      GroupByTimeFillPlan groupByFillPlan = (GroupByTimeFillPlan) plan;
       assertEquals(3L, groupByFillPlan.getInterval());
       assertEquals(3L, groupByFillPlan.getSlidingStep());
       assertEquals(8L, groupByFillPlan.getStartTime());
@@ -268,10 +268,10 @@ public class PhysicalPlanTest {
       if (!plan.isQuery()) {
         fail();
       }
-      if (!(plan instanceof GroupByFillTimePlan)) {
+      if (!(plan instanceof GroupByTimeFillPlan)) {
         fail();
       }
-      GroupByFillTimePlan groupByFillPlan = (GroupByFillTimePlan) plan;
+      GroupByTimeFillPlan groupByFillPlan = (GroupByTimeFillPlan) plan;
       assertEquals(3L, groupByFillPlan.getInterval());
       assertEquals(3L, groupByFillPlan.getSlidingStep());
       assertEquals(8L, groupByFillPlan.getStartTime());
@@ -299,10 +299,10 @@ public class PhysicalPlanTest {
       if (!plan.isQuery()) {
         fail();
       }
-      if (!(plan instanceof GroupByFillTimePlan)) {
+      if (!(plan instanceof GroupByTimeFillPlan)) {
         fail();
       }
-      GroupByFillTimePlan groupByFillPlan = (GroupByFillTimePlan) plan;
+      GroupByTimeFillPlan groupByFillPlan = (GroupByTimeFillPlan) plan;
       assertEquals(3L, groupByFillPlan.getInterval());
       assertEquals(3L, groupByFillPlan.getSlidingStep());
       assertEquals(8L, groupByFillPlan.getStartTime());
@@ -383,7 +383,7 @@ public class PhysicalPlanTest {
       if (!plan.isQuery()) {
         fail();
       }
-      if (!(plan instanceof GroupByFillPlan)) {
+      if (!(plan instanceof GroupByTimeFillPlan)) {
         fail();
       }
     } catch (Exception e) {
diff --git a/server/src/test/java/org/apache/iotdb/db/query/dataset/GroupByLevelDataSetTest.java b/server/src/test/java/org/apache/iotdb/db/query/dataset/GroupByLevelDataSetTest.java
index e473a5a..3e8cae0 100644
--- a/server/src/test/java/org/apache/iotdb/db/query/dataset/GroupByLevelDataSetTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/query/dataset/GroupByLevelDataSetTest.java
@@ -31,6 +31,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
 
 public class GroupByLevelDataSetTest {
   private IPlanExecutor queryExecutor = new PlanExecutor();
@@ -42,6 +43,8 @@ public class GroupByLevelDataSetTest {
     "CREATE TIMESERIES root.vehicle.d0.s1 WITH DATATYPE=TEXT, ENCODING=PLAIN",
     "CREATE TIMESERIES root.test.d0.s0 WITH DATATYPE=INT32, ENCODING=RLE",
     "CREATE TIMESERIES root.test.d0.s1 WITH DATATYPE=TEXT, ENCODING=PLAIN",
+    "CREATE TIMESERIES root.test.d0.s3 WITH DATATYPE=TEXT, ENCODING=PLAIN",
+    "CREATE TIMESERIES root.test.d1.\"s3.xy\" WITH DATATYPE=TEXT, ENCODING=PLAIN",
     "insert into root.vehicle.d0(timestamp,s0) values(10,100)",
     "insert into root.vehicle.d0(timestamp,s0,s1) values(12,101,'102')",
     "insert into root.vehicle.d0(timestamp,s1) values(19,'103')",
@@ -74,7 +77,9 @@ public class GroupByLevelDataSetTest {
     "insert into root.test.d0(timestamp,s1) values(30,'139')",
     "insert into root.test.d0(timestamp,s0) values(1900,1316)",
     "insert into root.test.d0(timestamp,s0,s1) values(700,1307,'1038')",
-    "insert into root.test.d0(timestamp,s1) values(3000,'1309')"};
+    "insert into root.test.d0(timestamp,s1) values(3000,'1309')",
+    "insert into root.test.d0(timestamp,s3) values(10,'100')",
+    "insert into root.test.d1(timestamp, \"s3.xy\") values(10, 'text')"};
 
   static {
     MManager.getInstance().init();
@@ -105,71 +110,47 @@ public class GroupByLevelDataSetTest {
     assertTrue(dataSet.hasNext());
     assertEquals("0\t12", dataSet.next().toString());
 
-    // level 0
     queryPlan = (QueryPlan) processor
-      .parseSQLToPhysicalPlan("select count(s1) from root.*.* group by level=0");
+      .parseSQLToPhysicalPlan("select count(s1) from root.test.* group by level=0");
     dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
 
     assertTrue(dataSet.hasNext());
-    assertEquals("0\t22", dataSet.next().toString());
+    assertEquals("0\t12", dataSet.next().toString());
 
-    // level large than series path
     queryPlan = (QueryPlan) processor
-      .parseSQLToPhysicalPlan("select count(s1) from root.test.* group by level=5");
+      .parseSQLToPhysicalPlan("select count(s1) from root.test.* group by level=6");
     dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
 
     assertTrue(dataSet.hasNext());
     assertEquals("0\t12", dataSet.next().toString());
 
-    // with time interval
+    // multi paths
     queryPlan = (QueryPlan) processor
-      .parseSQLToPhysicalPlan("select count(s1) from root.test.* group by ([0,20), 1ms), level=1");
+      .parseSQLToPhysicalPlan("select count(s1) from root.test.*,root.vehicle.* group by level=1");
     dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
 
     assertTrue(dataSet.hasNext());
-    assertEquals("0\t0", dataSet.next().toString());
-    assertTrue(dataSet.hasNext());
-    assertEquals("1\t0", dataSet.next().toString());
-    assertTrue(dataSet.hasNext());
-    assertEquals("2\t1", dataSet.next().toString());
+    assertEquals("0\t12\t10", dataSet.next().toString());
 
+    // with multi result
     queryPlan = (QueryPlan) processor
-      .parseSQLToPhysicalPlan("select count(s1) from root.test.* group by ([0,20), 1ms), level=0");
+      .parseSQLToPhysicalPlan("select count(s1), count(s0) from root.test.* group by level=6");
     dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
 
     assertTrue(dataSet.hasNext());
-    assertEquals("0\t0", dataSet.next().toString());
-    assertTrue(dataSet.hasNext());
-    assertEquals("1\t0", dataSet.next().toString());
-    assertTrue(dataSet.hasNext());
-    assertEquals("2\t1", dataSet.next().toString());
+    assertEquals("0\t12\t12", dataSet.next().toString());
 
+    // with multi result
     queryPlan = (QueryPlan) processor
-      .parseSQLToPhysicalPlan("select count(s1) from root.test.* group by ([0,20), 1ms), level=6");
+      .parseSQLToPhysicalPlan("select count(s1), count(s3) from root.test.* group by level=2");
     dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
 
     assertTrue(dataSet.hasNext());
-    assertEquals("0\t0", dataSet.next().toString());
-    assertTrue(dataSet.hasNext());
-    assertEquals("1\t0", dataSet.next().toString());
-    assertTrue(dataSet.hasNext());
-    assertEquals("2\t1", dataSet.next().toString());
+    assertEquals("0\t13", dataSet.next().toString());
 
-    // multi paths
+    // with double quotation mark
     queryPlan = (QueryPlan) processor
-      .parseSQLToPhysicalPlan("select count(s1) from root.test.*,root.vehicle.* group by ([0,20), 1ms), level=1");
-    dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
-
-    assertTrue(dataSet.hasNext());
-    assertEquals("0\t0\t0", dataSet.next().toString());
-    assertTrue(dataSet.hasNext());
-    assertEquals("1\t0\t0", dataSet.next().toString());
-    assertTrue(dataSet.hasNext());
-    assertEquals("2\t1\t0", dataSet.next().toString());
-
-    // with sliding step
-    queryPlan = (QueryPlan) processor
-      .parseSQLToPhysicalPlan("select count(s1) from root.test.* group by ([0,20), 3ms, 10ms), level=6");
+      .parseSQLToPhysicalPlan("select count(\"s3.xy\") from root.test.* group by level=2");
     dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
 
     assertTrue(dataSet.hasNext());
@@ -178,12 +159,11 @@ public class GroupByLevelDataSetTest {
     // not count
     try {
       queryPlan = (QueryPlan) processor
-        .parseSQLToPhysicalPlan("select sum(s0) from root.test.* group by ([0,200), 1ms), level=6");
+        .parseSQLToPhysicalPlan("select sum(s0) from root.test.* group by level=6");
       dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
       fail();
     } catch (Exception e) {
-      assertEquals("group by level only support count", e.getMessage());
+      assertEquals("group by level only support count now.", e.getMessage());
     }
   }
-
 }
diff --git a/server/src/test/java/org/apache/iotdb/db/query/dataset/GroupByLevelDataSetTest.java b/server/src/test/java/org/apache/iotdb/db/query/dataset/GroupByTimeDataSetTest.java
similarity index 89%
copy from server/src/test/java/org/apache/iotdb/db/query/dataset/GroupByLevelDataSetTest.java
copy to server/src/test/java/org/apache/iotdb/db/query/dataset/GroupByTimeDataSetTest.java
index e473a5a..09b31fd 100644
--- a/server/src/test/java/org/apache/iotdb/db/query/dataset/GroupByLevelDataSetTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/query/dataset/GroupByTimeDataSetTest.java
@@ -32,7 +32,7 @@ import org.junit.Test;
 
 import static org.junit.Assert.*;
 
-public class GroupByLevelDataSetTest {
+public class GroupByTimeDataSetTest {
   private IPlanExecutor queryExecutor = new PlanExecutor();
   private Planner processor = new Planner();
   private String[] sqls = {
@@ -42,6 +42,7 @@ public class GroupByLevelDataSetTest {
     "CREATE TIMESERIES root.vehicle.d0.s1 WITH DATATYPE=TEXT, ENCODING=PLAIN",
     "CREATE TIMESERIES root.test.d0.s0 WITH DATATYPE=INT32, ENCODING=RLE",
     "CREATE TIMESERIES root.test.d0.s1 WITH DATATYPE=TEXT, ENCODING=PLAIN",
+    "CREATE TIMESERIES root.test.d1.\"s3.xy\" WITH DATATYPE=TEXT, ENCODING=PLAIN",
     "insert into root.vehicle.d0(timestamp,s0) values(10,100)",
     "insert into root.vehicle.d0(timestamp,s0,s1) values(12,101,'102')",
     "insert into root.vehicle.d0(timestamp,s1) values(19,'103')",
@@ -74,13 +75,14 @@ public class GroupByLevelDataSetTest {
     "insert into root.test.d0(timestamp,s1) values(30,'139')",
     "insert into root.test.d0(timestamp,s0) values(1900,1316)",
     "insert into root.test.d0(timestamp,s0,s1) values(700,1307,'1038')",
-    "insert into root.test.d0(timestamp,s1) values(3000,'1309')"};
+    "insert into root.test.d0(timestamp,s1) values(3000,'1309')",
+    "insert into root.test.d1(timestamp, \"s3.xy\") values(10, 'text')"};
 
   static {
     MManager.getInstance().init();
   }
 
-  public GroupByLevelDataSetTest() throws QueryProcessException {
+  public GroupByTimeDataSetTest() throws QueryProcessException {
   }
 
   @Before
@@ -97,34 +99,11 @@ public class GroupByLevelDataSetTest {
   }
 
   @Test
-  public void testGroupByLevel() throws Exception {
-    QueryPlan queryPlan = (QueryPlan) processor
-      .parseSQLToPhysicalPlan("select count(s1) from root.test.* group by level=1");
-    QueryDataSet dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
-
-    assertTrue(dataSet.hasNext());
-    assertEquals("0\t12", dataSet.next().toString());
-
-    // level 0
-    queryPlan = (QueryPlan) processor
-      .parseSQLToPhysicalPlan("select count(s1) from root.*.* group by level=0");
-    dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
-
-    assertTrue(dataSet.hasNext());
-    assertEquals("0\t22", dataSet.next().toString());
-
-    // level large than series path
-    queryPlan = (QueryPlan) processor
-      .parseSQLToPhysicalPlan("select count(s1) from root.test.* group by level=5");
-    dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
-
-    assertTrue(dataSet.hasNext());
-    assertEquals("0\t12", dataSet.next().toString());
-
+  public void testGroupByTimeAndLevel() throws Exception {
     // with time interval
-    queryPlan = (QueryPlan) processor
+    QueryPlan queryPlan = (QueryPlan) processor
       .parseSQLToPhysicalPlan("select count(s1) from root.test.* group by ([0,20), 1ms), level=1");
-    dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
+    QueryDataSet dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
 
     assertTrue(dataSet.hasNext());
     assertEquals("0\t0", dataSet.next().toString());
@@ -175,6 +154,24 @@ public class GroupByLevelDataSetTest {
     assertTrue(dataSet.hasNext());
     assertEquals("0\t1", dataSet.next().toString());
 
+    // with multi result
+    queryPlan = (QueryPlan) processor
+      .parseSQLToPhysicalPlan("select count(s1), count(s1) from root.test.* group by ([0,20), 3ms, 10ms), level=6");
+    dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
+
+    assertTrue(dataSet.hasNext());
+    assertEquals("0\t1", dataSet.next().toString());
+
+    // with double quotation mark
+    queryPlan = (QueryPlan) processor
+      .parseSQLToPhysicalPlan("select count(\"s3.xy\") from root.test.* group by ([0,20), 3ms, 10ms), level=2");
+    dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
+
+    assertTrue(dataSet.hasNext());
+    assertEquals("0\t0", dataSet.next().toString());
+    assertTrue(dataSet.hasNext());
+    assertEquals("10\t1", dataSet.next().toString());
+
     // not count
     try {
       queryPlan = (QueryPlan) processor
@@ -182,7 +179,7 @@ public class GroupByLevelDataSetTest {
       dataSet = queryExecutor.processQuery(queryPlan, EnvironmentUtils.TEST_QUERY_CONTEXT);
       fail();
     } catch (Exception e) {
-      assertEquals("group by level only support count", e.getMessage());
+      assertEquals("group by level only support count now.", e.getMessage());
     }
   }