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 2022/01/10 07:29:47 UTC

[iotdb] branch master updated: [IOTDB-2295][IOTDB-2299] Fix Timeseries count group by level Bug & Fix Wrong SQL instance (#4727)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 8a51c42  [IOTDB-2295][IOTDB-2299] Fix Timeseries count group by level Bug & Fix Wrong SQL instance (#4727)
8a51c42 is described below

commit 8a51c4245f24709d8629344d2b2cb051d2593759
Author: Marcos_Zyk <38...@users.noreply.github.com>
AuthorDate: Mon Jan 10 15:29:15 2022 +0800

    [IOTDB-2295][IOTDB-2299] Fix Timeseries count group by level Bug & Fix Wrong SQL instance (#4727)
---
 .../DML-Data-Manipulation-Language.md              |  10 +-
 .../DML-Data-Manipulation-Language.md              |   2 +-
 .../iotdb/db/integration/IoTDBMetadataFetchIT.java | 128 ++++++++++-----------
 .../org/apache/iotdb/db/metadata/MManager.java     |   5 +
 .../org/apache/iotdb/db/metadata/mtree/MTree.java  |  10 ++
 .../counter/MeasurementGroupByLevelCounter.java    |  74 ++++++++++++
 .../apache/iotdb/db/qp/executor/PlanExecutor.java  |  13 ++-
 .../org/apache/iotdb/db/metadata/MTreeTest.java    |  73 ++++++++++++
 8 files changed, 238 insertions(+), 77 deletions(-)

diff --git a/docs/UserGuide/IoTDB-SQL-Language/DML-Data-Manipulation-Language.md b/docs/UserGuide/IoTDB-SQL-Language/DML-Data-Manipulation-Language.md
index 811e745..066c1f1 100644
--- a/docs/UserGuide/IoTDB-SQL-Language/DML-Data-Manipulation-Language.md
+++ b/docs/UserGuide/IoTDB-SQL-Language/DML-Data-Manipulation-Language.md
@@ -58,7 +58,7 @@ IoTDB > insert into root.ln.wf02.wt02(timestamp, status, hardware) VALUES (3, fa
 After inserting the data, we can simply query the inserted data using the SELECT statement:
 
 ```sql
-IoTDB > select * from root.ln.wf02 where time < 5
+IoTDB > select * from root.ln.wf02.wt02 where time < 5
 ```
 
 The result is shown below. The query result shows that the insertion statements of single column and multi column data are performed correctly.
@@ -1990,13 +1990,13 @@ Msg: 411: Meet error in query process: The value of SOFFSET (2) is equal to or e
 * IoTDB will join all the sensor value by its time, and if some sensors don't have values in that timestamp, we will fill it with null. In some analysis scenarios, we only need the row if all the columns of it have value.
 
 ```sql
-select * from root.ln.* where time <= 2017-11-01T00:01:00 WITHOUT NULL ANY
+select * from root.ln.** where time <= 2017-11-01T00:01:00 WITHOUT NULL ANY
 ```
 
 * In group by query, we will fill null for any group by interval if the columns don't have values in that group by interval. However, if all columns in that group by interval are null, maybe users don't need that RowRecord, so we can use `WITHOUT NULL ALL` to filter that row.
 
 ```sql
-select * from root.ln.* where time <= 2017-11-01T00:01:00 WITHOUT NULL ALL
+select * from root.ln.** where time <= 2017-11-01T00:01:00 WITHOUT NULL ALL
 ```
 
 ### Other ResultSet Formats
@@ -2010,7 +2010,7 @@ The 'align by device' indicates that the deviceId is considered as a column. The
 The SQL statement is:
 
 ```sql
-select * from root.ln.* where time <= 2017-11-01T00:01:00 align by device
+select * from root.ln.** where time <= 2017-11-01T00:01:00 align by device
 ```
 
 The result shows below:
@@ -2039,7 +2039,7 @@ The 'disable align' indicates that there are 2 columns for each time series in t
 The SQL statement is:
 
 ```sql
-select * from root.ln.* where time <= 2017-11-01T00:01:00 disable align
+select * from root.ln.** where time <= 2017-11-01T00:01:00 disable align
 ```
 
 The result shows below:
diff --git a/docs/zh/UserGuide/IoTDB-SQL-Language/DML-Data-Manipulation-Language.md b/docs/zh/UserGuide/IoTDB-SQL-Language/DML-Data-Manipulation-Language.md
index 06ce032..25653b0 100644
--- a/docs/zh/UserGuide/IoTDB-SQL-Language/DML-Data-Manipulation-Language.md
+++ b/docs/zh/UserGuide/IoTDB-SQL-Language/DML-Data-Manipulation-Language.md
@@ -59,7 +59,7 @@ IoTDB > insert into root.ln.wf02.wt02(timestamp, status, hardware) VALUES (3, fa
 插入数据后我们可以使用 SELECT 语句简单查询已插入的数据。
 
 ```sql
-IoTDB > select * from root.ln.wf02 where time < 5
+IoTDB > select * from root.ln.wf02.wt02 where time < 5
 ```
 
 结果如图所示。由查询结果可以看出,单列、多列数据的插入操作正确执行。
diff --git a/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBMetadataFetchIT.java b/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBMetadataFetchIT.java
index 43215af..039458c 100644
--- a/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBMetadataFetchIT.java
+++ b/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBMetadataFetchIT.java
@@ -46,6 +46,7 @@ import java.util.HashSet;
 import java.util.Set;
 
 import static org.apache.iotdb.db.metadata.MManager.TIME_SERIES_TREE_HEADER;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 /**
@@ -68,6 +69,12 @@ public class IoTDBMetadataFetchIT {
             "SET STORAGE GROUP TO root.ln2.wf01.wt01",
             "CREATE TIMESERIES root.ln.wf01.wt01.status WITH DATATYPE = BOOLEAN, ENCODING = PLAIN",
             "CREATE TIMESERIES root.ln.wf01.wt01.temperature WITH DATATYPE = FLOAT, ENCODING = RLE, "
+                + "compressor = SNAPPY, MAX_POINT_NUMBER = 3",
+            "CREATE TIMESERIES root.ln1.wf01.wt01.status WITH DATATYPE = BOOLEAN, ENCODING = PLAIN",
+            "CREATE TIMESERIES root.ln1.wf01.wt01.temperature WITH DATATYPE = FLOAT, ENCODING = RLE, "
+                + "compressor = SNAPPY, MAX_POINT_NUMBER = 3",
+            "CREATE TIMESERIES root.ln2.wf01.wt01.status WITH DATATYPE = BOOLEAN, ENCODING = PLAIN",
+            "CREATE TIMESERIES root.ln2.wf01.wt01.temperature WITH DATATYPE = FLOAT, ENCODING = RLE, "
                 + "compressor = SNAPPY, MAX_POINT_NUMBER = 3"
           };
 
@@ -101,15 +108,15 @@ public class IoTDBMetadataFetchIT {
       String[] sqls =
           new String[] {
             "show timeseries root.ln.wf01.wt01.status", // full seriesPath
-            "show timeseries root.ln", // prefix seriesPath
-            "show timeseries root.ln.*.wt01", // seriesPath with stars
+            "show timeseries root.ln.**", // prefix seriesPath
+            "show timeseries root.ln.*.wt01.*", // seriesPath with stars
             "show timeseries", // the same as root
             "show timeseries root.a.b", // nonexistent timeseries, thus returning ""
           };
       Set<String>[] standards =
           new Set[] {
             new HashSet<>(
-                Arrays.asList(
+                Collections.singletonList(
                     "root.ln.wf01.wt01.status,null,root.ln.wf01.wt01,BOOLEAN,PLAIN,SNAPPY,null,null,")),
             new HashSet<>(
                 Arrays.asList(
@@ -122,8 +129,12 @@ public class IoTDBMetadataFetchIT {
             new HashSet<>(
                 Arrays.asList(
                     "root.ln.wf01.wt01.status,null,root.ln.wf01.wt01,BOOLEAN,PLAIN,SNAPPY,null,null,",
-                    "root.ln.wf01.wt01.temperature,null,root.ln.wf01.wt01,FLOAT,RLE,SNAPPY,null,null,")),
-            new HashSet<>(Collections.singletonList(""))
+                    "root.ln.wf01.wt01.temperature,null,root.ln.wf01.wt01,FLOAT,RLE,SNAPPY,null,null,",
+                    "root.ln1.wf01.wt01.status,null,root.ln1.wf01.wt01,BOOLEAN,PLAIN,SNAPPY,null,null,",
+                    "root.ln1.wf01.wt01.temperature,null,root.ln1.wf01.wt01,FLOAT,RLE,SNAPPY,null,null,",
+                    "root.ln2.wf01.wt01.status,null,root.ln2.wf01.wt01,BOOLEAN,PLAIN,SNAPPY,null,null,",
+                    "root.ln2.wf01.wt01.temperature,null,root.ln2.wf01.wt01,FLOAT,RLE,SNAPPY,null,null,")),
+            new HashSet<>()
           };
       for (int n = 0; n < sqls.length; n++) {
         String sql = sqls[n];
@@ -138,8 +149,11 @@ public class IoTDBMetadataFetchIT {
                 for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
                   builder.append(resultSet.getString(i)).append(",");
                 }
-                Assert.assertTrue(standard.contains(builder.toString()));
+                String string = builder.toString();
+                Assert.assertTrue(standard.contains(string));
+                standard.remove(string);
               }
+              assertEquals(0, standard.size());
             }
           }
         } catch (SQLException e) {
@@ -158,7 +172,7 @@ public class IoTDBMetadataFetchIT {
       String[] sqls =
           new String[] {
             "show storage group",
-            "show storage group root.ln.wf01",
+            "show storage group root.ln.wf01.**",
             "show storage group root.ln.wf01.wt01.status"
           };
       Set<String>[] standards =
@@ -166,7 +180,7 @@ public class IoTDBMetadataFetchIT {
             new HashSet<>(
                 Arrays.asList("root.ln.wf01.wt01,", "root.ln1.wf01.wt01,", "root.ln2.wf01.wt01,")),
             new HashSet<>(Collections.singletonList("root.ln.wf01.wt01,")),
-            new HashSet<>(Collections.singletonList(""))
+            new HashSet<>()
           };
 
       for (int n = 0; n < sqls.length; n++) {
@@ -183,7 +197,11 @@ public class IoTDBMetadataFetchIT {
                   builder.append(resultSet.getString(i)).append(",");
                 }
                 Assert.assertTrue(standard.contains(builder.toString()));
+                String string = builder.toString();
+                Assert.assertTrue(standard.contains(string));
+                standard.remove(string);
               }
+              assertEquals(0, standard.size());
             }
           }
         } catch (SQLException e) {
@@ -197,19 +215,13 @@ public class IoTDBMetadataFetchIT {
   @Test
   @Category({LocalStandaloneTest.class})
   public void databaseMetaDataTest() throws SQLException {
-    Connection connection = null;
-    try {
-      connection = EnvFactory.getEnv().getConnection();
+    try (Connection connection = EnvFactory.getEnv().getConnection()) {
       databaseMetaData = connection.getMetaData();
       showTimeseriesInJson();
 
     } catch (Exception e) {
       logger.error("databaseMetaDataTest() failed", e);
       fail(e.getMessage());
-    } finally {
-      if (connection != null) {
-        connection.close();
-      }
     }
   }
 
@@ -240,15 +252,13 @@ public class IoTDBMetadataFetchIT {
         Statement statement = connection.createStatement()) {
       String[] sqls =
           new String[] {
-            "show devices root.ln with storage group", "show devices root.ln.wf01.wt01.temperature"
+            "show devices root.ln.** with storage group",
+            "show devices root.ln.wf01.wt01.temperature"
           };
       Set<String>[] standards =
           new Set[] {
-            new HashSet<>(
-                Arrays.asList(
-                    "root.ln.wf01.wt01,root.ln.wf01.wt01,",
-                    "root.ln.wf01.wt01.status,root.ln.wf01.wt01,")),
-            new HashSet<>(Collections.singletonList(""))
+            new HashSet<>(Collections.singletonList("root.ln.wf01.wt01,root.ln.wf01.wt01,")),
+            new HashSet<>()
           };
 
       for (int n = 0; n < sqls.length; n++) {
@@ -264,8 +274,11 @@ public class IoTDBMetadataFetchIT {
                 for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
                   builder.append(resultSet.getString(i)).append(",");
                 }
-                Assert.assertTrue(standard.contains(builder.toString()));
+                String string = builder.toString();
+                Assert.assertTrue(standard.contains(string));
+                standard.remove(string);
               }
+              assertEquals(0, standard.size());
             }
           }
         } catch (SQLException e) {
@@ -282,11 +295,10 @@ public class IoTDBMetadataFetchIT {
     try (Connection connection = EnvFactory.getEnv().getConnection();
         Statement statement = connection.createStatement()) {
       String[] sqls =
-          new String[] {"show devices root.ln", "show devices root.ln.wf01.wt01.temperature"};
+          new String[] {"show devices root.ln.**", "show devices root.ln.wf01.wt01.temperature"};
       Set<String>[] standards =
           new Set[] {
-            new HashSet<>(Arrays.asList("root.ln.wf01.wt01,", "root.ln.wf01.wt01.status,")),
-            new HashSet<>(Collections.singletonList(""))
+            new HashSet<>(Collections.singletonList("root.ln.wf01.wt01,")), new HashSet<>()
           };
 
       for (int n = 0; n < sqls.length; n++) {
@@ -302,8 +314,11 @@ public class IoTDBMetadataFetchIT {
                 for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
                   builder.append(resultSet.getString(i)).append(",");
                 }
-                Assert.assertTrue(standard.contains(builder.toString()));
+                String string = builder.toString();
+                Assert.assertTrue(standard.contains(string));
+                standard.remove(string);
               }
+              assertEquals(0, standard.size());
             }
           }
         } catch (SQLException e) {
@@ -386,7 +401,7 @@ public class IoTDBMetadataFetchIT {
     try (Connection connection = EnvFactory.getEnv().getConnection();
         Statement statement = connection.createStatement()) {
       String[] sqls = new String[] {"COUNT TIMESERIES root.ln.**", "COUNT TIMESERIES"};
-      String[] standards = new String[] {"2,\n", "2,\n"};
+      String[] standards = new String[] {"2,\n", "6,\n"};
       for (int n = 0; n < sqls.length; n++) {
         String sql = sqls[n];
         String standard = standards[n];
@@ -424,7 +439,7 @@ public class IoTDBMetadataFetchIT {
             "COUNT DEVICES",
             "COUNT DEVICES root.ln.wf01.wt01.temperature"
           };
-      String[] standards = new String[] {"1,\n", "1,\n", "0,\n"};
+      String[] standards = new String[] {"1,\n", "3,\n", "0,\n"};
       for (int n = 0; n < sqls.length; n++) {
         String sql = sqls[n];
         String standard = standards[n];
@@ -494,9 +509,20 @@ public class IoTDBMetadataFetchIT {
   public void showCountTimeSeriesGroupBy() throws SQLException {
     try (Connection connection = EnvFactory.getEnv().getConnection();
         Statement statement = connection.createStatement()) {
-      String[] sqls = new String[] {"COUNT TIMESERIES root group by level=1"};
+      String[] sqls =
+          new String[] {
+            "COUNT TIMESERIES root.** group by level=1",
+            "COUNT TIMESERIES root.** group by level=3",
+            "COUNT TIMESERIES root.**.status group by level=2"
+          };
       Set<String>[] standards =
-          new Set[] {new HashSet<>(Arrays.asList("root.ln,2,", "root.ln1,0,", "root.ln2,0,"))};
+          new Set[] {
+            new HashSet<>(Arrays.asList("root.ln,2,", "root.ln1,2,", "root.ln2,2,")),
+            new HashSet<>(
+                Arrays.asList(
+                    "root.ln.wf01.wt01,2,", "root.ln1.wf01.wt01,2,", "root.ln2.wf01.wt01,2,")),
+            new HashSet<>(Arrays.asList("root.ln.wf01,1,", "root.ln1.wf01,1,", "root.ln2.wf01,1,")),
+          };
       for (int n = 0; n < sqls.length; n++) {
         String sql = sqls[n];
         Set<String> standard = standards[n];
@@ -505,11 +531,11 @@ public class IoTDBMetadataFetchIT {
           if (hasResultSet) {
             try (ResultSet resultSet = statement.getResultSet()) {
               while (resultSet.next()) {
-                StringBuilder builder = new StringBuilder();
-                builder.append(resultSet.getString(1)).append(",");
-                builder.append(resultSet.getInt(2)).append(",");
-                Assert.assertTrue(standard.contains(builder.toString()));
+                String string = resultSet.getString(1) + "," + resultSet.getInt(2) + ",";
+                Assert.assertTrue(standard.contains(string));
+                standard.remove(string);
               }
+              assertEquals(0, standard.size());
             }
           }
         } catch (SQLException e) {
@@ -567,39 +593,7 @@ public class IoTDBMetadataFetchIT {
     String standard =
         "===  Timeseries Tree  ===\n"
             + "\n"
-            + "{\n"
-            + "\t\"root\":{\n"
-            + "\t\t\"ln2\":{\n"
-            + "\t\t\t\"wf01\":{\n"
-            + "\t\t\t\t\"wt01\":{}\n"
-            + "\t\t\t}\n"
-            + "\t\t},\n"
-            + "\t\t\"ln\":{\n"
-            + "\t\t\t\"wf01\":{\n"
-            + "\t\t\t\t\"wt01\":{\n"
-            + "\t\t\t\t\t\"temperature\":{\n"
-            + "\t\t\t\t\t\t\"args\":\"{max_point_number=3}\",\n"
-            + "\t\t\t\t\t\t\"StorageGroup\":\"root.ln.wf01.wt01\",\n"
-            + "\t\t\t\t\t\t\"DataType\":\"FLOAT\",\n"
-            + "\t\t\t\t\t\t\"Compressor\":\"SNAPPY\",\n"
-            + "\t\t\t\t\t\t\"Encoding\":\"RLE\"\n"
-            + "\t\t\t\t\t},\n"
-            + "\t\t\t\t\t\"status\":{\n"
-            + "\t\t\t\t\t\t\t\"StorageGroup\":\"root.ln.wf01.wt01\",\n"
-            + "\t\t\t\t\t\t\t\"DataType\":\"BOOLEAN\",\n"
-            + "\t\t\t\t\t\t\t\"Compressor\":\"SNAPPY\",\n"
-            + "\t\t\t\t\t\t\t\"Encoding\":\"PLAIN\"\n"
-            + "\t\t\t\t\t}\n"
-            + "\t\t\t\t}\n"
-            + "\t\t\t}\n"
-            + "\t\t},\n"
-            + "\t\t\"ln1\":{\n"
-            + "\t\t\t\"wf01\":{\n"
-            + "\t\t\t\t\"wt01\":{}\n"
-            + "\t\t\t}\n"
-            + "\t\t}\n"
-            + "\t}\n"
-            + "}";
+            + "{\"root\":{\"ln2\":{\"wf01\":{\"wt01\":{\"temperature\":{\"DataType\":\"FLOAT\",\"Encoding\":\"RLE\",\"Compressor\":\"SNAPPY\",\"args\":\"{max_point_number=3}\",\"StorageGroup\":\"root.ln2.wf01.wt01\"},\"status\":{\"DataType\":\"BOOLEAN\",\"Encoding\":\"PLAIN\",\"Compressor\":\"SNAPPY\",\"StorageGroup\":\"root.ln2.wf01.wt01\"}}}},\"ln\":{\"wf01\":{\"wt01\":{\"temperature\":{\"DataType\":\"FLOAT\",\"Encoding\":\"RLE\",\"Compressor\":\"SNAPPY\",\"args\":\"{max_point_number=3 [...]
 
     // TODO Remove the constant json String.
     // Do not depends on the sequence of property in json string if you do not
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java b/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
index a256198..071cafc 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/MManager.java
@@ -976,6 +976,11 @@ public class MManager {
     return mtree.getNodesCountInGivenLevel(pathPattern, level);
   }
 
+  public Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
+      PartialPath pathPattern, int level) throws MetadataException {
+    return mtree.getMeasurementCountGroupByLevel(pathPattern, level);
+  }
+
   // endregion
 
   // region Interfaces for level Node info Query
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTree.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTree.java
index a63bd66..80b4035 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTree.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTree.java
@@ -52,6 +52,7 @@ import org.apache.iotdb.db.metadata.mtree.traverser.counter.CounterTraverser;
 import org.apache.iotdb.db.metadata.mtree.traverser.counter.EntityCounter;
 import org.apache.iotdb.db.metadata.mtree.traverser.counter.MNodeLevelCounter;
 import org.apache.iotdb.db.metadata.mtree.traverser.counter.MeasurementCounter;
+import org.apache.iotdb.db.metadata.mtree.traverser.counter.MeasurementGroupByLevelCounter;
 import org.apache.iotdb.db.metadata.mtree.traverser.counter.StorageGroupCounter;
 import org.apache.iotdb.db.metadata.path.MeasurementPath;
 import org.apache.iotdb.db.metadata.path.PartialPath;
@@ -1251,6 +1252,15 @@ public class MTree implements Serializable {
     counter.traverse();
     return counter.getCount();
   }
+
+  public Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
+      PartialPath pathPattern, int level) throws MetadataException {
+    MeasurementGroupByLevelCounter counter =
+        new MeasurementGroupByLevelCounter(root, pathPattern, level);
+    counter.traverse();
+    return counter.getResult();
+  }
+
   // endregion
 
   // endregion
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/counter/MeasurementGroupByLevelCounter.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/counter/MeasurementGroupByLevelCounter.java
new file mode 100644
index 0000000..b3cecf1
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/counter/MeasurementGroupByLevelCounter.java
@@ -0,0 +1,74 @@
+/*
+ * 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.metadata.mtree.traverser.counter;
+
+import org.apache.iotdb.db.exception.metadata.MetadataException;
+import org.apache.iotdb.db.metadata.mnode.IMNode;
+import org.apache.iotdb.db.metadata.mtree.traverser.Traverser;
+import org.apache.iotdb.db.metadata.path.PartialPath;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MeasurementGroupByLevelCounter extends Traverser {
+
+  // level query option
+  private int groupByLevel;
+
+  private Map<PartialPath, Integer> result = new HashMap<>();
+
+  // path representing current branch and matching the pattern and level
+  private PartialPath path;
+
+  public MeasurementGroupByLevelCounter(IMNode startNode, PartialPath path, int groupByLevel)
+      throws MetadataException {
+    super(startNode, path);
+    this.groupByLevel = groupByLevel;
+  }
+
+  @Override
+  protected boolean processInternalMatchedMNode(IMNode node, int idx, int level)
+      throws MetadataException {
+    if (level == groupByLevel) {
+      path = node.getPartialPath();
+      result.putIfAbsent(path, 0);
+    }
+    return false;
+  }
+
+  @Override
+  protected boolean processFullMatchedMNode(IMNode node, int idx, int level)
+      throws MetadataException {
+    if (level == groupByLevel) {
+      path = node.getPartialPath();
+      result.putIfAbsent(path, 0);
+    }
+    if (!node.isMeasurement()) {
+      return false;
+    }
+    if (level >= groupByLevel) {
+      result.put(path, result.get(path) + 1);
+    }
+    return true;
+  }
+
+  public Map<PartialPath, Integer> getResult() {
+    return result;
+  }
+}
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 b4a778f..46f65d3 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
@@ -686,20 +686,19 @@ public class PlanExecutor implements IPlanExecutor {
   }
 
   private QueryDataSet processCountNodeTimeSeries(CountPlan countPlan) throws MetadataException {
-    // get the nodes that need to group by first
-    List<PartialPath> nodes = getNodesList(countPlan.getPath(), countPlan.getLevel());
+    Map<PartialPath, Integer> countResults = getTimeseriesCountGroupByLevel(countPlan);
     ListDataSet listDataSet =
         new ListDataSet(
             Arrays.asList(
                 new PartialPath(COLUMN_COLUMN, false), new PartialPath(COLUMN_COUNT, false)),
             Arrays.asList(TSDataType.TEXT, TSDataType.INT32));
-    for (PartialPath columnPath : nodes) {
+    for (PartialPath columnPath : countResults.keySet()) {
       RowRecord record = new RowRecord(0);
       Field field = new Field(TSDataType.TEXT);
       field.setBinaryV(new Binary(columnPath.getFullPath()));
       Field field1 = new Field(TSDataType.INT32);
       // get the count of every group
-      field1.setIntV(getPathsNum(columnPath));
+      field1.setIntV(countResults.get(columnPath));
       record.addField(field);
       record.addField(field1);
       listDataSet.putRecord(record);
@@ -776,6 +775,12 @@ public class PlanExecutor implements IPlanExecutor {
     return IoTDB.metaManager.getNodesListInGivenLevel(schemaPattern, level);
   }
 
+  private Map<PartialPath, Integer> getTimeseriesCountGroupByLevel(CountPlan countPlan)
+      throws MetadataException {
+    return IoTDB.metaManager.getMeasurementCountGroupByLevel(
+        countPlan.getPath(), countPlan.getLevel());
+  }
+
   private QueryDataSet processCountTimeSeries(CountPlan countPlan) throws MetadataException {
     int num = getPathsNum(countPlan.getPath());
     return createSingleDataSet(COLUMN_COUNT, TSDataType.INT32, num);
diff --git a/server/src/test/java/org/apache/iotdb/db/metadata/MTreeTest.java b/server/src/test/java/org/apache/iotdb/db/metadata/MTreeTest.java
index e396e32..2cea00f 100644
--- a/server/src/test/java/org/apache/iotdb/db/metadata/MTreeTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/metadata/MTreeTest.java
@@ -45,6 +45,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
@@ -977,4 +978,76 @@ public class MTreeTest {
     Assert.assertEquals(1, root.getDevicesByTimeseries(new PartialPath("root.*.d1.*")).size());
     Assert.assertEquals(2, root.getDevicesByTimeseries(new PartialPath("root.*.d*.*")).size());
   }
+
+  @Test
+  public void testGetMeasurementCountGroupByLevel() throws Exception {
+    MTree root = new MTree();
+    root.setStorageGroup(new PartialPath("root.sg1"));
+    root.createTimeseries(
+        new PartialPath("root.sg1.s1"),
+        TSDataType.INT32,
+        TSEncoding.PLAIN,
+        CompressionType.GZIP,
+        null,
+        null);
+    root.createTimeseries(
+        new PartialPath("root.sg1.d1.s1"),
+        TSDataType.INT32,
+        TSEncoding.PLAIN,
+        CompressionType.GZIP,
+        null,
+        null);
+    root.createTimeseries(
+        new PartialPath("root.sg1.d1.s2"),
+        TSDataType.INT32,
+        TSEncoding.PLAIN,
+        CompressionType.GZIP,
+        null,
+        null);
+
+    root.setStorageGroup(new PartialPath("root.sg2"));
+    root.createTimeseries(
+        new PartialPath("root.sg2.s1"),
+        TSDataType.INT32,
+        TSEncoding.PLAIN,
+        CompressionType.GZIP,
+        null,
+        null);
+    root.createTimeseries(
+        new PartialPath("root.sg2.d1.s1"),
+        TSDataType.INT32,
+        TSEncoding.PLAIN,
+        CompressionType.GZIP,
+        null,
+        null);
+
+    PartialPath pattern = new PartialPath("root.**");
+    Map<PartialPath, Integer> result = root.getMeasurementCountGroupByLevel(pattern, 1);
+    assertEquals(2, result.size());
+    assertEquals(3, (int) result.get(new PartialPath("root.sg1")));
+    assertEquals(2, (int) result.get(new PartialPath("root.sg2")));
+
+    result = root.getMeasurementCountGroupByLevel(pattern, 2);
+    assertEquals(4, result.size());
+    assertEquals(1, (int) result.get(new PartialPath("root.sg1.s1")));
+    assertEquals(2, (int) result.get(new PartialPath("root.sg1.d1")));
+    assertEquals(1, (int) result.get(new PartialPath("root.sg2.s1")));
+    assertEquals(1, (int) result.get(new PartialPath("root.sg2.d1")));
+
+    result = root.getMeasurementCountGroupByLevel(pattern, 4);
+    assertEquals(0, result.size());
+
+    pattern = new PartialPath("root.**.s1");
+    result = root.getMeasurementCountGroupByLevel(pattern, 1);
+    assertEquals(2, result.size());
+    assertEquals(2, (int) result.get(new PartialPath("root.sg1")));
+    assertEquals(2, (int) result.get(new PartialPath("root.sg2")));
+
+    result = root.getMeasurementCountGroupByLevel(pattern, 2);
+    assertEquals(4, result.size());
+    assertEquals(1, (int) result.get(new PartialPath("root.sg1.s1")));
+    assertEquals(1, (int) result.get(new PartialPath("root.sg1.d1")));
+    assertEquals(1, (int) result.get(new PartialPath("root.sg2.s1")));
+    assertEquals(1, (int) result.get(new PartialPath("root.sg2.d1")));
+  }
 }