You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ch...@apache.org on 2022/07/27 09:33:03 UTC

[iotdb] branch master updated: [IOTDB-3942] Support count timeseries where tag1 = v1 (#6763)

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

chaow 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 159f669103 [IOTDB-3942] Support count timeseries where tag1 = v1 (#6763)
159f669103 is described below

commit 159f6691034666fe2c3251199765c878d438b5a4
Author: 任宇华 <79...@users.noreply.github.com>
AuthorDate: Wed Jul 27 17:32:58 2022 +0800

    [IOTDB-3942] Support count timeseries where tag1 = v1 (#6763)
---
 .../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4   |  12 +--
 docs/UserGuide/Operate-Metadata/Timeseries.md      |  48 +++++++++-
 docs/zh/UserGuide/Operate-Metadata/Timeseries.md   |  50 +++++++++-
 .../iotdb/db/it/schema/IoTDBMetadataFetchIT.java   | 104 +++++++++++++++++++++
 .../schemaregion/rocksdb/RSchemaRegion.java        |  64 +++++++++++++
 .../iotdb/db/metadata/mtree/IMTreeBelowSG.java     |  17 ++++
 .../db/metadata/mtree/MTreeBelowSGCachedImpl.java  |  27 ++++++
 .../db/metadata/mtree/MTreeBelowSGMemoryImpl.java  |  27 ++++++
 .../traverser/counter/MeasurementCounter.java      |  24 +++++
 .../counter/MeasurementGroupByLevelCounter.java    |  24 +++++
 .../db/metadata/schemaregion/ISchemaRegion.java    |  13 +++
 .../schemaregion/SchemaRegionMemoryImpl.java       |  28 ++++++
 .../schemaregion/SchemaRegionSchemaFileImpl.java   |  28 ++++++
 .../apache/iotdb/db/metadata/tag/TagManager.java   |  39 ++++++++
 .../schema/LevelTimeSeriesCountOperator.java       |  28 +++++-
 .../operator/schema/TimeSeriesCountOperator.java   |  26 +++++-
 .../iotdb/db/mpp/plan/parser/ASTVisitor.java       |  44 ++++++---
 .../db/mpp/plan/planner/LocalExecutionPlanner.java |  13 ++-
 .../db/mpp/plan/planner/LogicalPlanBuilder.java    |  21 ++++-
 .../db/mpp/plan/planner/LogicalPlanVisitor.java    |  11 ++-
 .../metedata/read/LevelTimeSeriesCountNode.java    |  41 +++++++-
 .../node/metedata/read/TimeSeriesCountNode.java    |  40 +++++++-
 .../metadata/CountLevelTimeSeriesStatement.java    |  32 +++++++
 .../metadata/CountTimeSeriesStatement.java         |  30 ++++++
 .../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java    |   6 +-
 .../operator/schema/CountMergeOperatorTest.java    |  21 ++++-
 .../operator/schema/SchemaCountOperatorTest.java   |  21 ++++-
 .../metadata/read/SchemaCountNodeSerdeTest.java    |   8 +-
 28 files changed, 792 insertions(+), 55 deletions(-)

diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
index 03868d644a..fadcb92542 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4
@@ -261,11 +261,7 @@ showDevices
 
 // Show Timeseries
 showTimeseries
-    : SHOW LATEST? TIMESERIES prefixPath? showWhereClause? limitClause?
-    ;
-
-showWhereClause
-    : WHERE (attributePair | containsExpression)
+    : SHOW LATEST? TIMESERIES prefixPath? tagWhereClause? limitClause?
     ;
 
 // Show Child Paths
@@ -350,7 +346,7 @@ countDevices
 
 // Count Timeseries
 countTimeseries
-    : COUNT TIMESERIES prefixPath? (GROUP BY LEVEL operator_eq INTEGER_LITERAL)?
+    : COUNT TIMESERIES prefixPath? tagWhereClause? (GROUP BY LEVEL operator_eq INTEGER_LITERAL)?
     ;
 
 // Count Nodes
@@ -358,6 +354,10 @@ countNodes
     : COUNT NODES prefixPath LEVEL operator_eq INTEGER_LITERAL
     ;
 
+tagWhereClause
+    : WHERE (attributePair | containsExpression)
+    ;
+
 
 /**
  * 3. Data Manipulation Language (DML)
diff --git a/docs/UserGuide/Operate-Metadata/Timeseries.md b/docs/UserGuide/Operate-Metadata/Timeseries.md
index c09a75cd54..a256558a8f 100644
--- a/docs/UserGuide/Operate-Metadata/Timeseries.md
+++ b/docs/UserGuide/Operate-Metadata/Timeseries.md
@@ -275,7 +275,7 @@ ALTER timeseries root.turbine.d1.s1 UPSERT ALIAS=newAlias TAGS(tag3=v3, tag4=v4)
 SHOW TIMESERIES (<`PathPattern`>)? WhereClause
 ```
 
-  returns all the timeseries information that satisfy the where condition and match the pathPattern. SQL statements are as follows:
+returns all the timeseries information that satisfy the where condition and match the pathPattern. SQL statements are as follows:
 
 ```
 ALTER timeseries root.ln.wf02.wt02.hardware ADD TAGS unit=c
@@ -304,6 +304,52 @@ Total line number = 1
 It costs 0.004s
 ```
 
+- count timeseries using tags
+
+```
+COUNT TIMESERIES (<`PathPattern`>)? WhereClause
+COUNT TIMESERIES (<`PathPattern`>)? WhereClause GROUP BY LEVEL=<INTEGER>
+```
+
+returns all the number of timeseries that satisfy the where condition and match the pathPattern. SQL statements are as follows:
+
+```
+count timeseries
+count timeseries root.** where unit = c
+count timeseries root.** where unit = c group by level = 2
+```
+
+The results are shown below respectly :
+
+```
+IoTDB> count timeseries
++-----------------+
+|count(timeseries)|
++-----------------+
+|                6|
++-----------------+
+Total line number = 1
+It costs 0.019s
+IoTDB> count timeseries root.** where unit = c
++-----------------+
+|count(timeseries)|
++-----------------+
+|                2|
++-----------------+
+Total line number = 1
+It costs 0.020s
+IoTDB> count timeseries root.** where unit = c group by level = 2
++--------------+-----------------+
+|        column|count(timeseries)|
++--------------+-----------------+
+|  root.ln.wf02|                2|
+|  root.ln.wf01|                0|
+|root.sgcc.wf03|                0|
++--------------+-----------------+
+Total line number = 3
+It costs 0.011s
+```
+
 > Notice that, we only support one condition in the where clause. Either it's an equal filter or it is an `contains` filter. In both case, the property in the where condition must be a tag.
 
 create aligned timeseries
diff --git a/docs/zh/UserGuide/Operate-Metadata/Timeseries.md b/docs/zh/UserGuide/Operate-Metadata/Timeseries.md
index ee135da1e6..adfb2c9731 100644
--- a/docs/zh/UserGuide/Operate-Metadata/Timeseries.md
+++ b/docs/zh/UserGuide/Operate-Metadata/Timeseries.md
@@ -271,10 +271,10 @@ ALTER timeseries root.turbine.d1.s1 UPSERT ALIAS=newAlias TAGS(tag2=newV2, tag3=
 
 * 使用标签作为过滤条件查询时间序列
 ```
-* SHOW TIMESERIES (<`PathPattern`>)? WhereClause
+SHOW TIMESERIES (<`PathPattern`>)? WhereClause
 ```
 
-  返回给定路径的下的所有满足条件的时间序列信息,SQL 语句如下所示:
+返回给定路径的下的所有满足条件的时间序列信息,SQL 语句如下所示:
 
 ```
 ALTER timeseries root.ln.wf02.wt02.hardware ADD TAGS unit=c
@@ -303,6 +303,52 @@ Total line number = 1
 It costs 0.004s
 ```
 
+- 使用标签作为过滤条件统计时间序列数量
+
+```
+COUNT TIMESERIES (<`PathPattern`>)? WhereClause
+COUNT TIMESERIES (<`PathPattern`>)? WhereClause GROUP BY LEVEL=<INTEGER>
+```
+
+返回给定路径的下的所有满足条件的时间序列的数量,SQL 语句如下所示:
+
+```
+count timeseries
+count timeseries root.** where unit = c
+count timeseries root.** where unit = c group by level = 2
+```
+
+执行结果分别为:
+
+```
+IoTDB> count timeseries
++-----------------+
+|count(timeseries)|
++-----------------+
+|                6|
++-----------------+
+Total line number = 1
+It costs 0.019s
+IoTDB> count timeseries root.** where unit = c
++-----------------+
+|count(timeseries)|
++-----------------+
+|                2|
++-----------------+
+Total line number = 1
+It costs 0.020s
+IoTDB> count timeseries root.** where unit = c group by level = 2
++--------------+-----------------+
+|        column|count(timeseries)|
++--------------+-----------------+
+|  root.ln.wf02|                2|
+|  root.ln.wf01|                0|
+|root.sgcc.wf03|                0|
++--------------+-----------------+
+Total line number = 3
+It costs 0.011s
+```
+
 > 注意,现在我们只支持一个查询条件,要么是等值条件查询,要么是包含条件查询。当然 where 子句中涉及的必须是标签值,而不能是属性值。
 
 创建对齐时间序列
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/schema/IoTDBMetadataFetchIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/schema/IoTDBMetadataFetchIT.java
index 98b216e71e..5b77f67fab 100644
--- a/integration-test/src/test/java/org/apache/iotdb/db/it/schema/IoTDBMetadataFetchIT.java
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/schema/IoTDBMetadataFetchIT.java
@@ -26,6 +26,7 @@ import org.apache.iotdb.itbase.category.LocalStandaloneIT;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.runner.RunWith;
@@ -354,6 +355,45 @@ public class IoTDBMetadataFetchIT {
     }
   }
 
+  @Test
+  @Ignore(
+      value =
+          "Old IoTDB service not support 'count timeseries by tag',Waiting for the old IoTDB to be removed.")
+  public void showCountTimeSeriesWithTag() throws SQLException {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute(
+          "create timeseries root.sg.d.s1 with datatype=FLOAT, encoding=RLE, compression=SNAPPY tags('tag1'='v1', 'tag2'='v2')");
+      statement.execute(
+          "create timeseries root.sg1.d.s1 with datatype=FLOAT, encoding=RLE, compression=SNAPPY tags('tag1'='v1')");
+      String[] sqls =
+          new String[] {
+            "COUNT TIMESERIES root.sg.** where tag1 = v1",
+            "COUNT TIMESERIES where tag1 = v1",
+            "COUNT TIMESERIES where tag3 = v3"
+          };
+      String[] standards = new String[] {"1,\n", "2,\n", "0,\n"};
+      for (int n = 0; n < sqls.length; n++) {
+        String sql = sqls[n];
+        String standard = standards[n];
+        StringBuilder builder = new StringBuilder();
+        try (ResultSet resultSet = statement.executeQuery(sql)) {
+          ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
+          while (resultSet.next()) {
+            for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+              builder.append(resultSet.getString(i)).append(",");
+            }
+            builder.append("\n");
+          }
+          Assert.assertEquals(standard, builder.toString());
+        } catch (SQLException e) {
+          e.printStackTrace();
+          fail(e.getMessage());
+        }
+      }
+    }
+  }
+
   @Test
   public void showCountDevices() throws SQLException {
     try (Connection connection = EnvFactory.getEnv().getConnection();
@@ -457,6 +497,70 @@ public class IoTDBMetadataFetchIT {
     }
   }
 
+  @Test
+  @Ignore(
+      value =
+          "Old IoTDB service not support 'count timeseries by tag',Waiting for the old IoTDB to be removed.")
+  public void showCountTimeSeriesGroupByWithTag() throws SQLException {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute(
+          "create timeseries root.sg.d.status with datatype=FLOAT, encoding=RLE, compression=SNAPPY tags('tag1'='v1', 'tag2'='v2')");
+      statement.execute(
+          "create timeseries root.sg1.d.status with datatype=FLOAT, encoding=RLE, compression=SNAPPY tags('tag1'='v1')");
+      String[] sqls =
+          new String[] {
+            "COUNT TIMESERIES root.** where tag1 = v1 group by level=1",
+            "COUNT TIMESERIES root.** where tag2 = v2 group by level=3",
+            "COUNT TIMESERIES root.**.status where tag1 = v1 group by level=2",
+            "COUNT TIMESERIES root.** where tag3 = v3 group by level=2"
+          };
+      Set<String>[] standards =
+          new Set[] {
+            new HashSet<>(
+                Arrays.asList(
+                    "root.ln,0,", "root.ln1,0,", "root.ln2,0,", "root.sg,1,", "root.sg1,1,")),
+            new HashSet<>(
+                Arrays.asList(
+                    "root.ln.wf01.wt01,0,",
+                    "root.ln.wf01.wt02,0,",
+                    "root.ln1.wf01.wt01,0,",
+                    "root.ln2.wf01.wt01,0,",
+                    "root.sg.d.status,1,",
+                    "root.sg1.d.status,0,")),
+            new HashSet<>(
+                Arrays.asList(
+                    "root.ln.wf01,0,",
+                    "root.ln1.wf01,0,",
+                    "root.ln2.wf01,0,",
+                    "root.sg.d,1,",
+                    "root.sg1.d,1,")),
+            new HashSet<>(
+                Arrays.asList(
+                    "root.ln.wf01,0,",
+                    "root.ln1.wf01,0,",
+                    "root.ln2.wf01,0,",
+                    "root.sg.d,0,",
+                    "root.sg1.d,0,")),
+          };
+      for (int n = 0; n < sqls.length; n++) {
+        String sql = sqls[n];
+        Set<String> standard = standards[n];
+        try (ResultSet resultSet = statement.executeQuery(sql)) {
+          while (resultSet.next()) {
+            String string = resultSet.getString(1) + "," + resultSet.getInt(2) + ",";
+            Assert.assertTrue(standard.contains(string));
+            standard.remove(string);
+          }
+          assertEquals(0, standard.size());
+        } catch (SQLException e) {
+          e.printStackTrace();
+          fail(e.getMessage());
+        }
+      }
+    }
+  }
+
   @Test
   public void showCountNodes() throws SQLException {
     try (Connection connection = EnvFactory.getEnv().getConnection();
diff --git a/schema-engine-rocksdb/src/main/java/org/apache/iotdb/db/metadata/schemaregion/rocksdb/RSchemaRegion.java b/schema-engine-rocksdb/src/main/java/org/apache/iotdb/db/metadata/schemaregion/rocksdb/RSchemaRegion.java
index edadd1aa02..574bd21ca6 100644
--- a/schema-engine-rocksdb/src/main/java/org/apache/iotdb/db/metadata/schemaregion/rocksdb/RSchemaRegion.java
+++ b/schema-engine-rocksdb/src/main/java/org/apache/iotdb/db/metadata/schemaregion/rocksdb/RSchemaRegion.java
@@ -856,6 +856,13 @@ public class RSchemaRegion implements ISchemaRegion {
     return getCountByNodeType(new Character[] {NODE_TYPE_MEASUREMENT}, pathPattern.getNodes());
   }
 
+  @Override
+  public int getAllTimeseriesCount(
+      PartialPath pathPattern, boolean isPrefixMatch, String key, String value, boolean isContains)
+      throws MetadataException {
+    return getMatchedMeasurementPathWithTags(pathPattern.getNodes()).size();
+  }
+
   @TestOnly
   public int getAllTimeseriesCount(PartialPath pathPattern) throws MetadataException {
     return getAllTimeseriesCount(pathPattern, false);
@@ -933,6 +940,63 @@ public class RSchemaRegion implements ISchemaRegion {
     return result;
   }
 
+  @Override
+  public Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
+      PartialPath pathPattern,
+      int level,
+      boolean isPrefixMatch,
+      String key,
+      String value,
+      boolean isContains)
+      throws MetadataException {
+    Map<PartialPath, Integer> result = new ConcurrentHashMap<>();
+    Map<MeasurementPath, Pair<Map<String, String>, Map<String, String>>> measurementPathsAndTags =
+        getMatchedMeasurementPathWithTags(pathPattern.getNodes());
+    BiFunction<byte[], byte[], Boolean> function;
+    if (!measurementPathsAndTags.isEmpty()) {
+      function =
+          (a, b) -> {
+            String k = new String(a);
+            String partialName = splitToPartialNameByLevel(k, level);
+            if (partialName != null) {
+              PartialPath path = null;
+              try {
+                path = new PartialPath(partialName);
+              } catch (IllegalPathException e) {
+                logger.warn(e.getMessage());
+              }
+              if (!measurementPathsAndTags.keySet().contains(partialName)) {
+                result.put(path, result.get(path));
+              } else {
+                result.putIfAbsent(path, 0);
+                result.put(path, result.get(path) + 1);
+              }
+            }
+            return true;
+          };
+    } else {
+      function =
+          (a, b) -> {
+            String k = new String(a);
+            String partialName = splitToPartialNameByLevel(k, level);
+            if (partialName != null) {
+              PartialPath path = null;
+              try {
+                path = new PartialPath(partialName);
+              } catch (IllegalPathException e) {
+                logger.warn(e.getMessage());
+              }
+              result.putIfAbsent(path, 0);
+            }
+            return true;
+          };
+    }
+    traverseOutcomeBasins(
+        pathPattern.getNodes(), MAX_PATH_DEPTH, function, new Character[] {NODE_TYPE_MEASUREMENT});
+
+    return result;
+  }
+
   private String splitToPartialNameByLevel(String innerName, int level) {
     StringBuilder stringBuilder = new StringBuilder(ROOT_STRING);
     boolean currentIsFlag;
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/IMTreeBelowSG.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/IMTreeBelowSG.java
index 7c4fff16bc..f1e2b8cdce 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/IMTreeBelowSG.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/IMTreeBelowSG.java
@@ -203,6 +203,15 @@ public interface IMTreeBelowSG {
    */
   int getAllTimeseriesCount(PartialPath pathPattern) throws MetadataException;
 
+  /**
+   * Get the count of timeseries matching the given path by tag.
+   *
+   * @param pathPattern a path pattern or a full path, may contain wildcard
+   */
+  int getAllTimeseriesCount(
+      PartialPath pathPattern, boolean isPrefixMatch, List<String> timeseries, boolean hasTag)
+      throws MetadataException;
+
   /**
    * Get the count of devices matching the given path. If using prefix match, the path pattern is
    * used to match prefix path. All timeseries start with the matched prefix path will be counted.
@@ -230,6 +239,14 @@ public interface IMTreeBelowSG {
   Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
       PartialPath pathPattern, int level, boolean isPrefixMatch) throws MetadataException;
 
+  Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
+      PartialPath pathPattern,
+      int level,
+      boolean isPrefixMatch,
+      List<String> timeseries,
+      boolean hasTag)
+      throws MetadataException;
+
   /**
    * Get node by the path
    *
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTreeBelowSGCachedImpl.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTreeBelowSGCachedImpl.java
index 8d19efa765..7247047d7a 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTreeBelowSGCachedImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTreeBelowSGCachedImpl.java
@@ -862,6 +862,17 @@ public class MTreeBelowSGCachedImpl implements IMTreeBelowSG {
     return getAllTimeseriesCount(pathPattern, false);
   }
 
+  @Override
+  public int getAllTimeseriesCount(
+      PartialPath pathPattern, boolean isPrefixMatch, List<String> timeseries, boolean hasTag)
+      throws MetadataException {
+    CounterTraverser counter =
+        new MeasurementCounter(storageGroupMNode, pathPattern, store, timeseries, hasTag);
+    counter.setPrefixMatch(isPrefixMatch);
+    counter.traverse();
+    return counter.getCount();
+  }
+
   /**
    * Get the count of devices matching the given path. If using prefix match, the path pattern is
    * used to match prefix path. All timeseries start with the matched prefix path will be counted.
@@ -912,6 +923,22 @@ public class MTreeBelowSGCachedImpl implements IMTreeBelowSG {
     return counter.getResult();
   }
 
+  @Override
+  public Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
+      PartialPath pathPattern,
+      int level,
+      boolean isPrefixMatch,
+      List<String> timeseries,
+      boolean hasTag)
+      throws MetadataException {
+    MeasurementGroupByLevelCounter counter =
+        new MeasurementGroupByLevelCounter(
+            storageGroupMNode, pathPattern, store, level, timeseries, hasTag);
+    counter.setPrefixMatch(isPrefixMatch);
+    counter.traverse();
+    return counter.getResult();
+  }
+
   // endregion
 
   // endregion
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTreeBelowSGMemoryImpl.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTreeBelowSGMemoryImpl.java
index 46abb13445..67a020654b 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTreeBelowSGMemoryImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/MTreeBelowSGMemoryImpl.java
@@ -873,6 +873,17 @@ public class MTreeBelowSGMemoryImpl implements IMTreeBelowSG {
     return getAllTimeseriesCount(pathPattern, false);
   }
 
+  @Override
+  public int getAllTimeseriesCount(
+      PartialPath pathPattern, boolean isPrefixMatch, List<String> timeseries, boolean hasTag)
+      throws MetadataException {
+    CounterTraverser counter =
+        new MeasurementCounter(storageGroupMNode, pathPattern, store, timeseries, hasTag);
+    counter.setPrefixMatch(isPrefixMatch);
+    counter.traverse();
+    return counter.getCount();
+  }
+
   /**
    * Get the count of devices matching the given path. If using prefix match, the path pattern is
    * used to match prefix path. All timeseries start with the matched prefix path will be counted.
@@ -923,6 +934,22 @@ public class MTreeBelowSGMemoryImpl implements IMTreeBelowSG {
     return counter.getResult();
   }
 
+  @Override
+  public Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
+      PartialPath pathPattern,
+      int level,
+      boolean isPrefixMatch,
+      List<String> timeseries,
+      boolean hasTag)
+      throws MetadataException {
+    MeasurementGroupByLevelCounter counter =
+        new MeasurementGroupByLevelCounter(
+            storageGroupMNode, pathPattern, store, level, timeseries, hasTag);
+    counter.setPrefixMatch(isPrefixMatch);
+    counter.traverse();
+    return counter.getResult();
+  }
+
   // endregion
 
   // endregion
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/counter/MeasurementCounter.java b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/counter/MeasurementCounter.java
index c53afae24c..6fe05844ec 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/counter/MeasurementCounter.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/mtree/traverser/counter/MeasurementCounter.java
@@ -23,16 +23,35 @@ import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.db.metadata.mnode.IMNode;
 import org.apache.iotdb.db.metadata.mtree.store.IMTreeStore;
 
+import java.util.ArrayList;
+import java.util.List;
+
 // This method implements the measurement count function.
 // One MultiMeasurement will only be count once.
 public class MeasurementCounter extends CounterTraverser {
 
+  private List<String> timeseries = new ArrayList<>();
+  private boolean hasTag = false;
+
   public MeasurementCounter(IMNode startNode, PartialPath path, IMTreeStore store)
       throws MetadataException {
     super(startNode, path, store);
     shouldTraverseTemplate = true;
   }
 
+  public MeasurementCounter(
+      IMNode startNode,
+      PartialPath path,
+      IMTreeStore store,
+      List<String> timeseries,
+      boolean hasTag)
+      throws MetadataException {
+    super(startNode, path, store);
+    shouldTraverseTemplate = true;
+    this.timeseries = timeseries;
+    this.hasTag = hasTag;
+  }
+
   @Override
   protected boolean processInternalMatchedMNode(IMNode node, int idx, int level) {
     return false;
@@ -43,6 +62,11 @@ public class MeasurementCounter extends CounterTraverser {
     if (!node.isMeasurement()) {
       return false;
     }
+    if (hasTag) {
+      if (!timeseries.contains(node.getFullPath())) {
+        return true;
+      }
+    }
     count++;
     return true;
   }
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
index 5f58524885..55156fea9c 100644
--- 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
@@ -24,7 +24,9 @@ import org.apache.iotdb.db.metadata.mnode.IMNode;
 import org.apache.iotdb.db.metadata.mtree.store.IMTreeStore;
 import org.apache.iotdb.db.metadata.mtree.traverser.Traverser;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 public class MeasurementGroupByLevelCounter extends Traverser {
@@ -33,6 +35,8 @@ public class MeasurementGroupByLevelCounter extends Traverser {
   private int groupByLevel;
 
   private Map<PartialPath, Integer> result = new HashMap<>();
+  private List<String> timeseries = new ArrayList<>();
+  private boolean hasTag = false;
 
   // path representing current branch and matching the pattern and level
   private PartialPath path;
@@ -45,6 +49,21 @@ public class MeasurementGroupByLevelCounter extends Traverser {
     checkLevelAboveSG();
   }
 
+  public MeasurementGroupByLevelCounter(
+      IMNode startNode,
+      PartialPath path,
+      IMTreeStore store,
+      int groupByLevel,
+      List<String> timeseries,
+      boolean hasTag)
+      throws MetadataException {
+    super(startNode, path, store);
+    this.groupByLevel = groupByLevel;
+    this.timeseries = timeseries;
+    this.hasTag = hasTag;
+    checkLevelAboveSG();
+  }
+
   /**
    * The traverser may start traversing from a storageGroupMNode, which is an InternalMNode of the
    * whole MTree.
@@ -86,6 +105,11 @@ public class MeasurementGroupByLevelCounter extends Traverser {
     if (!node.isMeasurement()) {
       return false;
     }
+    if (hasTag) {
+      if (!timeseries.contains(node.getFullPath())) {
+        return true;
+      }
+    }
     if (level >= groupByLevel) {
       result.put(path, result.get(path) + 1);
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/ISchemaRegion.java b/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/ISchemaRegion.java
index 9f07db8ec6..700eb6f4d7 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/ISchemaRegion.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/ISchemaRegion.java
@@ -139,10 +139,23 @@ public interface ISchemaRegion {
   int getAllTimeseriesCount(PartialPath pathPattern, boolean isPrefixMatch)
       throws MetadataException;
 
+  int getAllTimeseriesCount(
+      PartialPath pathPattern, boolean isPrefixMatch, String key, String value, boolean isContains)
+      throws MetadataException;
+
   // The measurements will be grouped by the node in given level and then counted for each group.
   Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
       PartialPath pathPattern, int level, boolean isPrefixMatch) throws MetadataException;
 
+  Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
+      PartialPath pathPattern,
+      int level,
+      boolean isPrefixMatch,
+      String key,
+      String value,
+      boolean isContains)
+      throws MetadataException;
+
   /**
    * To calculate the count of devices for given path pattern. If using prefix match, the path
    * pattern is used to match prefix path. All timeseries start with the matched prefix path will be
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/SchemaRegionMemoryImpl.java b/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/SchemaRegionMemoryImpl.java
index 605ed59f2e..78754414eb 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/SchemaRegionMemoryImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/SchemaRegionMemoryImpl.java
@@ -923,6 +923,17 @@ public class SchemaRegionMemoryImpl implements ISchemaRegion {
     return mtree.getAllTimeseriesCount(pathPattern, isPrefixMatch);
   }
 
+  @Override
+  public int getAllTimeseriesCount(
+      PartialPath pathPattern, boolean isPrefixMatch, String key, String value, boolean isContains)
+      throws MetadataException {
+    return mtree.getAllTimeseriesCount(
+        pathPattern,
+        isPrefixMatch,
+        tagManager.getMatchedTimeseriesInIndex(key, value, isContains),
+        true);
+  }
+
   /**
    * To calculate the count of devices for given path pattern. If using prefix match, the path
    * pattern is used to match prefix path. All timeseries start with the matched prefix path will be
@@ -957,6 +968,23 @@ public class SchemaRegionMemoryImpl implements ISchemaRegion {
     return mtree.getMeasurementCountGroupByLevel(pathPattern, level, isPrefixMatch);
   }
 
+  @Override
+  public Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
+      PartialPath pathPattern,
+      int level,
+      boolean isPrefixMatch,
+      String key,
+      String value,
+      boolean isContains)
+      throws MetadataException {
+    return mtree.getMeasurementCountGroupByLevel(
+        pathPattern,
+        level,
+        isPrefixMatch,
+        tagManager.getMatchedTimeseriesInIndex(key, value, isContains),
+        true);
+  }
+
   // endregion
 
   // region Interfaces for level Node info Query
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/SchemaRegionSchemaFileImpl.java b/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/SchemaRegionSchemaFileImpl.java
index 9a706d84b9..a49e3767de 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/SchemaRegionSchemaFileImpl.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/schemaregion/SchemaRegionSchemaFileImpl.java
@@ -866,6 +866,17 @@ public class SchemaRegionSchemaFileImpl implements ISchemaRegion {
     return mtree.getAllTimeseriesCount(pathPattern, isPrefixMatch);
   }
 
+  @Override
+  public int getAllTimeseriesCount(
+      PartialPath pathPattern, boolean isPrefixMatch, String key, String value, boolean isContains)
+      throws MetadataException {
+    return mtree.getAllTimeseriesCount(
+        pathPattern,
+        isPrefixMatch,
+        tagManager.getMatchedTimeseriesInIndex(key, value, isContains),
+        true);
+  }
+
   /**
    * To calculate the count of devices for given path pattern. If using prefix match, the path
    * pattern is used to match prefix path. All timeseries start with the matched prefix path will be
@@ -900,6 +911,23 @@ public class SchemaRegionSchemaFileImpl implements ISchemaRegion {
     return mtree.getMeasurementCountGroupByLevel(pathPattern, level, isPrefixMatch);
   }
 
+  @Override
+  public Map<PartialPath, Integer> getMeasurementCountGroupByLevel(
+      PartialPath pathPattern,
+      int level,
+      boolean isPrefixMatch,
+      String key,
+      String value,
+      boolean isContains)
+      throws MetadataException {
+    return mtree.getMeasurementCountGroupByLevel(
+        pathPattern,
+        level,
+        isPrefixMatch,
+        tagManager.getMatchedTimeseriesInIndex(key, value, isContains),
+        true);
+  }
+
   // endregion
 
   // region Interfaces for level Node info Query
diff --git a/server/src/main/java/org/apache/iotdb/db/metadata/tag/TagManager.java b/server/src/main/java/org/apache/iotdb/db/metadata/tag/TagManager.java
index 4dd2295431..7c6eecd1d3 100644
--- a/server/src/main/java/org/apache/iotdb/db/metadata/tag/TagManager.java
+++ b/server/src/main/java/org/apache/iotdb/db/metadata/tag/TagManager.java
@@ -161,6 +161,45 @@ public class TagManager {
     }
   }
 
+  public List<String> getMatchedTimeseriesInIndex(String key, String value, boolean isContains) {
+    if (!tagIndex.containsKey(key)) {
+      return Collections.emptyList();
+    }
+    Map<String, Set<IMeasurementMNode>> value2Node = tagIndex.get(key);
+    if (value2Node.isEmpty()) {
+      return Collections.emptyList();
+    }
+    List<String> timeseries = new ArrayList<>();
+    List<IMeasurementMNode> allMatchedNodes = new ArrayList<>();
+    if (isContains) {
+      for (Map.Entry<String, Set<IMeasurementMNode>> entry : value2Node.entrySet()) {
+        if (entry.getKey() == null || entry.getValue() == null) {
+          continue;
+        }
+        String tagValue = entry.getKey();
+        if (tagValue.contains(value)) {
+          allMatchedNodes.addAll(entry.getValue());
+        }
+      }
+    } else {
+      for (Map.Entry<String, Set<IMeasurementMNode>> entry : value2Node.entrySet()) {
+        if (entry.getKey() == null || entry.getValue() == null) {
+          continue;
+        }
+        String tagValue = entry.getKey();
+        if (value.equals(tagValue)) {
+          allMatchedNodes.addAll(entry.getValue());
+        }
+      }
+    }
+    allMatchedNodes =
+        allMatchedNodes.stream()
+            .sorted(Comparator.comparing(IMNode::getFullPath))
+            .collect(toList());
+    allMatchedNodes.forEach(measurementMNode -> timeseries.add(measurementMNode.getFullPath()));
+    return timeseries;
+  }
+
   public List<IMeasurementMNode> getMatchedTimeseriesInIndex(
       ShowTimeSeriesPlan plan, QueryContext context) throws MetadataException {
     if (!tagIndex.containsKey(plan.getKey())) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/schema/LevelTimeSeriesCountOperator.java b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/schema/LevelTimeSeriesCountOperator.java
index 917d0e966d..9389035065 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/schema/LevelTimeSeriesCountOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/schema/LevelTimeSeriesCountOperator.java
@@ -38,6 +38,9 @@ public class LevelTimeSeriesCountOperator implements SourceOperator {
   private final PartialPath partialPath;
   private final boolean isPrefixPath;
   private final int level;
+  private final String key;
+  private final String value;
+  private final boolean isContains;
 
   private boolean isFinished;
 
@@ -46,12 +49,18 @@ public class LevelTimeSeriesCountOperator implements SourceOperator {
       OperatorContext operatorContext,
       PartialPath partialPath,
       boolean isPrefixPath,
-      int level) {
+      int level,
+      String key,
+      String value,
+      boolean isContains) {
     this.sourceId = sourceId;
     this.operatorContext = operatorContext;
     this.partialPath = partialPath;
     this.isPrefixPath = isPrefixPath;
     this.level = level;
+    this.key = key;
+    this.value = value;
+    this.isContains = isContains;
   }
 
   @Override
@@ -71,10 +80,19 @@ public class LevelTimeSeriesCountOperator implements SourceOperator {
         new TsBlockBuilder(HeaderConstant.countLevelTimeSeriesHeader.getRespDataTypes());
     Map<PartialPath, Integer> countMap;
     try {
-      countMap =
-          ((SchemaDriverContext) operatorContext.getInstanceContext().getDriverContext())
-              .getSchemaRegion()
-              .getMeasurementCountGroupByLevel(partialPath, level, isPrefixPath);
+      if (key != null && value != null) {
+        countMap =
+            ((SchemaDriverContext) operatorContext.getInstanceContext().getDriverContext())
+                .getSchemaRegion()
+                .getMeasurementCountGroupByLevel(
+                    partialPath, level, isPrefixPath, key, value, isContains);
+      } else {
+        countMap =
+            ((SchemaDriverContext) operatorContext.getInstanceContext().getDriverContext())
+                .getSchemaRegion()
+                .getMeasurementCountGroupByLevel(partialPath, level, isPrefixPath);
+      }
+
     } catch (MetadataException e) {
       throw new RuntimeException(e.getMessage(), e);
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/schema/TimeSeriesCountOperator.java b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/schema/TimeSeriesCountOperator.java
index e3662fc742..0fac3e052a 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/schema/TimeSeriesCountOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/schema/TimeSeriesCountOperator.java
@@ -34,6 +34,9 @@ public class TimeSeriesCountOperator implements SourceOperator {
   private final OperatorContext operatorContext;
   private final PartialPath partialPath;
   private final boolean isPrefixPath;
+  private final String key;
+  private final String value;
+  private final boolean isContains;
 
   private boolean isFinished;
 
@@ -46,11 +49,17 @@ public class TimeSeriesCountOperator implements SourceOperator {
       PlanNodeId sourceId,
       OperatorContext operatorContext,
       PartialPath partialPath,
-      boolean isPrefixPath) {
+      boolean isPrefixPath,
+      String key,
+      String value,
+      boolean isContains) {
     this.sourceId = sourceId;
     this.operatorContext = operatorContext;
     this.partialPath = partialPath;
     this.isPrefixPath = isPrefixPath;
+    this.key = key;
+    this.value = value;
+    this.isContains = isContains;
   }
 
   @Override
@@ -65,10 +74,17 @@ public class TimeSeriesCountOperator implements SourceOperator {
         new TsBlockBuilder(HeaderConstant.countTimeSeriesHeader.getRespDataTypes());
     int count = 0;
     try {
-      count =
-          ((SchemaDriverContext) operatorContext.getInstanceContext().getDriverContext())
-              .getSchemaRegion()
-              .getAllTimeseriesCount(partialPath, isPrefixPath);
+      if (key != null && value != null) {
+        count =
+            ((SchemaDriverContext) operatorContext.getInstanceContext().getDriverContext())
+                .getSchemaRegion()
+                .getAllTimeseriesCount(partialPath, isPrefixPath, key, value, isContains);
+      } else {
+        count =
+            ((SchemaDriverContext) operatorContext.getInstanceContext().getDriverContext())
+                .getSchemaRegion()
+                .getAllTimeseriesCount(partialPath, isPrefixPath);
+      }
     } catch (MetadataException e) {
       throw new RuntimeException(e.getMessage(), e);
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
index 4e7d8b6899..aecf817cd5 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/parser/ASTVisitor.java
@@ -190,7 +190,6 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
   /** Data Definition Language (DDL) */
 
   // Create Timeseries ========================================================================
-
   @Override
   public Statement visitCreateNonAlignedTimeseries(
       IoTDBSqlParser.CreateNonAlignedTimeseriesContext ctx) {
@@ -522,8 +521,8 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
           new ShowTimeSeriesStatement(
               new PartialPath(SQLConstant.getSingleRootArray()), orderByHeat);
     }
-    if (ctx.showWhereClause() != null) {
-      parseShowWhereClause(ctx.showWhereClause(), showTimeSeriesStatement);
+    if (ctx.tagWhereClause() != null) {
+      parseTagWhereClause(ctx.tagWhereClause(), showTimeSeriesStatement);
     }
     if (ctx.limitClause() != null) {
       parseLimitClause(ctx.limitClause(), showTimeSeriesStatement);
@@ -531,19 +530,34 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
     return showTimeSeriesStatement;
   }
 
-  private void parseShowWhereClause(
-      IoTDBSqlParser.ShowWhereClauseContext ctx, ShowTimeSeriesStatement statement) {
+  private void parseTagWhereClause(IoTDBSqlParser.TagWhereClauseContext ctx, Statement statement) {
     IoTDBSqlParser.AttributeValueContext attributeValueContext;
+    String key;
+    String value;
+    boolean isContains;
     if (ctx.containsExpression() != null) {
-      statement.setContains(true);
+      isContains = true;
       attributeValueContext = ctx.containsExpression().attributeValue();
-      statement.setKey(parseAttributeKey(ctx.containsExpression().attributeKey()));
+      key = parseAttributeKey(ctx.containsExpression().attributeKey());
     } else {
-      statement.setContains(false);
+      isContains = false;
       attributeValueContext = ctx.attributePair().attributeValue();
-      statement.setKey(parseAttributeKey(ctx.attributePair().attributeKey()));
+      key = parseAttributeKey(ctx.attributePair().attributeKey());
+    }
+    value = parseAttributeValue(attributeValueContext);
+    if (statement instanceof ShowTimeSeriesStatement) {
+      ((ShowTimeSeriesStatement) statement).setContains(isContains);
+      ((ShowTimeSeriesStatement) statement).setKey(key);
+      ((ShowTimeSeriesStatement) statement).setValue(value);
+    } else if (statement instanceof CountTimeSeriesStatement) {
+      ((CountTimeSeriesStatement) statement).setContains(isContains);
+      ((CountTimeSeriesStatement) statement).setKey(key);
+      ((CountTimeSeriesStatement) statement).setValue(value);
+    } else if (statement instanceof CountLevelTimeSeriesStatement) {
+      ((CountLevelTimeSeriesStatement) statement).setContains(isContains);
+      ((CountLevelTimeSeriesStatement) statement).setKey(key);
+      ((CountLevelTimeSeriesStatement) statement).setValue(value);
     }
-    statement.setValue(parseAttributeValue(attributeValueContext));
   }
 
   // Show Storage Group
@@ -594,6 +608,7 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
   // Count TimeSeries ========================================================================
   @Override
   public Statement visitCountTimeseries(CountTimeseriesContext ctx) {
+    Statement statement;
     PartialPath path;
     if (ctx.prefixPath() != null) {
       path = parsePrefixPath(ctx.prefixPath());
@@ -602,9 +617,14 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
     }
     if (ctx.INTEGER_LITERAL() != null) {
       int level = Integer.parseInt(ctx.INTEGER_LITERAL().getText());
-      return new CountLevelTimeSeriesStatement(path, level);
+      statement = new CountLevelTimeSeriesStatement(path, level);
+    } else {
+      statement = new CountTimeSeriesStatement(path);
+    }
+    if (ctx.tagWhereClause() != null) {
+      parseTagWhereClause(ctx.tagWhereClause(), statement);
     }
-    return new CountTimeSeriesStatement(path);
+    return statement;
   }
 
   // Count Nodes ========================================================================
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LocalExecutionPlanner.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LocalExecutionPlanner.java
index 51d2760e72..33eb8f249c 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LocalExecutionPlanner.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LocalExecutionPlanner.java
@@ -513,7 +513,13 @@ public class LocalExecutionPlanner {
               TimeSeriesCountOperator.class.getSimpleName());
       context.getTimeSliceAllocator().recordExecutionWeight(operatorContext, 1);
       return new TimeSeriesCountOperator(
-          node.getPlanNodeId(), operatorContext, node.getPath(), node.isPrefixPath());
+          node.getPlanNodeId(),
+          operatorContext,
+          node.getPath(),
+          node.isPrefixPath(),
+          node.getKey(),
+          node.getValue(),
+          node.isContains());
     }
 
     @Override
@@ -530,7 +536,10 @@ public class LocalExecutionPlanner {
           operatorContext,
           node.getPath(),
           node.isPrefixPath(),
-          node.getLevel());
+          node.getLevel(),
+          node.getKey(),
+          node.getValue(),
+          node.isContains());
     }
 
     @Override
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java
index 53cd1b10bc..eec1c6c026 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java
@@ -762,17 +762,30 @@ public class LogicalPlanBuilder {
     return this;
   }
 
-  public LogicalPlanBuilder planTimeSeriesCountSource(PartialPath partialPath, boolean prefixPath) {
+  public LogicalPlanBuilder planTimeSeriesCountSource(
+      PartialPath partialPath, boolean prefixPath, String key, String value, boolean isContains) {
     this.root =
-        new TimeSeriesCountNode(context.getQueryId().genPlanNodeId(), partialPath, prefixPath);
+        new TimeSeriesCountNode(
+            context.getQueryId().genPlanNodeId(), partialPath, prefixPath, key, value, isContains);
     return this;
   }
 
   public LogicalPlanBuilder planLevelTimeSeriesCountSource(
-      PartialPath partialPath, boolean prefixPath, int level) {
+      PartialPath partialPath,
+      boolean prefixPath,
+      int level,
+      String key,
+      String value,
+      boolean isContains) {
     this.root =
         new LevelTimeSeriesCountNode(
-            context.getQueryId().genPlanNodeId(), partialPath, prefixPath, level);
+            context.getQueryId().genPlanNodeId(),
+            partialPath,
+            prefixPath,
+            level,
+            key,
+            value,
+            isContains);
     return this;
   }
 
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
index 06bcce4417..0c9922a0f5 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
@@ -538,7 +538,11 @@ public class LogicalPlanVisitor extends StatementVisitor<PlanNode, MPPQueryConte
     LogicalPlanBuilder planBuilder = new LogicalPlanBuilder(context);
     return planBuilder
         .planTimeSeriesCountSource(
-            countTimeSeriesStatement.getPartialPath(), countTimeSeriesStatement.isPrefixPath())
+            countTimeSeriesStatement.getPartialPath(),
+            countTimeSeriesStatement.isPrefixPath(),
+            countTimeSeriesStatement.getKey(),
+            countTimeSeriesStatement.getValue(),
+            countTimeSeriesStatement.isContains())
         .planCountMerge()
         .getRoot();
   }
@@ -551,7 +555,10 @@ public class LogicalPlanVisitor extends StatementVisitor<PlanNode, MPPQueryConte
         .planLevelTimeSeriesCountSource(
             countLevelTimeSeriesStatement.getPartialPath(),
             countLevelTimeSeriesStatement.isPrefixPath(),
-            countLevelTimeSeriesStatement.getLevel())
+            countLevelTimeSeriesStatement.getLevel(),
+            countLevelTimeSeriesStatement.getKey(),
+            countLevelTimeSeriesStatement.getValue(),
+            countLevelTimeSeriesStatement.isContains())
         .planCountMerge()
         .getRoot();
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/read/LevelTimeSeriesCountNode.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/read/LevelTimeSeriesCountNode.java
index 5b5b9856d2..d450d765b7 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/read/LevelTimeSeriesCountNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/read/LevelTimeSeriesCountNode.java
@@ -35,20 +35,45 @@ import java.util.Objects;
 
 public class LevelTimeSeriesCountNode extends SchemaQueryScanNode {
   private final int level;
+  private final String key;
+  private final String value;
+  private final boolean isContains;
 
   public LevelTimeSeriesCountNode(
-      PlanNodeId id, PartialPath partialPath, boolean isPrefixPath, int level) {
+      PlanNodeId id,
+      PartialPath partialPath,
+      boolean isPrefixPath,
+      int level,
+      String key,
+      String value,
+      boolean isContains) {
     super(id, partialPath, isPrefixPath);
     this.level = level;
+    this.key = key;
+    this.value = value;
+    this.isContains = isContains;
   }
 
   public int getLevel() {
     return level;
   }
 
+  public String getKey() {
+    return key;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public boolean isContains() {
+    return isContains;
+  }
+
   @Override
   public PlanNode clone() {
-    return new LevelTimeSeriesCountNode(getPlanNodeId(), path, isPrefixPath, level);
+    return new LevelTimeSeriesCountNode(
+        getPlanNodeId(), path, isPrefixPath, level, key, value, isContains);
   }
 
   @Override
@@ -62,6 +87,9 @@ public class LevelTimeSeriesCountNode extends SchemaQueryScanNode {
     ReadWriteIOUtils.write(path.getFullPath(), byteBuffer);
     ReadWriteIOUtils.write(isPrefixPath, byteBuffer);
     ReadWriteIOUtils.write(level, byteBuffer);
+    ReadWriteIOUtils.write(key, byteBuffer);
+    ReadWriteIOUtils.write(value, byteBuffer);
+    ReadWriteIOUtils.write(isContains, byteBuffer);
   }
 
   @Override
@@ -70,6 +98,9 @@ public class LevelTimeSeriesCountNode extends SchemaQueryScanNode {
     ReadWriteIOUtils.write(path.getFullPath(), stream);
     ReadWriteIOUtils.write(isPrefixPath, stream);
     ReadWriteIOUtils.write(level, stream);
+    ReadWriteIOUtils.write(key, stream);
+    ReadWriteIOUtils.write(value, stream);
+    ReadWriteIOUtils.write(isContains, stream);
   }
 
   public static PlanNode deserialize(ByteBuffer buffer) {
@@ -82,8 +113,12 @@ public class LevelTimeSeriesCountNode extends SchemaQueryScanNode {
     }
     boolean isPrefixPath = ReadWriteIOUtils.readBool(buffer);
     int level = ReadWriteIOUtils.readInt(buffer);
+    String key = ReadWriteIOUtils.readString(buffer);
+    String value = ReadWriteIOUtils.readString(buffer);
+    boolean isContains = ReadWriteIOUtils.readBool(buffer);
     PlanNodeId planNodeId = PlanNodeId.deserialize(buffer);
-    return new LevelTimeSeriesCountNode(planNodeId, path, isPrefixPath, level);
+    return new LevelTimeSeriesCountNode(
+        planNodeId, path, isPrefixPath, level, key, value, isContains);
   }
 
   @Override
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/read/TimeSeriesCountNode.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/read/TimeSeriesCountNode.java
index b98f3447e5..34b5827412 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/read/TimeSeriesCountNode.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/plan/node/metedata/read/TimeSeriesCountNode.java
@@ -34,13 +34,38 @@ import java.util.List;
 
 public class TimeSeriesCountNode extends SchemaQueryScanNode {
 
-  public TimeSeriesCountNode(PlanNodeId id, PartialPath partialPath, boolean isPrefixPath) {
+  private final String key;
+  private final String value;
+  private final boolean isContains;
+
+  public TimeSeriesCountNode(
+      PlanNodeId id,
+      PartialPath partialPath,
+      boolean isPrefixPath,
+      String key,
+      String value,
+      boolean isContains) {
     super(id, partialPath, isPrefixPath);
+    this.key = key;
+    this.value = value;
+    this.isContains = isContains;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public boolean isContains() {
+    return isContains;
   }
 
   @Override
   public PlanNode clone() {
-    return new TimeSeriesCountNode(getPlanNodeId(), path, isPrefixPath);
+    return new TimeSeriesCountNode(getPlanNodeId(), path, isPrefixPath, key, value, isContains);
   }
 
   @Override
@@ -53,6 +78,9 @@ public class TimeSeriesCountNode extends SchemaQueryScanNode {
     PlanNodeType.TIME_SERIES_COUNT.serialize(byteBuffer);
     ReadWriteIOUtils.write(path.getFullPath(), byteBuffer);
     ReadWriteIOUtils.write(isPrefixPath, byteBuffer);
+    ReadWriteIOUtils.write(key, byteBuffer);
+    ReadWriteIOUtils.write(value, byteBuffer);
+    ReadWriteIOUtils.write(isContains, byteBuffer);
   }
 
   @Override
@@ -60,6 +88,9 @@ public class TimeSeriesCountNode extends SchemaQueryScanNode {
     PlanNodeType.TIME_SERIES_COUNT.serialize(stream);
     ReadWriteIOUtils.write(path.getFullPath(), stream);
     ReadWriteIOUtils.write(isPrefixPath, stream);
+    ReadWriteIOUtils.write(key, stream);
+    ReadWriteIOUtils.write(value, stream);
+    ReadWriteIOUtils.write(isContains, stream);
   }
 
   public static PlanNode deserialize(ByteBuffer buffer) {
@@ -71,7 +102,10 @@ public class TimeSeriesCountNode extends SchemaQueryScanNode {
       throw new IllegalArgumentException("Cannot deserialize DevicesSchemaScanNode", e);
     }
     boolean isPrefixPath = ReadWriteIOUtils.readBool(buffer);
+    String key = ReadWriteIOUtils.readString(buffer);
+    String value = ReadWriteIOUtils.readString(buffer);
+    boolean isContains = ReadWriteIOUtils.readBool(buffer);
     PlanNodeId planNodeId = PlanNodeId.deserialize(buffer);
-    return new TimeSeriesCountNode(planNodeId, path, isPrefixPath);
+    return new TimeSeriesCountNode(planNodeId, path, isPrefixPath, key, value, isContains);
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/CountLevelTimeSeriesStatement.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/CountLevelTimeSeriesStatement.java
index 30e8678543..c48b32fed1 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/CountLevelTimeSeriesStatement.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/CountLevelTimeSeriesStatement.java
@@ -24,6 +24,10 @@ import org.apache.iotdb.db.mpp.plan.statement.StatementVisitor;
 
 public class CountLevelTimeSeriesStatement extends CountStatement {
   private int level;
+  private String key;
+  private String value;
+
+  private boolean isContains;
 
   public CountLevelTimeSeriesStatement(PartialPath partialPath, int level) {
     super(partialPath);
@@ -34,6 +38,34 @@ public class CountLevelTimeSeriesStatement extends CountStatement {
     return level;
   }
 
+  public void setLevel(int level) {
+    this.level = level;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public void setKey(String key) {
+    this.key = key;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public void setValue(String value) {
+    this.value = value;
+  }
+
+  public boolean isContains() {
+    return isContains;
+  }
+
+  public void setContains(boolean contains) {
+    isContains = contains;
+  }
+
   @Override
   public <R, C> R accept(StatementVisitor<R, C> visitor, C context) {
     return visitor.visitCountLevelTimeSeries(this, context);
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/CountTimeSeriesStatement.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/CountTimeSeriesStatement.java
index 5ba529bf82..bc643daab9 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/CountTimeSeriesStatement.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/metadata/CountTimeSeriesStatement.java
@@ -23,10 +23,40 @@ import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.db.mpp.plan.statement.StatementVisitor;
 
 public class CountTimeSeriesStatement extends CountStatement {
+
+  private String key;
+  private String value;
+
+  private boolean isContains;
+
   public CountTimeSeriesStatement(PartialPath partialPath) {
     super(partialPath);
   }
 
+  public String getKey() {
+    return key;
+  }
+
+  public void setKey(String key) {
+    this.key = key;
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  public void setValue(String value) {
+    this.value = value;
+  }
+
+  public boolean isContains() {
+    return isContains;
+  }
+
+  public void setContains(boolean contains) {
+    isContains = contains;
+  }
+
   @Override
   public <R, C> R accept(StatementVisitor<R, C> visitor, C context) {
     return visitor.visitCountTimeSeries(this, context);
diff --git a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
index af5b8ec2e8..650095f01e 100644
--- a/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java
@@ -1057,8 +1057,8 @@ public class IoTDBSqlVisitor extends IoTDBSqlParserBaseVisitor<Operator> {
               new PartialPath(SQLConstant.getSingleRootArray()),
               orderByHeat);
     }
-    if (ctx.showWhereClause() != null) {
-      parseShowWhereClause(ctx.showWhereClause(), showTimeSeriesOperator);
+    if (ctx.tagWhereClause() != null) {
+      parseShowWhereClause(ctx.tagWhereClause(), showTimeSeriesOperator);
     }
     if (ctx.limitClause() != null) {
       parseLimitClause(ctx.limitClause(), showTimeSeriesOperator);
@@ -1067,7 +1067,7 @@ public class IoTDBSqlVisitor extends IoTDBSqlParserBaseVisitor<Operator> {
   }
 
   private void parseShowWhereClause(
-      IoTDBSqlParser.ShowWhereClauseContext ctx, ShowTimeSeriesOperator operator) {
+      IoTDBSqlParser.TagWhereClauseContext ctx, ShowTimeSeriesOperator operator) {
     IoTDBSqlParser.AttributeValueContext attributeValueContext;
     if (ctx.containsExpression() != null) {
       operator.setContains(true);
diff --git a/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/schema/CountMergeOperatorTest.java b/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/schema/CountMergeOperatorTest.java
index 8e154eddda..9264d38a9a 100644
--- a/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/schema/CountMergeOperatorTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/schema/CountMergeOperatorTest.java
@@ -100,7 +100,13 @@ public class CountMergeOperatorTest {
           .setDriverContext(new SchemaDriverContext(fragmentInstanceContext, schemaRegion));
       TimeSeriesCountOperator timeSeriesCountOperator =
           new TimeSeriesCountOperator(
-              planNodeId, fragmentInstanceContext.getOperatorContexts().get(0), partialPath, true);
+              planNodeId,
+              fragmentInstanceContext.getOperatorContexts().get(0),
+              partialPath,
+              true,
+              null,
+              null,
+              false);
       TsBlock tsBlock = null;
       while (timeSeriesCountOperator.hasNext()) {
         tsBlock = timeSeriesCountOperator.next();
@@ -112,6 +118,9 @@ public class CountMergeOperatorTest {
               planNodeId,
               fragmentInstanceContext.getOperatorContexts().get(0),
               new PartialPath(COUNT_MERGE_OPERATOR_TEST_SG + ".device1.*"),
+              false,
+              null,
+              null,
               false);
       tsBlock = timeSeriesCountOperator2.next();
       assertFalse(timeSeriesCountOperator2.hasNext());
@@ -155,14 +164,20 @@ public class CountMergeOperatorTest {
               fragmentInstanceContext.getOperatorContexts().get(0),
               new PartialPath(COUNT_MERGE_OPERATOR_TEST_SG),
               true,
-              2);
+              2,
+              null,
+              null,
+              false);
       LevelTimeSeriesCountOperator timeSeriesCountOperator2 =
           new LevelTimeSeriesCountOperator(
               planNodeId,
               fragmentInstanceContext.getOperatorContexts().get(0),
               new PartialPath(COUNT_MERGE_OPERATOR_TEST_SG + ".device2"),
               true,
-              2);
+              2,
+              null,
+              null,
+              false);
       CountMergeOperator countMergeOperator =
           new CountMergeOperator(
               planNodeId,
diff --git a/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/schema/SchemaCountOperatorTest.java b/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/schema/SchemaCountOperatorTest.java
index d714401d99..f664633ba7 100644
--- a/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/schema/SchemaCountOperatorTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/schema/SchemaCountOperatorTest.java
@@ -140,7 +140,13 @@ public class SchemaCountOperatorTest {
           .setDriverContext(new SchemaDriverContext(fragmentInstanceContext, schemaRegion));
       TimeSeriesCountOperator timeSeriesCountOperator =
           new TimeSeriesCountOperator(
-              planNodeId, fragmentInstanceContext.getOperatorContexts().get(0), partialPath, true);
+              planNodeId,
+              fragmentInstanceContext.getOperatorContexts().get(0),
+              partialPath,
+              true,
+              null,
+              null,
+              false);
       TsBlock tsBlock = null;
       while (timeSeriesCountOperator.hasNext()) {
         tsBlock = timeSeriesCountOperator.next();
@@ -152,6 +158,9 @@ public class SchemaCountOperatorTest {
               planNodeId,
               fragmentInstanceContext.getOperatorContexts().get(0),
               new PartialPath(SCHEMA_COUNT_OPERATOR_TEST_SG + ".device1.*"),
+              false,
+              null,
+              null,
               false);
       tsBlock = timeSeriesCountOperator2.next();
       assertFalse(timeSeriesCountOperator2.hasNext());
@@ -195,7 +204,10 @@ public class SchemaCountOperatorTest {
               fragmentInstanceContext.getOperatorContexts().get(0),
               partialPath,
               true,
-              2);
+              2,
+              null,
+              null,
+              false);
       TsBlock tsBlock = null;
       while (timeSeriesCountOperator.hasNext()) {
         tsBlock = timeSeriesCountOperator.next();
@@ -215,7 +227,10 @@ public class SchemaCountOperatorTest {
               fragmentInstanceContext.getOperatorContexts().get(0),
               partialPath,
               true,
-              1);
+              1,
+              null,
+              null,
+              false);
       while (timeSeriesCountOperator2.hasNext()) {
         tsBlock = timeSeriesCountOperator2.next();
       }
diff --git a/server/src/test/java/org/apache/iotdb/db/mpp/plan/plan/node/metadata/read/SchemaCountNodeSerdeTest.java b/server/src/test/java/org/apache/iotdb/db/mpp/plan/plan/node/metadata/read/SchemaCountNodeSerdeTest.java
index 532fe6fdf4..d79d959850 100644
--- a/server/src/test/java/org/apache/iotdb/db/mpp/plan/plan/node/metadata/read/SchemaCountNodeSerdeTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/mpp/plan/plan/node/metadata/read/SchemaCountNodeSerdeTest.java
@@ -70,7 +70,13 @@ public class SchemaCountNodeSerdeTest {
     ExchangeNode exchangeNode = new ExchangeNode(new PlanNodeId("exchange"));
     LevelTimeSeriesCountNode levelTimeSeriesCountNode =
         new LevelTimeSeriesCountNode(
-            new PlanNodeId("timeseriesCount"), new PartialPath("root.sg.device0"), true, 10);
+            new PlanNodeId("timeseriesCount"),
+            new PartialPath("root.sg.device0"),
+            true,
+            10,
+            null,
+            null,
+            false);
     FragmentSinkNode fragmentSinkNode = new FragmentSinkNode(new PlanNodeId("fragmentSink"));
     fragmentSinkNode.addChild(levelTimeSeriesCountNode);
     fragmentSinkNode.setDownStream(