[IOTDB-3695] Implememtation of having clause (#6840)

diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
index 9485865..12f4941 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
@@ -84,6 +84,7 @@
     | GLOBAL
     | GRANT
     | GROUP
+    | HAVING
     | INDEX
     | INFO
     | INSERT
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 127e497..3cf6157 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
@@ -384,9 +384,9 @@
 specialClause
     : specialLimit #specialLimitStatement
     | orderByClause specialLimit? #orderByTimeStatement
-    | groupByTimeClause orderByClause? specialLimit? #groupByTimeStatement
-    | groupByFillClause orderByClause? specialLimit? #groupByFillStatement
-    | groupByLevelClause orderByClause? specialLimit? #groupByLevelStatement
+    | groupByTimeClause havingClause? orderByClause? specialLimit? #groupByTimeStatement
+    | groupByFillClause havingClause? orderByClause? specialLimit? #groupByFillStatement
+    | groupByLevelClause havingClause? orderByClause? specialLimit? #groupByLevelStatement
     | fillClause orderByClause? specialLimit? #fillStatement
     ;
 
@@ -397,6 +397,10 @@
     | alignByDeviceClauseOrDisableAlign #alignByDeviceClauseOrDisableAlignStatement
     ;
 
+havingClause
+    : HAVING expression
+    ;
+
 alignByDeviceClauseOrDisableAlign
     : alignByDeviceClause
     | disableAlign
diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
index a87ede3..5ad902f 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4
@@ -251,6 +251,10 @@
     : G R O U P
     ;
 
+HAVING
+    : H A V I N G
+    ;
+
 INDEX
     : I N D E X
     ;
diff --git a/docs/UserGuide/Query-Data/Aggregate-Query.md b/docs/UserGuide/Query-Data/Aggregate-Query.md
index a55a145..3cc502b 100644
--- a/docs/UserGuide/Query-Data/Aggregate-Query.md
+++ b/docs/UserGuide/Query-Data/Aggregate-Query.md
@@ -452,4 +452,98 @@
 +-----------------------------+-------------------------+
 Total line number = 7
 It costs 0.004s
+```
+
+## Aggregate result filtering
+
+If you want to filter the results of aggregate queries, 
+you can use the `HAVING` clause after the `GROUP BY` clause.
+
+> NOTE:
+>
+> 1.The expression in HAVING clause must consist of aggregate values; the original sequence cannot appear alone.
+> The following usages are incorrect:
+> ```sql
+> select count(s1) from root.** group by ([1,3),1ms) having sum(s1) > s1
+> select count(s1) from root.** group by ([1,3),1ms) having s1 > 1
+> ```
+> 2.When filtering the `GROUP BY LEVEL` result, the PATH in `SELECT` and `HAVING` can only have one node.
+> The following usages are incorrect:
+> ```sql
+> select count(s1) from root.** group by ([1,3),1ms), level=1 having sum(d1.s1) > 1
+> select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having sum(s1) > 1
+> ```
+
+Here are a few examples of using the 'HAVING' clause to filter aggregate results.
+
+Aggregation result 1:
+
+```
++-----------------------------+---------------------+---------------------+
+|                         Time|count(root.test.*.s1)|count(root.test.*.s2)|
++-----------------------------+---------------------+---------------------+
+|1970-01-01T08:00:00.001+08:00|                    4|                    4|
+|1970-01-01T08:00:00.003+08:00|                    1|                    0|
+|1970-01-01T08:00:00.005+08:00|                    2|                    4|
+|1970-01-01T08:00:00.007+08:00|                    3|                    2|
+|1970-01-01T08:00:00.009+08:00|                    4|                    4|
++-----------------------------+---------------------+---------------------+
+```
+
+Aggregation result filtering query 1:
+
+```sql
+ select count(s1) from root.** group by ([1,11),2ms), level=1 having count(s2) > 1
+```
+
+Filtering result 1:
+
+```
++-----------------------------+---------------------+
+|                         Time|count(root.test.*.s1)|
++-----------------------------+---------------------+
+|1970-01-01T08:00:00.001+08:00|                    4|
+|1970-01-01T08:00:00.005+08:00|                    2|
+|1970-01-01T08:00:00.009+08:00|                    4|
++-----------------------------+---------------------+
+```
+
+Aggregation result 2:
+
+```
++-----------------------------+-------------+---------+---------+
+|                         Time|       Device|count(s1)|count(s2)|
++-----------------------------+-------------+---------+---------+
+|1970-01-01T08:00:00.001+08:00|root.test.sg1|        1|        2|
+|1970-01-01T08:00:00.003+08:00|root.test.sg1|        1|        0|
+|1970-01-01T08:00:00.005+08:00|root.test.sg1|        1|        2|
+|1970-01-01T08:00:00.007+08:00|root.test.sg1|        2|        1|
+|1970-01-01T08:00:00.009+08:00|root.test.sg1|        2|        2|
+|1970-01-01T08:00:00.001+08:00|root.test.sg2|        2|        2|
+|1970-01-01T08:00:00.003+08:00|root.test.sg2|        0|        0|
+|1970-01-01T08:00:00.005+08:00|root.test.sg2|        1|        2|
+|1970-01-01T08:00:00.007+08:00|root.test.sg2|        1|        1|
+|1970-01-01T08:00:00.009+08:00|root.test.sg2|        2|        2|
++-----------------------------+-------------+---------+---------+
+```
+
+Aggregation result filtering query 2:
+
+```sql
+ select count(s1), count(s2) from root.** group by ([1,11),2ms) having count(s2) > 1 align by device
+```
+
+Filtering result 2:
+
+```
++-----------------------------+-------------+---------+---------+
+|                         Time|       Device|count(s1)|count(s2)|
++-----------------------------+-------------+---------+---------+
+|1970-01-01T08:00:00.001+08:00|root.test.sg1|        1|        2|
+|1970-01-01T08:00:00.005+08:00|root.test.sg1|        1|        2|
+|1970-01-01T08:00:00.009+08:00|root.test.sg1|        2|        2|
+|1970-01-01T08:00:00.001+08:00|root.test.sg2|        2|        2|
+|1970-01-01T08:00:00.005+08:00|root.test.sg2|        1|        2|
+|1970-01-01T08:00:00.009+08:00|root.test.sg2|        2|        2|
++-----------------------------+-------------+---------+---------+
 ```
\ No newline at end of file
diff --git a/docs/zh/UserGuide/Query-Data/Aggregate-Query.md b/docs/zh/UserGuide/Query-Data/Aggregate-Query.md
index 51950fd..12e34e0 100644
--- a/docs/zh/UserGuide/Query-Data/Aggregate-Query.md
+++ b/docs/zh/UserGuide/Query-Data/Aggregate-Query.md
@@ -446,4 +446,97 @@
 +-----------------------------+-------------------------+
 Total line number = 7
 It costs 0.004s
+```
+
+## 聚合结果过滤
+
+如果想对聚合查询的结果进行过滤,可以在`GROUP BY`子句之后使用`HAVING`子句
+
+> 注意:
+> 
+> 1.`HAVING`子句中的过滤条件必须由聚合值构成,原始序列不能单独出现。
+> 下列使用方式是不正确的:
+> ```sql
+> select count(s1) from root.** group by ([1,3),1ms) having sum(s1) > s1
+> select count(s1) from root.** group by ([1,3),1ms) having s1 > 1
+> ```
+> 2.对`GROUP BY LEVEL`结果进行过滤时,`SELECT`和`HAVING`中出现的PATH只能有一级。
+> 下列使用方式是不正确的:
+> ```sql
+> select count(s1) from root.** group by ([1,3),1ms), level=1 having sum(d1.s1) > 1
+> select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having sum(s1) > 1
+> ```
+
+以下为几个使用`HAVING`子句对聚合结果进行过滤对例子
+
+聚合结果1:
+
+```
++-----------------------------+---------------------+---------------------+
+|                         Time|count(root.test.*.s1)|count(root.test.*.s2)|
++-----------------------------+---------------------+---------------------+
+|1970-01-01T08:00:00.001+08:00|                    4|                    4|
+|1970-01-01T08:00:00.003+08:00|                    1|                    0|
+|1970-01-01T08:00:00.005+08:00|                    2|                    4|
+|1970-01-01T08:00:00.007+08:00|                    3|                    2|
+|1970-01-01T08:00:00.009+08:00|                    4|                    4|
++-----------------------------+---------------------+---------------------+
+```
+
+聚合结果过滤查询1:
+
+```sql
+ select count(s1) from root.** group by ([1,11),2ms), level=1 having count(s2) > 2
+```
+
+过滤结果1:
+
+```
++-----------------------------+---------------------+
+|                         Time|count(root.test.*.s1)|
++-----------------------------+---------------------+
+|1970-01-01T08:00:00.001+08:00|                    4|
+|1970-01-01T08:00:00.005+08:00|                    2|
+|1970-01-01T08:00:00.009+08:00|                    4|
++-----------------------------+---------------------+
+```
+
+聚合结果2:
+
+```
++-----------------------------+-------------+---------+---------+
+|                         Time|       Device|count(s1)|count(s2)|
++-----------------------------+-------------+---------+---------+
+|1970-01-01T08:00:00.001+08:00|root.test.sg1|        1|        2|
+|1970-01-01T08:00:00.003+08:00|root.test.sg1|        1|        0|
+|1970-01-01T08:00:00.005+08:00|root.test.sg1|        1|        2|
+|1970-01-01T08:00:00.007+08:00|root.test.sg1|        2|        1|
+|1970-01-01T08:00:00.009+08:00|root.test.sg1|        2|        2|
+|1970-01-01T08:00:00.001+08:00|root.test.sg2|        2|        2|
+|1970-01-01T08:00:00.003+08:00|root.test.sg2|        0|        0|
+|1970-01-01T08:00:00.005+08:00|root.test.sg2|        1|        2|
+|1970-01-01T08:00:00.007+08:00|root.test.sg2|        1|        1|
+|1970-01-01T08:00:00.009+08:00|root.test.sg2|        2|        2|
++-----------------------------+-------------+---------+---------+
+```
+
+聚合结果过滤查询2:
+
+```sql
+ select count(s1), count(s2) from root.** group by ([1,11),2ms) having count(s2) > 1 align by device
+```
+
+过滤结果2:
+
+```
++-----------------------------+-------------+---------+---------+
+|                         Time|       Device|count(s1)|count(s2)|
++-----------------------------+-------------+---------+---------+
+|1970-01-01T08:00:00.001+08:00|root.test.sg1|        1|        2|
+|1970-01-01T08:00:00.005+08:00|root.test.sg1|        1|        2|
+|1970-01-01T08:00:00.009+08:00|root.test.sg1|        2|        2|
+|1970-01-01T08:00:00.001+08:00|root.test.sg2|        2|        2|
+|1970-01-01T08:00:00.005+08:00|root.test.sg2|        1|        2|
+|1970-01-01T08:00:00.009+08:00|root.test.sg2|        2|        2|
++-----------------------------+-------------+---------+---------+
 ```
\ No newline at end of file
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBHavingIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBHavingIT.java
new file mode 100644
index 0000000..aff5e44
--- /dev/null
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/groupby/IoTDBHavingIT.java
@@ -0,0 +1,247 @@
+/*
+ * 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.it.groupby;
+
+import org.apache.iotdb.it.env.ConfigFactory;
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.ClusterIT;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.assertTestFail;
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareData;
+import static org.apache.iotdb.db.it.utils.TestUtils.resultSetEqualTest;
+import static org.apache.iotdb.db.it.utils.TestUtils.resultSetEqualWithDescOrderTest;
+import static org.apache.iotdb.itbase.constant.TestConstant.TIMESTAMP_STR;
+import static org.apache.iotdb.itbase.constant.TestConstant.count;
+import static org.apache.iotdb.itbase.constant.TestConstant.sum;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({ClusterIT.class}) // TODO After old StandAlone remove
+public class IoTDBHavingIT {
+  private static final String[] SQLs =
+      new String[] {
+        "SET STORAGE GROUP TO root.test",
+        "CREATE TIMESERIES root.test.sg1.s1 WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.test.sg1.s2 WITH DATATYPE=INT32, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.test.sg1.s3 WITH DATATYPE=DOUBLE, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.test.sg1.s4 WITH DATATYPE=INT32, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.test.sg2.s1 WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.test.sg2.s2 WITH DATATYPE=INT32, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.test.sg2.s3 WITH DATATYPE=DOUBLE, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.test.sg2.s4 WITH DATATYPE=INT32, ENCODING=PLAIN",
+        "CREATE ALIGNED TIMESERIES root.test.sg3(s5 INT32, s6 BOOLEAN, s7 DOUBLE, s8 INT32)",
+        "CREATE TIMESERIES root.test.sg5.s1 WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.test.sg5.s9 WITH DATATYPE=INT32, ENCODING=PLAIN",
+        "INSERT INTO root.test.sg1(timestamp,s1,s2, s3, s4) " + "values(1, true, 1, 1.0, 1)",
+        "INSERT INTO root.test.sg2(timestamp,s1,s2, s3, s4) " + "values(1, false, 1, 1.0, 1)",
+        "INSERT INTO root.test.sg1(timestamp, s2) " + "values(2, 2)",
+        "INSERT INTO root.test.sg1(timestamp, s3) " + "values(2, 2.0)",
+        "INSERT INTO root.test.sg1(timestamp, s4) " + "values(2, 2)",
+        "INSERT INTO root.test.sg2(timestamp,s1,s2, s3, s4) " + "values(2, true, 2, 2.0, 2)",
+        "flush",
+        "INSERT INTO root.test.sg1(timestamp, s1) " + "values(3, false)",
+        "INSERT INTO root.test.sg1(timestamp, s2) " + "values(5, 5)",
+        "INSERT INTO root.test.sg1(timestamp, s3) " + "values(5, 5.0)",
+        "INSERT INTO root.test.sg1(timestamp, s4) " + "values(5, 5)",
+        "INSERT INTO root.test.sg2(timestamp, s2) " + "values(5, 5)",
+        "INSERT INTO root.test.sg2(timestamp, s3) " + "values(5, 5.0)",
+        "INSERT INTO root.test.sg2(timestamp, s4) " + "values(5, 5)",
+        "flush",
+        "INSERT INTO root.test.sg1(timestamp,s1,s2, s3, s4) " + "values(6, true, 6, 6.0, 6)",
+        "INSERT INTO root.test.sg2(timestamp,s1,s2, s3, s4) " + "values(6, true, 6, 6.0, 6)",
+        "INSERT INTO root.test.sg1(timestamp, s1) " + "values(7, true)",
+        "INSERT INTO root.test.sg1(timestamp, s3) " + "values(7, 7.0)",
+        "INSERT INTO root.test.sg2(timestamp,s1,s2, s3) " + "values(7, true, 7, 7.0)",
+        "flush",
+        "INSERT INTO root.test.sg1(timestamp, s1) " + "values(8, true)",
+        "INSERT INTO root.test.sg1(timestamp, s2) " + "values(8, 8)",
+        "INSERT INTO root.test.sg1(timestamp, s3) " + "values(8, 8.0)",
+        "INSERT INTO root.test.sg2(timestamp, s3) " + "values(8, 8.0)",
+        "INSERT INTO root.test.sg1(timestamp,s1,s2, s3, s4) " + "values(9, false, 9, 9.0, 9)",
+        "INSERT INTO root.test.sg2(timestamp, s1) " + "values(9, true)",
+        "flush",
+        "INSERT INTO root.test.sg2(timestamp, s2) " + "values(9, 9)",
+        "INSERT INTO root.test.sg2(timestamp, s4) " + "values(9, 9)",
+        "INSERT INTO root.test.sg1(timestamp,s1,s2, s3, s4) " + "values(10, true, 10, 10.0, 10)",
+        "INSERT INTO root.test.sg2(timestamp,s1,s2, s3, s4) " + "values(10, true, 10, 10.0, 10)",
+        "flush",
+        "INSERT INTO root.test.sg3(time, s5, s6, s7, s8) aligned values(1, 1, true, 1.0, 1)",
+        "INSERT INTO root.test.sg3(time, s6, s7, s8) aligned values(2, false, 2.0, 2)",
+        "INSERT INTO root.test.sg3(time, s5, s7, s8) aligned values(3, 3, 3.0, 3)",
+        "INSERT INTO root.test.sg3(time, s5) aligned values(5, 5)",
+        "INSERT INTO root.test.sg3(time, s5, s8) aligned values(6, 6, 6)",
+        "INSERT INTO root.test.sg3(time, s6) aligned values(7, true)",
+        "INSERT INTO root.test.sg3(time, s5, s7) aligned values(8, 8, 8.0)",
+        "INSERT INTO root.test.sg3(time, s5, s7, s8) aligned values(9, 9, 9.0, 9)",
+        "INSERT INTO root.test.sg3(time, s7, s8) aligned values(10, 10.0, 10)",
+        "INSERT INTO root.test.sg5(timestamp, s1, s9) " + "values(1, true, 1)",
+        "flush",
+      };
+
+  private static long prevPartitionInterval;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    prevPartitionInterval = ConfigFactory.getConfig().getPartitionInterval();
+    ConfigFactory.getConfig().setPartitionInterval(1000);
+    EnvFactory.getEnv().initBeforeClass();
+    prepareData(SQLs);
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvFactory.getEnv().cleanAfterClass();
+    ConfigFactory.getConfig().setPartitionInterval(prevPartitionInterval);
+  }
+
+  @Test
+  public void testUnsatisfiedRuleQuery() {
+    assertTestFail(
+        "select count(s1) from root.** group by ([1,3),1ms), level=1 having sum(d1.s1) > 1",
+        "416: When Having used with GroupByLevel: the suffix paths can only be measurement or one-level wildcard");
+
+    assertTestFail(
+        "select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having sum(s1) > 1",
+        "416: When Having used with GroupByLevel: the suffix paths can only be measurement or one-level wildcard");
+
+    assertTestFail(
+        "select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having sum(s1) + s1 > 1",
+        "416: Raw data and aggregation result hybrid calculation is not supported");
+
+    assertTestFail(
+        "select count(d1.s1) from root.** group by ([1,3),1ms), level=1 having s1 + 1 > 1",
+        "416: Expression of HAVING clause must to be an Aggregation");
+  }
+
+  @Test
+  public void groupByTimeWithHavingTest() {
+    String[] expectedHeader = new String[] {TIMESTAMP_STR, count("root.test.sg1.s1")};
+    String[] retArray = new String[] {"1,1,", "5,1,", "9,2,"};
+    resultSetEqualWithDescOrderTest(
+        "select count(sg1.s1) from root.** "
+            + "GROUP BY ([1,11), 2ms) "
+            + "Having count(sg1.s2) > 1",
+        expectedHeader,
+        retArray);
+
+    resultSetEqualWithDescOrderTest(
+        "select count(sg1.s1) from root.**"
+            + "GROUP BY ([1,11), 2ms) "
+            + "Having count(sg1.s2) > 2",
+        expectedHeader,
+        new String[] {});
+  }
+
+  @Test
+  public void groupByTimeAlignByDeviceWithHavingTest() {
+    String[] expectedHeader = new String[] {TIMESTAMP_STR, "Device", count("s1"), count("s2")};
+    String[] retArray =
+        new String[] {
+          "1,root.test.sg1,1,2,",
+          "5,root.test.sg1,1,2,",
+          "9,root.test.sg1,2,2,",
+          "1,root.test.sg2,2,2,",
+          "5,root.test.sg2,1,2,",
+          "9,root.test.sg2,2,2,",
+        };
+    resultSetEqualTest(
+        "select count(s1), count(s2) from root.** "
+            + "GROUP BY ([1,11), 2ms) "
+            + "Having count(s2) > 1 "
+            + "Align by device",
+        expectedHeader,
+        retArray);
+
+    resultSetEqualTest(
+        "select count(s1), count(s2) from root.** "
+            + "GROUP BY ([1,11), 2ms) "
+            + "Having count(s2) > 2 "
+            + "Align by device",
+        expectedHeader,
+        new String[] {});
+  }
+
+  @Test
+  public void groupByLevelWithHavingTest() {
+    String[] expectedHeader =
+        new String[] {count("root.test.*.s1"), count("root.test.*.s2"), sum("root.test.*.s3")};
+    String[] retArray = new String[] {"14,14,87.0,"};
+    resultSetEqualWithDescOrderTest(
+        "select count(s1), count(s2), sum(s3) from root.** "
+            + "GROUP BY level=1 "
+            + "Having sum(s3) > 80",
+        expectedHeader,
+        retArray);
+
+    resultSetEqualWithDescOrderTest(
+        "select count(s1), count(s2), sum(s3) from root.** "
+            + "GROUP BY level=1 "
+            + "Having sum(s3) > 90",
+        expectedHeader,
+        new String[] {});
+  }
+
+  @Test
+  public void groupByTimeLevelWithHavingTest() {
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR, count("root.test.*.s1"), count("root.test.*.s2"),
+        };
+    String[] retArray =
+        new String[] {
+          "1,4,4,", "5,2,4,", "9,4,4,",
+        };
+    resultSetEqualWithDescOrderTest(
+        "select count(s1), count(s2) from root.** "
+            + "GROUP BY ([1,11),2ms), level=1 "
+            + "Having count(s2)>2",
+        expectedHeader,
+        retArray);
+
+    resultSetEqualWithDescOrderTest(
+        "select count(s1), count(s2) from root.** "
+            + "GROUP BY ([1,11),2ms), level=1 "
+            + "Having count(s2)>4",
+        expectedHeader,
+        new String[] {});
+  }
+
+  @Test
+  public void groupByTimeLevelWithHavingLimitTest() {
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR, count("root.test.*.s1"), count("root.test.*.s2"),
+        };
+    String[] retArray = new String[] {"5,2,4,"};
+    resultSetEqualTest(
+        "select count(s1), count(s2) from root.** "
+            + "GROUP BY ([1,11),2ms), level=1 "
+            + "Having count(s2) > 1 "
+            + "Limit 1 offset 1",
+        expectedHeader,
+        retArray);
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
index 77799b4..6f9a65e 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
@@ -81,6 +81,8 @@
 
   private Expression queryFilter;
 
+  private Expression havingExpression;
+
   // map from grouped path name to list of input aggregation in `GROUP BY LEVEL` clause
   private Map<Expression, Set<Expression>> groupByLevelExpressions;
 
@@ -108,6 +110,9 @@
   // map from device name to query filter under this device
   private Map<String, Expression> deviceToQueryFilter;
 
+  // map from device name to havingExpression under this device
+  private Map<String, Expression> deviceToHavingExpression;
+
   // e.g. [s1,s2,s3] is query, but [s1, s3] exists in device1, then device1 -> [1, 3], s1 is 1 but
   // not 0 because device is the first column
   private Map<String, List<Integer>> deviceToMeasurementIndexesMap;
@@ -292,6 +297,22 @@
     return groupByTimeParameter;
   }
 
+  public Expression getHavingExpression() {
+    return havingExpression;
+  }
+
+  public void setHavingExpression(Expression havingExpression) {
+    this.havingExpression = havingExpression;
+  }
+
+  public Map<String, Expression> getDeviceToHavingExpression() {
+    return deviceToHavingExpression;
+  }
+
+  public void setDeviceToHavingExpression(Map<String, Expression> deviceTohavingExpression) {
+    this.deviceToHavingExpression = deviceTohavingExpression;
+  }
+
   public void setGroupByTimeParameter(GroupByTimeParameter groupByTimeParameter) {
     this.groupByTimeParameter = groupByTimeParameter;
   }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
index 026820f..4927629 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
@@ -224,6 +224,7 @@
       List<Pair<Expression, String>> outputExpressions;
       if (queryStatement.isAlignByDevice()) {
         Map<String, Set<Expression>> deviceToTransformExpressions = new HashMap<>();
+        Map<String, Set<Expression>> deviceToTransformExpressionsInHaving = new HashMap<>();
 
         // all selected device
         Set<PartialPath> deviceList = analyzeFrom(queryStatement, schemaTree);
@@ -237,6 +238,35 @@
                 deviceToTransformExpressions,
                 deviceToMeasurementsMap);
 
+        if (queryStatement.hasHaving()) {
+          List<PartialPath> measurementNotExistDevices = new ArrayList<>();
+          for (PartialPath device : deviceList) {
+            try {
+              deviceToTransformExpressionsInHaving.put(
+                  device.toString(),
+                  ExpressionAnalyzer.removeWildcardInFilterByDevice(
+                          queryStatement.getHavingCondition().getPredicate(),
+                          device,
+                          schemaTree,
+                          typeProvider,
+                          false)
+                      .stream()
+                      .collect(Collectors.toSet()));
+            } catch (SemanticException e) {
+              if (e instanceof MeasurementNotExistException) {
+                logger.warn(e.getMessage());
+                measurementNotExistDevices.add(device);
+                continue;
+              }
+              throw e;
+            }
+          }
+          for (PartialPath measurementNotExistDevice : measurementNotExistDevices) {
+            deviceList.remove(measurementNotExistDevice);
+            deviceToTransformExpressions.remove(measurementNotExistDevice.getFullPath());
+          }
+        }
+
         Map<String, List<Integer>> deviceToMeasurementIndexesMap = new HashMap<>();
         List<String> allMeasurements =
             outputExpressions.stream()
@@ -263,8 +293,12 @@
 
         Map<String, Set<Expression>> deviceToAggregationExpressions = new HashMap<>();
         Map<String, Set<Expression>> deviceToAggregationTransformExpressions = new HashMap<>();
+        Map<String, Expression> deviceToHavingExpression =
+            new HashMap<>(); // store filter of every device
         for (String deviceName : deviceToTransformExpressions.keySet()) {
           Set<Expression> transformExpressions = deviceToTransformExpressions.get(deviceName);
+          Set<Expression> transformExpressionsInHaving =
+              deviceToTransformExpressionsInHaving.get(deviceName);
           Set<Expression> aggregationExpressions = new LinkedHashSet<>();
           Set<Expression> aggregationTransformExpressions = new LinkedHashSet<>();
 
@@ -275,6 +309,25 @@
             isHasRawDataInputAggregation =
                 analyzeAggregation(
                     transformExpressions, aggregationExpressions, aggregationTransformExpressions);
+
+            if (queryStatement.hasHaving() && transformExpressionsInHaving != null) {
+              List<Expression> aggregationExpressionsInHaving = new ArrayList<>();
+              isHasRawDataInputAggregation |=
+                  analyzeAggregationInHaving(
+                      transformExpressionsInHaving,
+                      aggregationExpressionsInHaving,
+                      aggregationExpressions,
+                      aggregationTransformExpressions);
+
+              Expression havingExpression;
+              havingExpression =
+                  analyzeHavingSplitByDevice(
+                      transformExpressionsInHaving); // construct Filter from Having
+
+              havingExpression.inferTypes(typeProvider);
+              deviceToHavingExpression.put(deviceName, havingExpression);
+            }
+
             deviceToAggregationExpressions.put(deviceName, aggregationExpressions);
             deviceToAggregationTransformExpressions.put(
                 deviceName, aggregationTransformExpressions);
@@ -291,12 +344,23 @@
                 deviceToSourceExpressions.computeIfAbsent(deviceName, key -> new LinkedHashSet<>()),
                 isRawDataSource);
           }
+
+          if (queryStatement.hasHaving() && transformExpressionsInHaving != null) {
+            for (Expression expression : transformExpressionsInHaving) {
+              updateSource(
+                  expression,
+                  deviceToSourceExpressions.computeIfAbsent(
+                      deviceName, key -> new LinkedHashSet<>()),
+                  isRawDataSource);
+            }
+          }
           deviceToIsRawDataSource.put(deviceName, isRawDataSource);
         }
         analysis.setDeviceToAggregationExpressions(deviceToAggregationExpressions);
         analysis.setDeviceToAggregationTransformExpressions(
             deviceToAggregationTransformExpressions);
         analysis.setDeviceToIsRawDataSource(deviceToIsRawDataSource);
+        analysis.setDeviceToHavingExpression(deviceToHavingExpression);
 
         if (queryStatement.getWhereCondition() != null) {
           Map<String, Expression> deviceToQueryFilter = new HashMap<>();
@@ -334,6 +398,20 @@
                 .map(Pair::getLeft)
                 .collect(Collectors.toCollection(LinkedHashSet::new));
 
+        // get removeWildcard Expressions in Having
+        // used to analyzeAggregation in Having expression and updateSource
+        Set<Expression> transformExpressionsInHaving =
+            queryStatement.hasHaving()
+                ? ExpressionAnalyzer.removeWildcardInFilter(
+                        queryStatement.getHavingCondition().getPredicate(),
+                        queryStatement.getFromComponent().getPrefixPaths(),
+                        schemaTree,
+                        typeProvider,
+                        false)
+                    .stream()
+                    .collect(Collectors.toSet())
+                : null;
+
         if (queryStatement.isGroupByLevel()) {
           // map from grouped expression to set of input expressions
           Map<Expression, Expression> rawPathToGroupedPathMap = new HashMap<>();
@@ -350,9 +428,28 @@
         if (queryStatement.isAggregationQuery()) {
           Set<Expression> aggregationExpressions = new HashSet<>();
           Set<Expression> aggregationTransformExpressions = new HashSet<>();
+          List<Expression> aggregationExpressionsInHaving =
+              new ArrayList<>(); // as input of GroupByLevelController
           isHasRawDataInputAggregation =
               analyzeAggregation(
                   transformExpressions, aggregationExpressions, aggregationTransformExpressions);
+          if (queryStatement.hasHaving()) {
+            isHasRawDataInputAggregation |=
+                analyzeAggregationInHaving(
+                    transformExpressionsInHaving,
+                    aggregationExpressionsInHaving,
+                    aggregationExpressions,
+                    aggregationTransformExpressions);
+
+            Expression havingExpression = // construct Filter from Having
+                analyzeHaving(
+                    queryStatement,
+                    analysis.getGroupByLevelExpressions(),
+                    transformExpressionsInHaving,
+                    aggregationExpressionsInHaving);
+            havingExpression.inferTypes(typeProvider);
+            analysis.setHavingExpression(havingExpression);
+          }
           analysis.setAggregationExpressions(aggregationExpressions);
           analysis.setAggregationTransformExpressions(aggregationTransformExpressions);
         }
@@ -368,6 +465,12 @@
           updateSource(expression, sourceExpressions, isRawDataSource);
         }
 
+        if (queryStatement.hasHaving()) {
+          for (Expression expression : transformExpressionsInHaving) {
+            updateSource(expression, sourceExpressions, isRawDataSource);
+          }
+        }
+
         if (queryStatement.getWhereCondition() != null) {
           Expression queryFilter = analyzeWhere(queryStatement, schemaTree);
 
@@ -648,13 +751,40 @@
     return isHasRawDataInputAggregation;
   }
 
+  private boolean analyzeAggregationInHaving(
+      Set<Expression> expressions,
+      List<Expression> aggregationExpressionsInHaving,
+      Set<Expression> aggregationExpressions,
+      Set<Expression> aggregationTransformExpressions) {
+    // true if nested expressions and UDFs exist in aggregation function
+    // i.e. select sum(s1 + 1) from root.sg.d1 align by device
+    boolean isHasRawDataInputAggregation = false;
+    for (Expression expression : expressions) {
+      for (Expression aggregationExpression :
+          ExpressionAnalyzer.searchAggregationExpressions(expression)) {
+        aggregationExpressionsInHaving.add(aggregationExpression);
+        aggregationExpressions.add(aggregationExpression);
+        aggregationTransformExpressions.addAll(aggregationExpression.getExpressions());
+      }
+    }
+
+    for (Expression aggregationTransformExpression : aggregationTransformExpressions) {
+      if (ExpressionAnalyzer.checkIsNeedTransform(aggregationTransformExpression)) {
+        isHasRawDataInputAggregation = true;
+        break;
+      }
+    }
+    return isHasRawDataInputAggregation;
+  }
+
   private Expression analyzeWhere(QueryStatement queryStatement, ISchemaTree schemaTree) {
     List<Expression> rewrittenPredicates =
-        ExpressionAnalyzer.removeWildcardInQueryFilter(
+        ExpressionAnalyzer.removeWildcardInFilter(
             queryStatement.getWhereCondition().getPredicate(),
             queryStatement.getFromComponent().getPrefixPaths(),
             schemaTree,
-            typeProvider);
+            typeProvider,
+            true);
     return ExpressionUtils.constructQueryFilter(
         rewrittenPredicates.stream().distinct().collect(Collectors.toList()));
   }
@@ -662,15 +792,60 @@
   private Expression analyzeWhereSplitByDevice(
       QueryStatement queryStatement, PartialPath devicePath, ISchemaTree schemaTree) {
     List<Expression> rewrittenPredicates =
-        ExpressionAnalyzer.removeWildcardInQueryFilterByDevice(
+        ExpressionAnalyzer.removeWildcardInFilterByDevice(
             queryStatement.getWhereCondition().getPredicate(),
             devicePath,
             schemaTree,
-            typeProvider);
+            typeProvider,
+            true);
     return ExpressionUtils.constructQueryFilter(
         rewrittenPredicates.stream().distinct().collect(Collectors.toList()));
   }
 
+  private Expression analyzeHaving(
+      QueryStatement queryStatement,
+      Map<Expression, Set<Expression>> groupByLevelExpressions,
+      Set<Expression> transformExpressionsInHaving,
+      List<Expression> aggregationExpressionsInHaving) {
+
+    if (queryStatement.isGroupByLevel()) {
+      Map<Expression, Expression> RawPathToGroupedPathMapInHaving =
+          analyzeGroupByLevelInHaving(
+              queryStatement, aggregationExpressionsInHaving, groupByLevelExpressions);
+      List<Expression> convertedPredicates = new ArrayList<>();
+      for (Expression expression : transformExpressionsInHaving) {
+        convertedPredicates.add(
+            ExpressionAnalyzer.replaceRawPathWithGroupedPath(
+                expression, RawPathToGroupedPathMapInHaving));
+      }
+      return ExpressionUtils.constructQueryFilter(
+          convertedPredicates.stream().distinct().collect(Collectors.toList()));
+    }
+
+    return ExpressionUtils.constructQueryFilter(
+        transformExpressionsInHaving.stream().distinct().collect(Collectors.toList()));
+  }
+
+  private Expression analyzeHavingSplitByDevice(Set<Expression> transformExpressionsInHaving) {
+    return ExpressionUtils.constructQueryFilter(
+        transformExpressionsInHaving.stream().distinct().collect(Collectors.toList()));
+  }
+
+  private Map<Expression, Expression> analyzeGroupByLevelInHaving(
+      QueryStatement queryStatement,
+      List<Expression> inputExpressions,
+      Map<Expression, Set<Expression>> groupByLevelExpressions) {
+    GroupByLevelController groupByLevelController =
+        new GroupByLevelController(
+            queryStatement.getGroupByLevelComponent().getLevels(), typeProvider);
+    for (Expression inputExpression : inputExpressions) {
+      groupByLevelController.control(false, inputExpression, null);
+    }
+    Map<Expression, Set<Expression>> groupedPathMap = groupByLevelController.getGroupedPathMap();
+    groupByLevelExpressions.putAll(groupedPathMap);
+    return groupByLevelController.getRawPathToGroupedPathMap();
+  }
+
   private Map<Expression, Set<Expression>> analyzeGroupByLevel(
       QueryStatement queryStatement,
       List<Pair<Expression, String>> outputExpressions,
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java
index f4ee8a2..ed0ed49 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java
@@ -72,6 +72,13 @@
       ExpressionAnalyzer.constructPatternTreeFromExpression(
           queryStatement.getWhereCondition().getPredicate(), prefixPaths, patternTree);
     }
+
+    // concat HAVING with FROM
+    if (queryStatement.getHavingCondition() != null) {
+      ExpressionAnalyzer.constructPatternTreeFromExpression(
+          queryStatement.getHavingCondition().getPredicate(), prefixPaths, patternTree);
+    }
+
     return queryStatement;
   }
 
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java
index aa35eef..9c46f16 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java
@@ -50,6 +50,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.cartesianProduct;
@@ -58,17 +59,21 @@
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.getPairFromBetweenTimeFirst;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.getPairFromBetweenTimeSecond;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.getPairFromBetweenTimeThird;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructBinaryExpression;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructBinaryExpressions;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructFunctionExpression;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructFunctionExpressions;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTernaryExpression;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTernaryExpressions;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTimeSeriesOperand;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTimeSeriesOperands;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructUnaryExpression;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructUnaryExpressions;
 
 public class ExpressionAnalyzer {
-
   /**
-   * Check if all suffix paths in expression are measurements or one-level wildcards in ALIGN BY
-   * DEVICE query. If not, throw a {@link SemanticException}.
+   * Check if all suffix paths in expression are measurements or one-level wildcards, used in ALIGN
+   * BY DEVICE query or GroupByLevel query. If not, throw a {@link SemanticException}.
    *
    * @param expression expression to be checked
    */
@@ -91,7 +96,7 @@
       if (path.getNodes().length > 1
           || path.getFullPath().equals(IoTDBConstant.MULTI_LEVEL_PATH_WILDCARD)) {
         throw new SemanticException(
-            "ALIGN BY DEVICE: the suffix paths can only be measurement or one-level wildcard");
+            "the suffix paths can only be measurement or one-level wildcard");
       }
     } else if (expression instanceof TimestampOperand || expression instanceof ConstantOperand) {
       // do nothing
@@ -395,53 +400,60 @@
   }
 
   /**
-   * Concat suffix path in WHERE clause with the prefix path in the FROM clause. And then, bind
-   * schema ({@link PartialPath} -> {@link MeasurementPath}) and removes wildcards in Expression.
+   * Concat suffix path in WHERE and HAVING clause with the prefix path in the FROM clause. And
+   * then, bind schema ({@link PartialPath} -> {@link MeasurementPath}) and removes wildcards in
+   * Expression.
    *
    * @param prefixPaths prefix paths in the FROM clause
    * @param schemaTree interface for querying schema information
    * @param typeProvider a map to record output symbols and their data types
    * @return the expression list with full path and after binding schema
    */
-  public static List<Expression> removeWildcardInQueryFilter(
+  public static List<Expression> removeWildcardInFilter(
       Expression predicate,
       List<PartialPath> prefixPaths,
       ISchemaTree schemaTree,
-      TypeProvider typeProvider) {
+      TypeProvider typeProvider,
+      boolean isWhere) {
     if (predicate instanceof TernaryExpression) {
       List<Expression> firstExpressions =
-          removeWildcardInQueryFilter(
+          removeWildcardInFilter(
               ((TernaryExpression) predicate).getFirstExpression(),
               prefixPaths,
               schemaTree,
-              typeProvider);
+              typeProvider,
+              isWhere);
       List<Expression> secondExpressions =
-          removeWildcardInQueryFilter(
+          removeWildcardInFilter(
               ((TernaryExpression) predicate).getSecondExpression(),
               prefixPaths,
               schemaTree,
-              typeProvider);
+              typeProvider,
+              isWhere);
       List<Expression> thirdExpressions =
-          removeWildcardInQueryFilter(
+          removeWildcardInFilter(
               ((TernaryExpression) predicate).getThirdExpression(),
               prefixPaths,
               schemaTree,
-              typeProvider);
+              typeProvider,
+              isWhere);
       return reconstructTernaryExpressions(
           predicate, firstExpressions, secondExpressions, thirdExpressions);
     } else if (predicate instanceof BinaryExpression) {
       List<Expression> leftExpressions =
-          removeWildcardInQueryFilter(
+          removeWildcardInFilter(
               ((BinaryExpression) predicate).getLeftExpression(),
               prefixPaths,
               schemaTree,
-              typeProvider);
+              typeProvider,
+              isWhere);
       List<Expression> rightExpressions =
-          removeWildcardInQueryFilter(
+          removeWildcardInFilter(
               ((BinaryExpression) predicate).getRightExpression(),
               prefixPaths,
               schemaTree,
-              typeProvider);
+              typeProvider,
+              isWhere);
       if (predicate.getExpressionType() == ExpressionType.LOGIC_AND) {
         List<Expression> resultExpressions = new ArrayList<>(leftExpressions);
         resultExpressions.addAll(rightExpressions);
@@ -451,17 +463,22 @@
           predicate.getExpressionType(), leftExpressions, rightExpressions);
     } else if (predicate instanceof UnaryExpression) {
       List<Expression> childExpressions =
-          removeWildcardInQueryFilter(
-              ((UnaryExpression) predicate).getExpression(), prefixPaths, schemaTree, typeProvider);
+          removeWildcardInFilter(
+              ((UnaryExpression) predicate).getExpression(),
+              prefixPaths,
+              schemaTree,
+              typeProvider,
+              isWhere);
       return reconstructUnaryExpressions((UnaryExpression) predicate, childExpressions);
     } else if (predicate instanceof FunctionExpression) {
-      if (predicate.isBuiltInAggregationFunctionExpression()) {
+      if (predicate.isBuiltInAggregationFunctionExpression() && isWhere) {
         throw new SemanticException("aggregate functions are not supported in WHERE clause");
       }
       List<List<Expression>> extendedExpressions = new ArrayList<>();
       for (Expression suffixExpression : predicate.getExpressions()) {
         extendedExpressions.add(
-            removeWildcardInQueryFilter(suffixExpression, prefixPaths, schemaTree, typeProvider));
+            removeWildcardInFilter(
+                suffixExpression, prefixPaths, schemaTree, typeProvider, isWhere));
       }
       List<List<Expression>> childExpressionsList = new ArrayList<>();
       cartesianProduct(extendedExpressions, childExpressionsList, 0, new ArrayList<>());
@@ -481,7 +498,9 @@
         List<MeasurementPath> actualPaths = schemaTree.searchMeasurementPaths(concatPath).left;
         if (actualPaths.size() == 0) {
           throw new SemanticException(
-              String.format("the path '%s' in WHERE clause does not exist", concatPath));
+              String.format(
+                  "the path '%s' in %s clause does not exist",
+                  concatPath, isWhere ? "WHERE" : "HAVING"));
         }
         noStarPaths.addAll(actualPaths);
       }
@@ -498,6 +517,57 @@
     }
   }
 
+  public static Expression replaceRawPathWithGroupedPath(
+      Expression predicate, Map<Expression, Expression> rawPathToGroupedPathMapInHaving) {
+    if (predicate instanceof TernaryExpression) {
+      Expression firstExpression =
+          replaceRawPathWithGroupedPath(
+              ((TernaryExpression) predicate).getFirstExpression(),
+              rawPathToGroupedPathMapInHaving);
+      Expression secondExpression =
+          replaceRawPathWithGroupedPath(
+              ((TernaryExpression) predicate).getSecondExpression(),
+              rawPathToGroupedPathMapInHaving);
+      Expression thirdExpression =
+          replaceRawPathWithGroupedPath(
+              ((TernaryExpression) predicate).getThirdExpression(),
+              rawPathToGroupedPathMapInHaving);
+      return reconstructTernaryExpression(
+          predicate, firstExpression, secondExpression, thirdExpression);
+    } else if (predicate instanceof BinaryExpression) {
+      Expression leftExpression =
+          replaceRawPathWithGroupedPath(
+              ((BinaryExpression) predicate).getLeftExpression(), rawPathToGroupedPathMapInHaving);
+      Expression rightExpression =
+          replaceRawPathWithGroupedPath(
+              ((BinaryExpression) predicate).getRightExpression(), rawPathToGroupedPathMapInHaving);
+      return reconstructBinaryExpression(
+          predicate.getExpressionType(), leftExpression, rightExpression);
+    } else if (predicate instanceof UnaryExpression) {
+      Expression expression =
+          replaceRawPathWithGroupedPath(
+              ((UnaryExpression) predicate).getExpression(), rawPathToGroupedPathMapInHaving);
+      return reconstructUnaryExpression((UnaryExpression) predicate, expression);
+    } else if (predicate instanceof FunctionExpression) {
+      List<Expression> expressions = predicate.getExpressions();
+      List<Expression> childrenExpressions = new ArrayList<>();
+      for (Expression expression : expressions) {
+        childrenExpressions.add(
+            replaceRawPathWithGroupedPath(expression, rawPathToGroupedPathMapInHaving));
+      }
+      return reconstructFunctionExpression((FunctionExpression) predicate, childrenExpressions);
+    } else if (predicate instanceof TimeSeriesOperand) {
+      PartialPath groupedPath =
+          ((TimeSeriesOperand) rawPathToGroupedPathMapInHaving.get(predicate)).getPath();
+      return reconstructTimeSeriesOperand(groupedPath);
+    } else if (predicate instanceof TimestampOperand || predicate instanceof ConstantOperand) {
+      return predicate;
+    } else {
+      throw new IllegalArgumentException(
+          "unsupported expression type: " + predicate.getExpressionType());
+    }
+  }
+
   /**
    * Concat expression with the device path in the FROM clause.And then, bind schema ({@link
    * PartialPath} -> {@link MeasurementPath}) and removes wildcards in Expression. This method used
@@ -587,50 +657,56 @@
   }
 
   /**
-   * Concat measurement in WHERE clause with device path. And then, bind schema ({@link PartialPath}
-   * -> {@link MeasurementPath}) and removes wildcards.
+   * Concat measurement in WHERE and HAVING clause with device path. And then, bind schema ({@link
+   * PartialPath} -> {@link MeasurementPath}) and removes wildcards.
    *
    * @return the expression list with full path and after binding schema
    */
-  public static List<Expression> removeWildcardInQueryFilterByDevice(
+  public static List<Expression> removeWildcardInFilterByDevice(
       Expression predicate,
       PartialPath devicePath,
       ISchemaTree schemaTree,
-      TypeProvider typeProvider) {
+      TypeProvider typeProvider,
+      boolean isWhere) {
     if (predicate instanceof TernaryExpression) {
       List<Expression> firstExpressions =
-          removeWildcardInQueryFilterByDevice(
+          removeWildcardInFilterByDevice(
               ((TernaryExpression) predicate).getFirstExpression(),
               devicePath,
               schemaTree,
-              typeProvider);
+              typeProvider,
+              isWhere);
       List<Expression> secondExpressions =
-          removeWildcardInQueryFilterByDevice(
+          removeWildcardInFilterByDevice(
               ((TernaryExpression) predicate).getSecondExpression(),
               devicePath,
               schemaTree,
-              typeProvider);
+              typeProvider,
+              isWhere);
       List<Expression> thirdExpressions =
-          removeWildcardInQueryFilterByDevice(
+          removeWildcardInFilterByDevice(
               ((TernaryExpression) predicate).getThirdExpression(),
               devicePath,
               schemaTree,
-              typeProvider);
+              typeProvider,
+              isWhere);
       return reconstructTernaryExpressions(
           predicate, firstExpressions, secondExpressions, thirdExpressions);
     } else if (predicate instanceof BinaryExpression) {
       List<Expression> leftExpressions =
-          removeWildcardInQueryFilterByDevice(
+          removeWildcardInFilterByDevice(
               ((BinaryExpression) predicate).getLeftExpression(),
               devicePath,
               schemaTree,
-              typeProvider);
+              typeProvider,
+              isWhere);
       List<Expression> rightExpressions =
-          removeWildcardInQueryFilterByDevice(
+          removeWildcardInFilterByDevice(
               ((BinaryExpression) predicate).getRightExpression(),
               devicePath,
               schemaTree,
-              typeProvider);
+              typeProvider,
+              isWhere);
       if (predicate.getExpressionType() == ExpressionType.LOGIC_AND) {
         List<Expression> resultExpressions = new ArrayList<>(leftExpressions);
         resultExpressions.addAll(rightExpressions);
@@ -640,18 +716,22 @@
           predicate.getExpressionType(), leftExpressions, rightExpressions);
     } else if (predicate instanceof UnaryExpression) {
       List<Expression> childExpressions =
-          removeWildcardInQueryFilterByDevice(
-              ((UnaryExpression) predicate).getExpression(), devicePath, schemaTree, typeProvider);
+          removeWildcardInFilterByDevice(
+              ((UnaryExpression) predicate).getExpression(),
+              devicePath,
+              schemaTree,
+              typeProvider,
+              isWhere);
       return reconstructUnaryExpressions((UnaryExpression) predicate, childExpressions);
     } else if (predicate instanceof FunctionExpression) {
-      if (predicate.isBuiltInAggregationFunctionExpression()) {
+      if (predicate.isBuiltInAggregationFunctionExpression() && isWhere) {
         throw new SemanticException("aggregate functions are not supported in WHERE clause");
       }
       List<List<Expression>> extendedExpressions = new ArrayList<>();
       for (Expression suffixExpression : predicate.getExpressions()) {
         extendedExpressions.add(
-            removeWildcardInQueryFilterByDevice(
-                suffixExpression, devicePath, schemaTree, typeProvider));
+            removeWildcardInFilterByDevice(
+                suffixExpression, devicePath, schemaTree, typeProvider, isWhere));
       }
       List<List<Expression>> childExpressionsList = new ArrayList<>();
       cartesianProduct(extendedExpressions, childExpressionsList, 0, new ArrayList<>());
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java
index f1b0df1..62df923 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java
@@ -66,6 +66,10 @@
     return resultExpressions;
   }
 
+  public static Expression reconstructTimeSeriesOperand(PartialPath actualPath) {
+    return new TimeSeriesOperand(actualPath);
+  }
+
   public static List<Expression> reconstructFunctionExpressions(
       FunctionExpression expression, List<List<Expression>> childExpressionsList) {
     List<Expression> resultExpressions = new ArrayList<>();
@@ -79,6 +83,12 @@
     return resultExpressions;
   }
 
+  public static Expression reconstructFunctionExpression(
+      FunctionExpression expression, List<Expression> childExpressions) {
+    return new FunctionExpression(
+        expression.getFunctionName(), expression.getFunctionAttributes(), childExpressions);
+  }
+
   public static List<Expression> reconstructUnaryExpressions(
       UnaryExpression expression, List<Expression> childExpressions) {
     List<Expression> resultExpressions = new ArrayList<>();
@@ -123,6 +133,39 @@
     return resultExpressions;
   }
 
+  public static Expression reconstructUnaryExpression(
+      UnaryExpression expression, Expression childExpression) {
+    switch (expression.getExpressionType()) {
+      case IS_NULL:
+        return new IsNullExpression(childExpression, ((IsNullExpression) expression).isNot());
+      case IN:
+        return new InExpression(
+            childExpression,
+            ((InExpression) expression).isNotIn(),
+            ((InExpression) expression).getValues());
+      case LIKE:
+        return new LikeExpression(
+            childExpression,
+            ((LikeExpression) expression).getPatternString(),
+            ((LikeExpression) expression).getPattern());
+      case LOGIC_NOT:
+        return new LogicNotExpression(childExpression);
+
+      case NEGATION:
+        return new NegationExpression(childExpression);
+
+      case REGEXP:
+        return new RegularExpression(
+            childExpression,
+            ((RegularExpression) expression).getPatternString(),
+            ((RegularExpression) expression).getPattern());
+
+      default:
+        throw new IllegalArgumentException(
+            "unsupported expression type: " + expression.getExpressionType());
+    }
+  }
+
   public static List<Expression> reconstructBinaryExpressions(
       ExpressionType expressionType,
       List<Expression> leftExpressions,
@@ -178,6 +221,49 @@
     return resultExpressions;
   }
 
+  public static Expression reconstructBinaryExpression(
+      ExpressionType expressionType, Expression leftExpression, Expression rightExpression) {
+    switch (expressionType) {
+      case ADDITION:
+        return new AdditionExpression(leftExpression, rightExpression);
+
+      case SUBTRACTION:
+        return new SubtractionExpression(leftExpression, rightExpression);
+      case MULTIPLICATION:
+        return new MultiplicationExpression(leftExpression, rightExpression);
+      case DIVISION:
+        return new DivisionExpression(leftExpression, rightExpression);
+      case MODULO:
+        return new ModuloExpression(leftExpression, rightExpression);
+
+      case LESS_THAN:
+        return new LessThanExpression(leftExpression, rightExpression);
+      case LESS_EQUAL:
+        return new LessEqualExpression(leftExpression, rightExpression);
+
+      case GREATER_THAN:
+        return new GreaterThanExpression(leftExpression, rightExpression);
+
+      case GREATER_EQUAL:
+        return new GreaterEqualExpression(leftExpression, rightExpression);
+
+      case EQUAL_TO:
+        return new EqualToExpression(leftExpression, rightExpression);
+
+      case NON_EQUAL:
+        return new NonEqualExpression(leftExpression, rightExpression);
+
+      case LOGIC_AND:
+        return new LogicAndExpression(leftExpression, rightExpression);
+
+      case LOGIC_OR:
+        return new LogicOrExpression(leftExpression, rightExpression);
+
+      default:
+        throw new IllegalArgumentException("unsupported expression type: " + expressionType);
+    }
+  }
+
   public static List<Expression> reconstructTernaryExpressions(
       Expression expression,
       List<Expression> firstExpressions,
@@ -194,13 +280,32 @@
                       fe, se, te, ((BetweenExpression) expression).isNotBetween()));
               break;
             default:
-              throw new UnsupportedOperationException();
+              throw new IllegalArgumentException(
+                  "unsupported expression type: " + expression.getExpressionType());
           }
         }
     }
     return resultExpressions;
   }
 
+  public static Expression reconstructTernaryExpression(
+      Expression expression,
+      Expression firstExpression,
+      Expression secondExpression,
+      Expression thirdExpression) {
+    switch (expression.getExpressionType()) {
+      case BETWEEN:
+        return new BetweenExpression(
+            firstExpression,
+            secondExpression,
+            thirdExpression,
+            ((BetweenExpression) expression).isNotBetween());
+      default:
+        throw new IllegalArgumentException(
+            "unsupported expression type: " + expression.getExpressionType());
+    }
+  }
+
   public static <T> void cartesianProduct(
       List<List<T>> dimensionValue, List<List<T>> resultList, int layer, List<T> currentList) {
     if (layer < dimensionValue.size() - 1) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/FunctionExpression.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/FunctionExpression.java
index 2371300..259b505 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/FunctionExpression.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/multi/FunctionExpression.java
@@ -432,7 +432,7 @@
   @Override
   public boolean isMappable(TypeProvider typeProvider) {
     if (isBuiltInAggregationFunctionExpression) {
-      return false;
+      return true;
     }
     return new UDTFInformationInferrer(functionName)
         .getAccessStrategy(
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 e0c6bf0..1e444de 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
@@ -65,6 +65,7 @@
 import org.apache.iotdb.db.mpp.plan.statement.component.FromComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.GroupByLevelComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.GroupByTimeComponent;
+import org.apache.iotdb.db.mpp.plan.statement.component.HavingCondition;
 import org.apache.iotdb.db.mpp.plan.statement.component.OrderByComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
 import org.apache.iotdb.db.mpp.plan.statement.component.ResultColumn;
@@ -827,6 +828,11 @@
       parseOrderByClause(ctx.orderByClause());
     }
 
+    // parse Having
+    if (ctx.havingClause() != null) {
+      parseHavingClause(ctx.havingClause());
+    }
+
     // parse limit & offset
     if (ctx.specialLimit() != null) {
       return visit(ctx.specialLimit());
@@ -845,6 +851,11 @@
       parseOrderByClause(ctx.orderByClause());
     }
 
+    // parse Having
+    if (ctx.havingClause() != null) {
+      parseHavingClause(ctx.havingClause());
+    }
+
     // parse limit & offset
     if (ctx.specialLimit() != null) {
       return visit(ctx.specialLimit());
@@ -975,6 +986,11 @@
       parseOrderByClause(ctx.orderByClause());
     }
 
+    // parse Having
+    if (ctx.havingClause() != null) {
+      parseHavingClause(ctx.havingClause());
+    }
+
     // parse limit & offset
     if (ctx.specialLimit() != null) {
       return visit(ctx.specialLimit());
@@ -998,6 +1014,13 @@
     queryStatement.setGroupByLevelComponent(groupByLevelComponent);
   }
 
+  // HAVING Clause
+  public void parseHavingClause(IoTDBSqlParser.HavingClauseContext ctx) {
+    Expression predicate =
+        parseExpression(ctx.expression(), ctx.expression().OPERATOR_NOT() == null);
+    queryStatement.setHavingCondition(new HavingCondition(predicate));
+  }
+
   // Fill Clause
   @Override
   public Statement visitFillStatement(IoTDBSqlParser.FillStatementContext ctx) {
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 ad5cbbe..8dda44b 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
@@ -118,6 +118,9 @@
                     analysis.getDeviceToQueryFilter() != null
                         ? analysis.getDeviceToQueryFilter().get(deviceName)
                         : null,
+                    analysis.getDeviceToHavingExpression() != null
+                        ? analysis.getDeviceToHavingExpression().get(deviceName)
+                        : null,
                     analysis.getDeviceToMeasurementIndexesMap().get(deviceName),
                     context));
         deviceToSubPlanMap.put(deviceName, subPlanBuilder.getRoot());
@@ -142,6 +145,7 @@
                   analysis.getAggregationTransformExpressions(),
                   analysis.getTransformExpressions(),
                   analysis.getQueryFilter(),
+                  analysis.getHavingExpression(),
                   null,
                   context));
     }
@@ -164,6 +168,7 @@
       Set<Expression> aggregationTransformExpressions,
       Set<Expression> transformExpressions,
       Expression queryFilter,
+      Expression havingExpression,
       List<Integer> measurementIndexes, // only used in ALIGN BY DEVICE
       MPPQueryContext context) {
     LogicalPlanBuilder planBuilder = new LogicalPlanBuilder(context);
@@ -232,12 +237,32 @@
           }
         }
 
-        planBuilder =
-            planBuilder.planTransform(
-                transformExpressions,
-                queryStatement.isGroupByTime(),
-                queryStatement.getSelectComponent().getZoneId(),
-                queryStatement.getResultTimeOrder());
+        if (queryStatement.isGroupByLevel()) {
+          planBuilder = // plan Having with GroupByLevel
+              planBuilder.planFilterAndTransform(
+                  havingExpression,
+                  analysis.getGroupByLevelExpressions().keySet(),
+                  queryStatement.isGroupByTime(),
+                  queryStatement.getSelectComponent().getZoneId(),
+                  queryStatement.getResultTimeOrder());
+        } else {
+          if (havingExpression != null) {
+            planBuilder = // plan Having without GroupByLevel
+                planBuilder.planFilterAndTransform(
+                    havingExpression,
+                    transformExpressions,
+                    queryStatement.isGroupByTime(),
+                    queryStatement.getSelectComponent().getZoneId(),
+                    queryStatement.getResultTimeOrder());
+          } else {
+            planBuilder = // no Having
+                planBuilder.planTransform(
+                    transformExpressions,
+                    queryStatement.isGroupByTime(),
+                    queryStatement.getSelectComponent().getZoneId(),
+                    queryStatement.getResultTimeOrder());
+          }
+        }
       } else {
         if (analysis.hasValueFilter()) {
           planBuilder =
@@ -284,23 +309,61 @@
                 measurementIndexes,
                 analysis.getGroupByLevelExpressions(),
                 analysis.getTypeProvider());
+        if (queryStatement.isGroupByLevel()) {
+          planBuilder = // plan Having with GroupByLevel
+              planBuilder.planFilterAndTransform(
+                  havingExpression,
+                  analysis.getGroupByLevelExpressions().keySet(),
+                  queryStatement.isGroupByTime(),
+                  queryStatement.getSelectComponent().getZoneId(),
+                  queryStatement.getResultTimeOrder());
+        } else {
+          planBuilder = // plan Having without GroupByLevel
+              planBuilder.planFilterAndTransform(
+                  havingExpression,
+                  transformExpressions,
+                  queryStatement.isGroupByTime(),
+                  queryStatement.getSelectComponent().getZoneId(),
+                  queryStatement.getResultTimeOrder());
+        }
       } else {
         planBuilder =
-            planBuilder
-                .planAggregationSource(
-                    sourceExpressions,
-                    curStep,
-                    queryStatement.getResultTimeOrder(),
-                    analysis.getGlobalTimeFilter(),
-                    analysis.getGroupByTimeParameter(),
-                    aggregationExpressions,
-                    analysis.getGroupByLevelExpressions(),
-                    analysis.getTypeProvider())
-                .planTransform(
+            planBuilder.planAggregationSource(
+                sourceExpressions,
+                curStep,
+                queryStatement.getResultTimeOrder(),
+                analysis.getGlobalTimeFilter(),
+                analysis.getGroupByTimeParameter(),
+                aggregationExpressions,
+                analysis.getGroupByLevelExpressions(),
+                analysis.getTypeProvider());
+
+        if (queryStatement.isGroupByLevel()) {
+          planBuilder = // plan Having with GroupByLevel
+              planBuilder.planFilterAndTransform(
+                  havingExpression,
+                  analysis.getGroupByLevelExpressions().keySet(),
+                  queryStatement.isGroupByTime(),
+                  queryStatement.getSelectComponent().getZoneId(),
+                  queryStatement.getResultTimeOrder());
+        } else {
+          if (havingExpression != null) {
+            planBuilder = // plan Having without GroupByLevel
+                planBuilder.planFilterAndTransform(
+                    havingExpression,
                     transformExpressions,
                     queryStatement.isGroupByTime(),
                     queryStatement.getSelectComponent().getZoneId(),
                     queryStatement.getResultTimeOrder());
+          } else {
+            planBuilder = // no Having
+                planBuilder.planTransform(
+                    transformExpressions,
+                    queryStatement.isGroupByTime(),
+                    queryStatement.getSelectComponent().getZoneId(),
+                    queryStatement.getResultTimeOrder());
+          }
+        }
       }
     }
 
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/HavingCondition.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/HavingCondition.java
new file mode 100644
index 0000000..c9a89e3
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/HavingCondition.java
@@ -0,0 +1,42 @@
+/*
+ * 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.mpp.plan.statement.component;
+
+import org.apache.iotdb.db.mpp.plan.expression.Expression;
+import org.apache.iotdb.db.mpp.plan.statement.StatementNode;
+
+/** This class maintains information of {@code HAVING} clause. */
+public class HavingCondition extends StatementNode {
+  private Expression predicate;
+
+  public HavingCondition() {}
+
+  public HavingCondition(Expression predicate) {
+    this.predicate = predicate;
+  }
+
+  public Expression getPredicate() {
+    return predicate;
+  }
+
+  public void setPredicate(Expression predicate) {
+    this.predicate = predicate;
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
index 0ab276b..0b06d17 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
@@ -31,6 +31,7 @@
 import org.apache.iotdb.db.mpp.plan.statement.component.FromComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.GroupByLevelComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.GroupByTimeComponent;
+import org.apache.iotdb.db.mpp.plan.statement.component.HavingCondition;
 import org.apache.iotdb.db.mpp.plan.statement.component.OrderByComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
 import org.apache.iotdb.db.mpp.plan.statement.component.ResultColumn;
@@ -66,6 +67,7 @@
   protected SelectComponent selectComponent;
   protected FromComponent fromComponent;
   protected WhereCondition whereCondition;
+  protected HavingCondition havingCondition;
 
   // row limit and offset for result set. The default value is 0, which means no limit
   protected int rowLimit = 0;
@@ -122,6 +124,18 @@
     this.whereCondition = whereCondition;
   }
 
+  public boolean hasHaving() {
+    return havingCondition != null;
+  }
+
+  public HavingCondition getHavingCondition() {
+    return havingCondition;
+  }
+
+  public void setHavingCondition(HavingCondition havingCondition) {
+    this.havingCondition = havingCondition;
+  }
+
   public int getRowLimit() {
     return rowLimit;
   }
@@ -264,14 +278,37 @@
       }
     }
 
+    if (getHavingCondition() != null) {
+      Expression havingExpression = getHavingCondition().getPredicate();
+      if (ExpressionAnalyzer.identifyOutputColumnType(havingExpression, true)
+          != ResultColumn.ColumnType.AGGREGATION) {
+        throw new SemanticException("Expression of HAVING clause must to be an Aggregation");
+      }
+      try {
+        if (isGroupByLevel()) { // check path in SELECT and HAVING only have one node
+          for (ResultColumn resultColumn : getSelectComponent().getResultColumns()) {
+            ExpressionAnalyzer.checkIsAllMeasurement(resultColumn.getExpression());
+          }
+          ExpressionAnalyzer.checkIsAllMeasurement(havingExpression);
+        }
+      } catch (SemanticException e) {
+        throw new SemanticException("When Having used with GroupByLevel: " + e.getMessage());
+      }
+    }
+
     if (isAlignByDevice()) {
       // the paths can only be measurement or one-level wildcard in ALIGN BY DEVICE
-      for (ResultColumn resultColumn : selectComponent.getResultColumns()) {
-        ExpressionAnalyzer.checkIsAllMeasurement(resultColumn.getExpression());
+      try {
+        for (ResultColumn resultColumn : selectComponent.getResultColumns()) {
+          ExpressionAnalyzer.checkIsAllMeasurement(resultColumn.getExpression());
+        }
+        if (getWhereCondition() != null) {
+          ExpressionAnalyzer.checkIsAllMeasurement(getWhereCondition().getPredicate());
+        }
+      } catch (SemanticException e) {
+        throw new SemanticException("ALIGN BY DEVICE: " + e.getMessage());
       }
-      if (getWhereCondition() != null) {
-        ExpressionAnalyzer.checkIsAllMeasurement(getWhereCondition().getPredicate());
-      }
+
       if (isOrderByTimeseries()) {
         throw new SemanticException("Sorting by timeseries is only supported in last queries.");
       }