You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by ja...@apache.org on 2022/07/01 10:54:57 UTC

[iotdb] branch master updated: [IOTDB-3173] Support Between expression (#6483)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 6924bef386 [IOTDB-3173] Support Between expression (#6483)
6924bef386 is described below

commit 6924bef386c87fe9d2854099df0728f8067e80f5
Author: Weihao Li <60...@users.noreply.github.com>
AuthorDate: Fri Jul 1 18:54:52 2022 +0800

    [IOTDB-3173] Support Between expression (#6483)
---
 .../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4   |   1 +
 .../antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4  |   2 +
 docs/UserGuide/Query-Data/Query-Filter.md          |   7 +-
 docs/zh/UserGuide/Query-Data/Query-Filter.md       |  13 +-
 .../apache/iotdb/db/it/IoTDBFilterBetweenIT.java   | 179 +++++++++++
 .../org/apache/iotdb/db/it/IoTDBFilterNullIT.java  |   2 +-
 .../org/apache/iotdb/db/it/IoTDBNestedQueryIT.java |  72 +++++
 .../db/it/IoTDBSyntaxConventionIdentifierIT.java   |   4 -
 .../iotdb/db/integration/IoTDBNestedQueryIT.java   |  38 +++
 .../IoTDBSyntaxConventionIdentifierIT.java         |   4 -
 .../db/mpp/plan/analyze/ExpressionAnalyzer.java    | 257 ++++++++++++++--
 .../iotdb/db/mpp/plan/analyze/ExpressionUtils.java |  73 +++++
 .../iotdb/db/mpp/plan/expression/Expression.java   |   5 +-
 .../db/mpp/plan/expression/ExpressionType.java     |   2 +-
 .../plan/expression/ternary/BetweenExpression.java | 110 +++++++
 .../plan/expression/ternary/TernaryExpression.java | 336 +++++++++++++++++++++
 .../iotdb/db/mpp/plan/parser/ASTVisitor.java       |  17 +-
 .../dag/transformer/Transformer.java               |  21 ++
 .../transformer/ternary/BetweenTransformer.java    |  67 ++++
 .../ternary/CompareTernaryTransformer.java         |  86 ++++++
 .../transformer/ternary/TernaryTransformer.java    | 265 ++++++++++++++++
 .../apache/iotdb/db/qp/sql/IoTDBSqlVisitor.java    |  37 ++-
 .../iotdb/tsfile/read/filter/TimeFilter.java       |  12 +
 .../tsfile/read/filter/factory/FilterFactory.java  |   4 +
 .../read/filter/factory/FilterSerializeId.java     |   3 +-
 .../iotdb/tsfile/read/filter/operator/Between.java | 141 +++++++++
 26 files changed, 1717 insertions(+), 41 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 1dbb03f7ce..dd01a8e693 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
@@ -861,6 +861,7 @@ expression
     | leftExpression=expression (PLUS | MINUS) rightExpression=expression
     | leftExpression=expression (OPERATOR_GT | OPERATOR_GTE | OPERATOR_LT | OPERATOR_LTE | OPERATOR_SEQ | OPERATOR_DEQ | OPERATOR_NEQ) rightExpression=expression
     | unaryBeforeRegularOrLikeExpression=expression (REGEXP | LIKE) STRING_LITERAL
+    | firstExpression=expression OPERATOR_NOT? OPERATOR_BETWEEN secondExpression=expression OPERATOR_AND thirdExpression=expression
     | unaryBeforeIsNullExpression=expression OPERATOR_IS OPERATOR_NOT? NULL_LITERAL
     | unaryBeforeInExpression=expression OPERATOR_NOT? (OPERATOR_IN | OPERATOR_CONTAINS) LR_BRACKET constant (COMMA constant)* RR_BRACKET
     | leftExpression=expression OPERATOR_AND rightExpression=expression
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 7e9b769e88..307622686e 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
@@ -773,6 +773,8 @@ OPERATOR_LT : '<';
 OPERATOR_LTE : '<=';
 OPERATOR_NEQ : '!=' | '<>';
 
+OPERATOR_BETWEEN : B E T W E E N;
+
 OPERATOR_IS : I S;
 
 OPERATOR_IN : I N;
diff --git a/docs/UserGuide/Query-Data/Query-Filter.md b/docs/UserGuide/Query-Data/Query-Filter.md
index d0d90ebbf3..91ef5722bf 100644
--- a/docs/UserGuide/Query-Data/Query-Filter.md
+++ b/docs/UserGuide/Query-Data/Query-Filter.md
@@ -74,10 +74,13 @@ An example is as follows:
     select status from root.sg1.d1 where status = true;
     ````
 
-3. Select data for the interval (36.5,40]:
+3. Select data for the interval [36.5,40] or not:
 
     ```sql
-    select temperature from root.sg1.d1 where temperature > 36.5 and temperature < 40;
+    select temperature from root.sg1.d1 where temperature between 36.5 and 40;
+    ````
+    ```sql
+    select temperature from root.sg1.d1 where temperature not between 36.5 and 40;
     ````
 
 4. Select data with values within a specific range:
diff --git a/docs/zh/UserGuide/Query-Data/Query-Filter.md b/docs/zh/UserGuide/Query-Data/Query-Filter.md
index 569bc04b02..a53f1c7994 100644
--- a/docs/zh/UserGuide/Query-Data/Query-Filter.md
+++ b/docs/zh/UserGuide/Query-Data/Query-Filter.md
@@ -73,12 +73,15 @@
    ```sql
    select status from root.sg1.d1 where status = true;
 
-3. 选择区间 (36.5,40] 的数据:
-
-   ```sql
-   select temperature from root.sg1.d1 where temperature > 36.5 and temperature < 40;
-   ```
+3. 选择区间 [36.5,40] 内或之外的数据:
 
+    ```sql
+    select temperature from root.sg1.d1 where temperature between 36.5 and 40;
+    ````
+    ```sql
+    select temperature from root.sg1.d1 where temperature not between 36.5 and 40;
+    ````
+   
 4. 选择值在特定范围内的数据:
 
    ```sql
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBFilterBetweenIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBFilterBetweenIT.java
new file mode 100644
index 0000000000..802205b7c0
--- /dev/null
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBFilterBetweenIT.java
@@ -0,0 +1,179 @@
+/*
+ * 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;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.env.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.ClusterIT;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import static org.junit.Assert.fail;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({ClusterIT.class}) // TODO After old StandAlone remove
+public class IoTDBFilterBetweenIT {
+  protected static final int ITERATION_TIMES = 10;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    EnvFactory.getEnv().initBeforeTest();
+    createTimeSeries();
+    generateData();
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvFactory.getEnv().cleanAfterTest();
+  }
+
+  private static void createTimeSeries() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      statement.execute("SET STORAGE GROUP TO root.vehicle");
+      statement.execute("CREATE TIMESERIES root.vehicle.d1.s1 with datatype=INT32,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.vehicle.d1.s2 with datatype=INT32,encoding=PLAIN");
+      statement.execute("CREATE TIMESERIES root.vehicle.d1.s3 with datatype=TEXT,encoding=PLAIN");
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  private static void generateData() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (int i = 1; i <= ITERATION_TIMES; ++i) {
+        statement.execute(
+            String.format(
+                "insert into root.vehicle.d1(timestamp,s1,s2,s3) values(%d,%d,%d,%s)", i, i, i, i));
+      }
+    } catch (SQLException throwable) {
+      fail(throwable.getMessage());
+    }
+  }
+
+  @Test
+  public void testBetweenExpression() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      int start = 1, end = 5;
+      String query = "SELECT * FROM root.vehicle.d1 WHERE s1 BETWEEN " + start + " AND " + end;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+
+      query =
+          "SELECT * FROM root.vehicle.d1 WHERE s1 NOT BETWEEN " // test not between
+              + (end + 1)
+              + " AND "
+              + ITERATION_TIMES;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+
+      query = "SELECT * FROM root.vehicle.d1 WHERE time BETWEEN " + start + " AND " + end;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+
+      query =
+          "SELECT * FROM root.vehicle.d1 WHERE time NOT BETWEEN " // test not between
+              + (end + 1)
+              + " AND "
+              + ITERATION_TIMES;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+
+      query = "SELECT * FROM root.vehicle.d1 WHERE " + start + " BETWEEN time AND " + end;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        Assert.assertTrue(rs.next());
+        Assert.assertEquals("1", rs.getString("Time"));
+        Assert.assertEquals("1", rs.getString("root.vehicle.d1.s1"));
+        Assert.assertEquals("1", rs.getString("root.vehicle.d1.s2"));
+        Assert.assertEquals("1", rs.getString("root.vehicle.d1.s3"));
+      }
+
+      query = "SELECT * FROM root.vehicle.d1 WHERE " + start + " NOT BETWEEN time AND " + end;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        for (int i = start + 1; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+
+      query = "SELECT * FROM root.vehicle.d1 WHERE " + start + " BETWEEN " + end + " AND time";
+      try (ResultSet rs = statement.executeQuery(query)) {
+        Assert.assertFalse(rs.next());
+      }
+
+      query = "SELECT * FROM root.vehicle.d1 WHERE " + start + " NOT BETWEEN " + end + " AND time";
+      try (ResultSet rs = statement.executeQuery(query)) {
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      Assert.fail(e.getMessage());
+    }
+  }
+}
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBFilterNullIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBFilterNullIT.java
index 189ccc1596..173b181d47 100644
--- a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBFilterNullIT.java
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBFilterNullIT.java
@@ -33,7 +33,7 @@ import java.sql.Statement;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
-@Category(ClusterIT.class)
+@Category(ClusterIT.class) // TODO After old StandAlone remove
 public class IoTDBFilterNullIT {
 
   private static final String[] createSqls =
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBNestedQueryIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBNestedQueryIT.java
index cf474e813d..4f1f57386c 100644
--- a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBNestedQueryIT.java
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBNestedQueryIT.java
@@ -570,6 +570,78 @@ public class IoTDBNestedQueryIT {
     }
   }
 
+  @Test
+  public void testBetweenExpression() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      int start = 2, end = 8;
+      String query = "SELECT * FROM root.vehicle.d1 WHERE s1 BETWEEN " + start + " AND " + end;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+
+      query =
+          "SELECT * FROM root.vehicle.d1 WHERE s1 NOT BETWEEN " // test not between
+              + (end + 1)
+              + " AND "
+              + ITERATION_TIMES;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        Assert.assertTrue(rs.next());
+        Assert.assertEquals("1", rs.getString("Time"));
+        Assert.assertEquals("1", rs.getString("root.vehicle.d1.s1"));
+        Assert.assertEquals("1", rs.getString("root.vehicle.d1.s2"));
+        Assert.assertEquals("1", rs.getString("root.vehicle.d1.s3"));
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+
+      query = "SELECT * FROM root.vehicle.d1 WHERE time BETWEEN " + start + " AND " + end;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+
+      query =
+          "SELECT * FROM root.vehicle.d1 WHERE time NOT BETWEEN " // test not between
+              + (end + 1)
+              + " AND "
+              + ITERATION_TIMES;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        Assert.assertTrue(rs.next());
+        Assert.assertEquals("1", rs.getString("Time"));
+        Assert.assertEquals("1", rs.getString("root.vehicle.d1.s1"));
+        Assert.assertEquals("1", rs.getString("root.vehicle.d1.s2"));
+        Assert.assertEquals("1", rs.getString("root.vehicle.d1.s3"));
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      Assert.fail(e.getMessage());
+    }
+  }
+
   // todo: remove ignore after iotdb-3349 is merged
   @Ignore
   @Test
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSyntaxConventionIdentifierIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSyntaxConventionIdentifierIT.java
index c57b77ef95..7084bff368 100644
--- a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSyntaxConventionIdentifierIT.java
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSyntaxConventionIdentifierIT.java
@@ -58,7 +58,6 @@ public class IoTDBSyntaxConventionIdentifierIT {
     String[] createNodeNames = {
       "add",
       "as",
-      "between",
       "select",
       "drop_trigger",
       "REVOKE_USER_ROLE",
@@ -71,7 +70,6 @@ public class IoTDBSyntaxConventionIdentifierIT {
     String[] resultTimeseries = {
       "root.sg1.d1.add",
       "root.sg1.d1.as",
-      "root.sg1.d1.between",
       "root.sg1.d1.select",
       "root.sg1.d1.drop_trigger",
       "root.sg1.d1.REVOKE_USER_ROLE",
@@ -84,7 +82,6 @@ public class IoTDBSyntaxConventionIdentifierIT {
     String[] selectNodeNames = {
       "add",
       "as",
-      "between",
       "select",
       "drop_trigger",
       "REVOKE_USER_ROLE",
@@ -97,7 +94,6 @@ public class IoTDBSyntaxConventionIdentifierIT {
     String[] suffixInResultColumns = {
       "add",
       "as",
-      "between",
       "select",
       "drop_trigger",
       "REVOKE_USER_ROLE",
diff --git a/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBNestedQueryIT.java b/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBNestedQueryIT.java
index d56ad09184..7f613da051 100644
--- a/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBNestedQueryIT.java
+++ b/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBNestedQueryIT.java
@@ -592,6 +592,44 @@ public class IoTDBNestedQueryIT {
     }
   }
 
+  @Test
+  public void testBetweenExpression() {
+    try (Connection connection =
+            DriverManager.getConnection(
+                Config.IOTDB_URL_PREFIX + "127.0.0.1:6667/", "root", "root");
+        Statement statement = connection.createStatement()) {
+      int start = 1, end = 5;
+      String query = "SELECT * FROM root.vehicle.d1 WHERE s1 BETWEEN " + start + " AND " + end;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+
+      query =
+          "SELECT * FROM root.vehicle.d1 WHERE s1 NOT BETWEEN " // test not between
+              + (end + 1)
+              + " AND "
+              + ITERATION_TIMES;
+      try (ResultSet rs = statement.executeQuery(query)) {
+        for (int i = start; i <= end; i++) {
+          Assert.assertTrue(rs.next());
+          Assert.assertEquals(String.valueOf(i), rs.getString("Time"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s1"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s2"));
+          Assert.assertEquals(String.valueOf(i), rs.getString("root.vehicle.d1.s3"));
+        }
+      }
+    } catch (SQLException e) {
+      e.printStackTrace();
+      Assert.fail(e.getMessage());
+    }
+  }
+
   @Test
   public void testRegularLikeInExpressions() {
     try (Connection connection =
diff --git a/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBSyntaxConventionIdentifierIT.java b/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBSyntaxConventionIdentifierIT.java
index 2f2ec8923d..c962703832 100644
--- a/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBSyntaxConventionIdentifierIT.java
+++ b/integration/src/test/java/org/apache/iotdb/db/integration/IoTDBSyntaxConventionIdentifierIT.java
@@ -57,7 +57,6 @@ public class IoTDBSyntaxConventionIdentifierIT {
     String[] createNodeNames = {
       "add",
       "as",
-      "between",
       "select",
       "drop_trigger",
       "REVOKE_USER_ROLE",
@@ -70,7 +69,6 @@ public class IoTDBSyntaxConventionIdentifierIT {
     String[] resultTimeseries = {
       "root.sg1.d1.add",
       "root.sg1.d1.as",
-      "root.sg1.d1.between",
       "root.sg1.d1.select",
       "root.sg1.d1.drop_trigger",
       "root.sg1.d1.REVOKE_USER_ROLE",
@@ -83,7 +81,6 @@ public class IoTDBSyntaxConventionIdentifierIT {
     String[] selectNodeNames = {
       "add",
       "as",
-      "between",
       "select",
       "drop_trigger",
       "REVOKE_USER_ROLE",
@@ -96,7 +93,6 @@ public class IoTDBSyntaxConventionIdentifierIT {
     String[] suffixInResultColumns = {
       "add",
       "as",
-      "between",
       "select",
       "drop_trigger",
       "REVOKE_USER_ROLE",
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 229b6ebd3c..100e8f2390 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
@@ -42,6 +42,8 @@ import org.apache.iotdb.db.mpp.plan.expression.leaf.LeafOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
+import org.apache.iotdb.db.mpp.plan.expression.ternary.TernaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.IsNullExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.LikeExpression;
@@ -62,9 +64,14 @@ import java.util.List;
 import java.util.stream.Collectors;
 
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.cartesianProduct;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.checkConstantSatisfy;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.constructTimeFilter;
+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.reconstructBinaryExpressions;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructFunctionExpressions;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTernaryExpressions;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTimeSeriesOperands;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructUnaryExpressions;
 
@@ -77,7 +84,11 @@ public class ExpressionAnalyzer {
    * @param expression expression to be checked
    */
   public static void checkIsAllMeasurement(Expression expression) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      checkIsAllMeasurement(((TernaryExpression) expression).getFirstExpression());
+      checkIsAllMeasurement(((TernaryExpression) expression).getSecondExpression());
+      checkIsAllMeasurement(((TernaryExpression) expression).getThirdExpression());
+    } else if (expression instanceof BinaryExpression) {
       checkIsAllMeasurement(((BinaryExpression) expression).getLeftExpression());
       checkIsAllMeasurement(((BinaryExpression) expression).getRightExpression());
     } else if (expression instanceof UnaryExpression) {
@@ -108,7 +119,32 @@ public class ExpressionAnalyzer {
    * @return true if this expression is valid
    */
   public static ResultColumn.ColumnType identifyOutputColumnType(Expression expression) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      ResultColumn.ColumnType firstType =
+          identifyOutputColumnType(((TernaryExpression) expression).getFirstExpression());
+      ResultColumn.ColumnType secondType =
+          identifyOutputColumnType(((TernaryExpression) expression).getSecondExpression());
+      ResultColumn.ColumnType thirdType =
+          identifyOutputColumnType(((TernaryExpression) expression).getThirdExpression());
+      boolean rawFlag = false, aggregationFlag = false;
+      if (firstType == ResultColumn.ColumnType.RAW
+          || secondType == ResultColumn.ColumnType.RAW
+          || thirdType == ResultColumn.ColumnType.RAW) rawFlag = true;
+      if (firstType == ResultColumn.ColumnType.AGGREGATION
+          || secondType == ResultColumn.ColumnType.AGGREGATION
+          || thirdType == ResultColumn.ColumnType.AGGREGATION) aggregationFlag = true;
+      if (rawFlag && aggregationFlag)
+        throw new SemanticException(
+            "Raw data and aggregation result hybrid calculation is not supported.");
+      if (firstType == ResultColumn.ColumnType.CONSTANT
+          && secondType == ResultColumn.ColumnType.CONSTANT
+          && thirdType == ResultColumn.ColumnType.CONSTANT) {
+        throw new SemanticException("Constant column is not supported.");
+      }
+      if (firstType != ResultColumn.ColumnType.CONSTANT) return firstType;
+      if (secondType != ResultColumn.ColumnType.CONSTANT) return secondType;
+      return thirdType;
+    } else if (expression instanceof BinaryExpression) {
       ResultColumn.ColumnType leftType =
           identifyOutputColumnType(((BinaryExpression) expression).getLeftExpression());
       ResultColumn.ColumnType rightType =
@@ -162,7 +198,19 @@ public class ExpressionAnalyzer {
    */
   public static List<Expression> concatExpressionWithSuffixPaths(
       Expression expression, List<PartialPath> prefixPaths, PathPatternTree patternTree) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      List<Expression> firstExpressions =
+          concatExpressionWithSuffixPaths(
+              ((TernaryExpression) expression).getFirstExpression(), prefixPaths, patternTree);
+      List<Expression> secondExpressions =
+          concatExpressionWithSuffixPaths(
+              ((TernaryExpression) expression).getSecondExpression(), prefixPaths, patternTree);
+      List<Expression> thirdExpressions =
+          concatExpressionWithSuffixPaths(
+              ((TernaryExpression) expression).getThirdExpression(), prefixPaths, patternTree);
+      return reconstructTernaryExpressions(
+          expression, firstExpressions, secondExpressions, thirdExpressions);
+    } else if (expression instanceof BinaryExpression) {
       List<Expression> leftExpressions =
           concatExpressionWithSuffixPaths(
               ((BinaryExpression) expression).getLeftExpression(), prefixPaths, patternTree);
@@ -218,11 +266,25 @@ public class ExpressionAnalyzer {
    */
   public static void constructPatternTreeFromExpression(
       Expression predicate, List<PartialPath> prefixPaths, PathPatternTree patternTree) {
-    if (predicate instanceof BinaryExpression) {
+    if (predicate instanceof TernaryExpression) {
+      constructPatternTreeFromExpression(
+          ((TernaryExpression) predicate).getFirstExpression(), prefixPaths, patternTree);
+      constructPatternTreeFromExpression(
+          ((TernaryExpression) predicate).getSecondExpression(), prefixPaths, patternTree);
+      constructPatternTreeFromExpression(
+          ((TernaryExpression) predicate).getThirdExpression(), prefixPaths, patternTree);
+    } else if (predicate instanceof BinaryExpression) {
       constructPatternTreeFromExpression(
           ((BinaryExpression) predicate).getLeftExpression(), prefixPaths, patternTree);
       constructPatternTreeFromExpression(
           ((BinaryExpression) predicate).getRightExpression(), prefixPaths, patternTree);
+    } else if (predicate instanceof TernaryExpression) {
+      constructPatternTreeFromExpression(
+          ((TernaryExpression) predicate).getFirstExpression(), prefixPaths, patternTree);
+      constructPatternTreeFromExpression(
+          ((TernaryExpression) predicate).getSecondExpression(), prefixPaths, patternTree);
+      constructPatternTreeFromExpression(
+          ((TernaryExpression) predicate).getThirdExpression(), prefixPaths, patternTree);
     } else if (predicate instanceof UnaryExpression) {
       constructPatternTreeFromExpression(
           ((UnaryExpression) predicate).getExpression(), prefixPaths, patternTree);
@@ -257,7 +319,19 @@ public class ExpressionAnalyzer {
    */
   public static List<Expression> removeWildcardInExpression(
       Expression expression, SchemaTree schemaTree) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      List<Expression> firstExpressions =
+          removeWildcardInExpression(
+              ((TernaryExpression) expression).getFirstExpression(), schemaTree);
+      List<Expression> secondExpressions =
+          removeWildcardInExpression(
+              ((TernaryExpression) expression).getSecondExpression(), schemaTree);
+      List<Expression> thirdExpressions =
+          removeWildcardInExpression(
+              ((TernaryExpression) expression).getThirdExpression(), schemaTree);
+      return reconstructTernaryExpressions(
+          expression, firstExpressions, secondExpressions, thirdExpressions);
+    } else if (expression instanceof BinaryExpression) {
       List<Expression> leftExpressions =
           removeWildcardInExpression(
               ((BinaryExpression) expression).getLeftExpression(), schemaTree);
@@ -320,7 +394,28 @@ public class ExpressionAnalyzer {
       List<PartialPath> prefixPaths,
       SchemaTree schemaTree,
       TypeProvider typeProvider) {
-    if (predicate instanceof BinaryExpression) {
+    if (predicate instanceof TernaryExpression) {
+      List<Expression> firstExpressions =
+          removeWildcardInQueryFilter(
+              ((TernaryExpression) predicate).getFirstExpression(),
+              prefixPaths,
+              schemaTree,
+              typeProvider);
+      List<Expression> secondExpressions =
+          removeWildcardInQueryFilter(
+              ((TernaryExpression) predicate).getSecondExpression(),
+              prefixPaths,
+              schemaTree,
+              typeProvider);
+      List<Expression> thirdExpressions =
+          removeWildcardInQueryFilter(
+              ((TernaryExpression) predicate).getThirdExpression(),
+              prefixPaths,
+              schemaTree,
+              typeProvider);
+      return reconstructTernaryExpressions(
+          predicate, firstExpressions, secondExpressions, thirdExpressions);
+    } else if (predicate instanceof BinaryExpression) {
       List<Expression> leftExpressions =
           removeWildcardInQueryFilter(
               ((BinaryExpression) predicate).getLeftExpression(),
@@ -402,7 +497,28 @@ public class ExpressionAnalyzer {
       PartialPath devicePath,
       SchemaTree schemaTree,
       TypeProvider typeProvider) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      List<Expression> firstExpressions =
+          concatDeviceAndRemoveWildcard(
+              ((TernaryExpression) expression).getFirstExpression(),
+              devicePath,
+              schemaTree,
+              typeProvider);
+      List<Expression> secondExpressions =
+          concatDeviceAndRemoveWildcard(
+              ((TernaryExpression) expression).getSecondExpression(),
+              devicePath,
+              schemaTree,
+              typeProvider);
+      List<Expression> thirdExpressions =
+          concatDeviceAndRemoveWildcard(
+              ((TernaryExpression) expression).getThirdExpression(),
+              devicePath,
+              schemaTree,
+              typeProvider);
+      return reconstructTernaryExpressions(
+          expression, firstExpressions, secondExpressions, thirdExpressions);
+    } else if (expression instanceof BinaryExpression) {
       List<Expression> leftExpressions =
           concatDeviceAndRemoveWildcard(
               ((BinaryExpression) expression).getLeftExpression(),
@@ -467,7 +583,28 @@ public class ExpressionAnalyzer {
       PartialPath devicePath,
       SchemaTree schemaTree,
       TypeProvider typeProvider) {
-    if (predicate instanceof BinaryExpression) {
+    if (predicate instanceof TernaryExpression) {
+      List<Expression> firstExpressions =
+          removeWildcardInQueryFilterByDevice(
+              ((TernaryExpression) predicate).getFirstExpression(),
+              devicePath,
+              schemaTree,
+              typeProvider);
+      List<Expression> secondExpressions =
+          removeWildcardInQueryFilterByDevice(
+              ((TernaryExpression) predicate).getSecondExpression(),
+              devicePath,
+              schemaTree,
+              typeProvider);
+      List<Expression> thirdExpressions =
+          removeWildcardInQueryFilterByDevice(
+              ((TernaryExpression) predicate).getThirdExpression(),
+              devicePath,
+              schemaTree,
+              typeProvider);
+      return reconstructTernaryExpressions(
+          predicate, firstExpressions, secondExpressions, thirdExpressions);
+    } else if (predicate instanceof BinaryExpression) {
       List<Expression> leftExpressions =
           removeWildcardInQueryFilterByDevice(
               ((BinaryExpression) predicate).getLeftExpression(),
@@ -590,6 +727,29 @@ public class ExpressionAnalyzer {
         return new Pair<>(timeInRightFilter, false);
       }
       return new Pair<>(null, true);
+    } else if (predicate instanceof LikeExpression || predicate instanceof RegularExpression) {
+      return new Pair<>(null, true);
+    } else if (predicate instanceof BetweenExpression) {
+      Expression firstExpression = ((TernaryExpression) predicate).getFirstExpression();
+      Expression secondExpression = ((TernaryExpression) predicate).getSecondExpression();
+      Expression thirdExpression = ((TernaryExpression) predicate).getThirdExpression();
+      if (firstExpression instanceof TimestampOperand) {
+        return getPairFromBetweenTimeFirst(
+            secondExpression, thirdExpression, ((BetweenExpression) predicate).isNotBetween());
+      } else if (secondExpression instanceof TimestampOperand) {
+        if (checkConstantSatisfy(firstExpression, thirdExpression)) {
+          return getPairFromBetweenTimeSecond((BetweenExpression) predicate, firstExpression);
+        } else {
+          return new Pair<>(null, true); // TODO return Filter.True/False
+        }
+      } else if (thirdExpression instanceof TimestampOperand) {
+        if (checkConstantSatisfy(secondExpression, firstExpression)) {
+          return getPairFromBetweenTimeThird((BetweenExpression) predicate, firstExpression);
+        } else {
+          return new Pair<>(null, true); // TODO return Filter.True/False
+        }
+      }
+      return new Pair<>(null, true);
     } else if (predicate instanceof IsNullExpression) {
       return new Pair<>(null, true);
     } else if (predicate instanceof InExpression) {
@@ -603,8 +763,6 @@ public class ExpressionAnalyzer {
             false);
       }
       return new Pair<>(null, true);
-    } else if (predicate instanceof LikeExpression || predicate instanceof RegularExpression) {
-      return new Pair<>(null, true);
     } else {
       throw new IllegalArgumentException(
           "unsupported expression type: " + predicate.getExpressionType());
@@ -621,7 +779,19 @@ public class ExpressionAnalyzer {
    */
   public static List<Expression> searchSourceExpressions(
       Expression expression, boolean isRawDataSource) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      List<Expression> resultExpressions = new ArrayList<>();
+      resultExpressions.addAll(
+          searchSourceExpressions(
+              ((TernaryExpression) expression).getFirstExpression(), isRawDataSource));
+      resultExpressions.addAll(
+          searchSourceExpressions(
+              ((TernaryExpression) expression).getSecondExpression(), isRawDataSource));
+      resultExpressions.addAll(
+          searchSourceExpressions(
+              ((TernaryExpression) expression).getThirdExpression(), isRawDataSource));
+      return resultExpressions;
+    } else if (expression instanceof BinaryExpression) {
       List<Expression> resultExpressions = new ArrayList<>();
       resultExpressions.addAll(
           searchSourceExpressions(
@@ -659,7 +829,16 @@ public class ExpressionAnalyzer {
    * @return searched aggregate functions list
    */
   public static List<Expression> searchAggregationExpressions(Expression expression) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      List<Expression> resultExpressions = new ArrayList<>();
+      resultExpressions.addAll(
+          searchAggregationExpressions(((TernaryExpression) expression).getFirstExpression()));
+      resultExpressions.addAll(
+          searchAggregationExpressions(((TernaryExpression) expression).getSecondExpression()));
+      resultExpressions.addAll(
+          searchAggregationExpressions(((TernaryExpression) expression).getThirdExpression()));
+      return resultExpressions;
+    } else if (expression instanceof BinaryExpression) {
       List<Expression> resultExpressions = new ArrayList<>();
       resultExpressions.addAll(
           searchAggregationExpressions(((BinaryExpression) expression).getLeftExpression()));
@@ -688,7 +867,11 @@ public class ExpressionAnalyzer {
 
   /** Update typeProvider by expression. */
   public static void updateTypeProvider(Expression expression, TypeProvider typeProvider) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      updateTypeProvider(((TernaryExpression) expression).getFirstExpression(), typeProvider);
+      updateTypeProvider(((TernaryExpression) expression).getSecondExpression(), typeProvider);
+      updateTypeProvider(((TernaryExpression) expression).getThirdExpression(), typeProvider);
+    } else if (expression instanceof BinaryExpression) {
       updateTypeProvider(((BinaryExpression) expression).getLeftExpression(), typeProvider);
       updateTypeProvider(((BinaryExpression) expression).getRightExpression(), typeProvider);
     } else if (expression instanceof UnaryExpression) {
@@ -718,7 +901,20 @@ public class ExpressionAnalyzer {
    * @return expression after removing alias
    */
   public static Expression removeAliasFromExpression(Expression expression) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      Expression firstExpression =
+          removeAliasFromExpression(((TernaryExpression) expression).getFirstExpression());
+      Expression secondExpression =
+          removeAliasFromExpression(((TernaryExpression) expression).getFirstExpression());
+      Expression thirdExpression =
+          removeAliasFromExpression(((TernaryExpression) expression).getFirstExpression());
+      return reconstructTernaryExpressions(
+              expression,
+              Collections.singletonList(firstExpression),
+              Collections.singletonList(secondExpression),
+              Collections.singletonList(thirdExpression))
+          .get(0);
+    } else if (expression instanceof BinaryExpression) {
       Expression leftExpression =
           removeAliasFromExpression(((BinaryExpression) expression).getLeftExpression());
       Expression rightExpression =
@@ -765,7 +961,9 @@ public class ExpressionAnalyzer {
 
   /** Check for arithmetic expression, logical expression, UDF. Returns true if it exists. */
   public static boolean checkIsNeedTransform(Expression expression) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      return true;
+    } else if (expression instanceof BinaryExpression) {
       return true;
     } else if (expression instanceof UnaryExpression) {
       return true;
@@ -784,7 +982,19 @@ public class ExpressionAnalyzer {
   /////////////////////////////////////////////////////////////////////////////////////////////////
 
   public static String getDeviceNameInSourceExpression(Expression expression) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      String DeviceName =
+          getDeviceNameInSourceExpression(((TernaryExpression) expression).getFirstExpression());
+      if (DeviceName == null) {
+        DeviceName =
+            getDeviceNameInSourceExpression(((TernaryExpression) expression).getFirstExpression());
+      }
+      if (DeviceName == null) {
+        DeviceName =
+            getDeviceNameInSourceExpression(((TernaryExpression) expression).getThirdExpression());
+      }
+      return DeviceName;
+    } else if (expression instanceof BinaryExpression) {
       String leftDeviceName =
           getDeviceNameInSourceExpression(((BinaryExpression) expression).getLeftExpression());
       if (leftDeviceName != null) {
@@ -806,7 +1016,20 @@ public class ExpressionAnalyzer {
   }
 
   public static Expression getMeasurementExpression(Expression expression) {
-    if (expression instanceof BinaryExpression) {
+    if (expression instanceof TernaryExpression) {
+      Expression firstExpression =
+          getMeasurementExpression(((TernaryExpression) expression).getFirstExpression());
+      Expression secondExpression =
+          getMeasurementExpression(((TernaryExpression) expression).getFirstExpression());
+      Expression thirdExpression =
+          getMeasurementExpression(((TernaryExpression) expression).getFirstExpression());
+      return reconstructTernaryExpressions(
+              expression,
+              Collections.singletonList(firstExpression),
+              Collections.singletonList(secondExpression),
+              Collections.singletonList(thirdExpression))
+          .get(0);
+    } else if (expression instanceof BinaryExpression) {
       Expression leftExpression =
           getMeasurementExpression(((BinaryExpression) expression).getLeftExpression());
       Expression rightExpression =
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 2e6b3ea69b..f1b0df13ef 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
@@ -39,6 +39,7 @@ import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.IsNullExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.LikeExpression;
@@ -49,6 +50,7 @@ import org.apache.iotdb.db.mpp.plan.expression.unary.UnaryExpression;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.read.filter.TimeFilter;
 import org.apache.iotdb.tsfile.read.filter.basic.Filter;
+import org.apache.iotdb.tsfile.utils.Pair;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -176,6 +178,29 @@ public class ExpressionUtils {
     return resultExpressions;
   }
 
+  public static List<Expression> reconstructTernaryExpressions(
+      Expression expression,
+      List<Expression> firstExpressions,
+      List<Expression> secondExpressions,
+      List<Expression> thirdExpressions) {
+    List<Expression> resultExpressions = new ArrayList<>();
+    for (Expression fe : firstExpressions) {
+      for (Expression se : secondExpressions)
+        for (Expression te : thirdExpressions) {
+          switch (expression.getExpressionType()) {
+            case BETWEEN:
+              resultExpressions.add(
+                  new BetweenExpression(
+                      fe, se, te, ((BetweenExpression) expression).isNotBetween()));
+              break;
+            default:
+              throw new UnsupportedOperationException();
+          }
+        }
+    }
+    return resultExpressions;
+  }
+
   public static <T> void cartesianProduct(
       List<List<T>> dimensionValue, List<List<T>> resultList, int layer, List<T> currentList) {
     if (layer < dimensionValue.size() - 1) {
@@ -227,6 +252,54 @@ public class ExpressionUtils {
     return null;
   }
 
+  public static Pair<Filter, Boolean> getPairFromBetweenTimeFirst(
+      Expression firstExpression, Expression secondExpression, boolean not) {
+    if (firstExpression instanceof ConstantOperand
+        && secondExpression instanceof ConstantOperand
+        && ((ConstantOperand) firstExpression).getDataType() == TSDataType.INT64
+        && ((ConstantOperand) secondExpression).getDataType() == TSDataType.INT64) {
+      long value1 = Long.parseLong(((ConstantOperand) firstExpression).getValueString());
+      long value2 = Long.parseLong(((ConstantOperand) secondExpression).getValueString());
+      return new Pair<>(TimeFilter.between(value1, value2, not), false);
+    } else {
+      return new Pair<>(null, true);
+    }
+  }
+
+  public static Pair<Filter, Boolean> getPairFromBetweenTimeSecond(
+      BetweenExpression predicate, Expression expression) {
+    if (predicate.isNotBetween()) {
+      return new Pair<>(
+          TimeFilter.gt(Long.parseLong(((ConstantOperand) expression).getValueString())), false);
+
+    } else {
+      return new Pair<>(
+          TimeFilter.ltEq(Long.parseLong(((ConstantOperand) expression).getValueString())), false);
+    }
+  }
+
+  public static Pair<Filter, Boolean> getPairFromBetweenTimeThird(
+      BetweenExpression predicate, Expression expression) {
+    if (predicate.isNotBetween()) {
+      return new Pair<>(
+          TimeFilter.lt(Long.parseLong(((ConstantOperand) expression).getValueString())), false);
+
+    } else {
+      return new Pair<>(
+          TimeFilter.gtEq(Long.parseLong(((ConstantOperand) expression).getValueString())), false);
+    }
+  }
+
+  public static boolean checkConstantSatisfy(
+      Expression firstExpression, Expression secondExpression) {
+    return firstExpression.isConstantOperand()
+        && secondExpression.isConstantOperand()
+        && ((ConstantOperand) firstExpression).getDataType() == TSDataType.INT64
+        && ((ConstantOperand) secondExpression).getDataType() == TSDataType.INT64
+        && (Long.parseLong(((ConstantOperand) firstExpression).getValueString())
+            <= Long.parseLong(((ConstantOperand) secondExpression).getValueString()));
+  }
+
   public static Expression constructQueryFilter(List<Expression> expressions) {
     if (expressions.size() == 1) {
       return expressions.get(0);
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/Expression.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/Expression.java
index 5ce5a38dbe..3dc95b9485 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/Expression.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/Expression.java
@@ -41,6 +41,7 @@ import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.IsNullExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.LikeExpression;
@@ -381,9 +382,9 @@ public abstract class Expression {
         expression = new IsNullExpression(byteBuffer);
         break;
 
-        /*case 16:
+      case 16:
         expression = new BetweenExpression(byteBuffer);
-        break;*/
+        break;
 
       case 17:
         expression = new InExpression(byteBuffer);
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionType.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionType.java
index b8118709e4..0bcec3a15c 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionType.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionType.java
@@ -47,7 +47,7 @@ public enum ExpressionType {
 
   IS_NULL((short) 15, (short) 475),
 
-  // BETWEEN((short) 16, (short) 450),
+  BETWEEN((short) 16, (short) 450),
 
   IN((short) 17, (short) 400),
 
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ternary/BetweenExpression.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ternary/BetweenExpression.java
new file mode 100644
index 0000000000..294bf2edf9
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ternary/BetweenExpression.java
@@ -0,0 +1,110 @@
+/*
+ *
+ *  * 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.expression.ternary;
+
+import org.apache.iotdb.db.mpp.plan.analyze.TypeProvider;
+import org.apache.iotdb.db.mpp.plan.expression.Expression;
+import org.apache.iotdb.db.mpp.plan.expression.ExpressionType;
+import org.apache.iotdb.db.mpp.transformation.api.LayerPointReader;
+import org.apache.iotdb.db.mpp.transformation.dag.transformer.ternary.BetweenTransformer;
+import org.apache.iotdb.db.mpp.transformation.dag.transformer.ternary.TernaryTransformer;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class BetweenExpression extends TernaryExpression {
+  private final boolean isNotBetween;
+
+  public boolean isNotBetween() {
+    return isNotBetween;
+  }
+
+  public BetweenExpression(
+      Expression firstExpression,
+      Expression secondExpression,
+      Expression thirdExpression,
+      boolean isNotBetween) {
+    super(firstExpression, secondExpression, thirdExpression);
+    this.isNotBetween = isNotBetween;
+  }
+
+  public BetweenExpression(
+      Expression firstExpression, Expression secondExpression, Expression thirdExpression) {
+    super(firstExpression, secondExpression, thirdExpression);
+    this.isNotBetween = false;
+  }
+
+  public BetweenExpression(ByteBuffer byteBuffer) {
+    super(byteBuffer);
+    this.isNotBetween = ReadWriteIOUtils.readBool(byteBuffer);
+  }
+
+  @Override
+  protected TernaryTransformer constructTransformer(
+      LayerPointReader firstParentLayerPointReader,
+      LayerPointReader secondParentLayerPointReader,
+      LayerPointReader thirdParentLayerPointReader) {
+    return new BetweenTransformer(
+        firstParentLayerPointReader,
+        secondParentLayerPointReader,
+        thirdParentLayerPointReader,
+        isNotBetween);
+  }
+
+  @Override
+  protected String operator() {
+    return "between";
+  }
+
+  @Override
+  public TSDataType inferTypes(TypeProvider typeProvider) {
+    return TSDataType.BOOLEAN;
+  }
+
+  @Override
+  protected String getExpressionStringInternal() {
+    return firstExpression + " BETWEEN " + secondExpression + " AND " + thirdExpression;
+  }
+
+  @Override
+  public ExpressionType getExpressionType() {
+    return ExpressionType.BETWEEN;
+  }
+
+  protected void serialize(ByteBuffer byteBuffer) {
+    super.serialize(byteBuffer);
+    ReadWriteIOUtils.write(isNotBetween, byteBuffer);
+  }
+
+  @Override
+  protected void serialize(DataOutputStream stream) throws IOException {
+    super.serialize(stream);
+    ReadWriteIOUtils.write(isNotBetween, stream);
+  }
+
+  public Expression getExpression() {
+    return this;
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ternary/TernaryExpression.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ternary/TernaryExpression.java
new file mode 100644
index 0000000000..f1498ed804
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ternary/TernaryExpression.java
@@ -0,0 +1,336 @@
+/*
+ *
+ *  * 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.expression.ternary;
+
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.db.exception.query.LogicalOptimizeException;
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.mpp.plan.analyze.TypeProvider;
+import org.apache.iotdb.db.mpp.plan.expression.Expression;
+import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.InputLocation;
+import org.apache.iotdb.db.mpp.transformation.api.LayerPointReader;
+import org.apache.iotdb.db.mpp.transformation.dag.input.QueryDataSetInputLayer;
+import org.apache.iotdb.db.mpp.transformation.dag.intermediate.IntermediateLayer;
+import org.apache.iotdb.db.mpp.transformation.dag.intermediate.SingleInputColumnMultiReferenceIntermediateLayer;
+import org.apache.iotdb.db.mpp.transformation.dag.intermediate.SingleInputColumnSingleReferenceIntermediateLayer;
+import org.apache.iotdb.db.mpp.transformation.dag.memory.LayerMemoryAssigner;
+import org.apache.iotdb.db.mpp.transformation.dag.transformer.Transformer;
+import org.apache.iotdb.db.mpp.transformation.dag.transformer.ternary.TernaryTransformer;
+import org.apache.iotdb.db.mpp.transformation.dag.udf.UDTFContext;
+import org.apache.iotdb.db.mpp.transformation.dag.udf.UDTFExecutor;
+import org.apache.iotdb.db.qp.physical.crud.UDTFPlan;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public abstract class TernaryExpression extends Expression {
+  protected final Expression firstExpression;
+  protected final Expression secondExpression;
+  protected final Expression thirdExpression;
+  // protected final boolean isNot = false;
+
+  public Expression getFirstExpression() {
+    return firstExpression;
+  }
+
+  public Expression getSecondExpression() {
+    return secondExpression;
+  }
+
+  public Expression getThirdExpression() {
+    return thirdExpression;
+  }
+
+  protected TernaryExpression(
+      Expression firstExpression, Expression secondExpression, Expression thirdExpression) {
+    this.firstExpression = firstExpression;
+    this.secondExpression = secondExpression;
+    this.thirdExpression = thirdExpression;
+  }
+
+  protected TernaryExpression(ByteBuffer byteBuffer) {
+    this.firstExpression = Expression.deserialize(byteBuffer);
+    this.secondExpression = Expression.deserialize(byteBuffer);
+    this.thirdExpression = Expression.deserialize(byteBuffer);
+  }
+
+  @Override
+  public boolean isConstantOperandInternal() {
+    return firstExpression.isConstantOperand()
+        && secondExpression.isConstantOperand()
+        && thirdExpression.isConstantOperand();
+  }
+
+  @Override
+  public boolean isTimeSeriesGeneratingFunctionExpression() {
+    return !isUserDefinedAggregationFunctionExpression();
+  }
+
+  @Override
+  public boolean isUserDefinedAggregationFunctionExpression() {
+    return firstExpression.isBuiltInAggregationFunctionExpression()
+        || secondExpression.isBuiltInAggregationFunctionExpression()
+        || thirdExpression.isUserDefinedAggregationFunctionExpression();
+  }
+
+  @Override
+  public List<Expression> getExpressions() {
+    return Arrays.asList(firstExpression, secondExpression, thirdExpression);
+  }
+
+  @Override
+  public final void concat(List<PartialPath> prefixPaths, List<Expression> resultExpressions) {
+    List<Expression> firstExpressions = new ArrayList<>();
+    firstExpression.concat(prefixPaths, firstExpressions);
+
+    List<Expression> secondExpressions = new ArrayList<>();
+    secondExpression.concat(prefixPaths, secondExpressions);
+
+    List<Expression> thirdExpressions = new ArrayList<>();
+    secondExpression.concat(prefixPaths, thirdExpressions);
+
+    reconstruct(firstExpressions, secondExpressions, thirdExpressions, resultExpressions);
+  }
+
+  @Override
+  public final void removeWildcards(
+      org.apache.iotdb.db.qp.utils.WildcardsRemover wildcardsRemover,
+      List<Expression> resultExpressions)
+      throws LogicalOptimizeException {
+    List<Expression> firstExpressions = new ArrayList<>();
+    firstExpression.removeWildcards(wildcardsRemover, firstExpressions);
+
+    List<Expression> secondExpressions = new ArrayList<>();
+    secondExpression.removeWildcards(wildcardsRemover, secondExpressions);
+
+    List<Expression> thirdExpressions = new ArrayList<>();
+    thirdExpression.removeWildcards(wildcardsRemover, secondExpressions);
+    reconstruct(firstExpressions, secondExpressions, thirdExpressions, resultExpressions);
+  }
+
+  private void reconstruct(
+      List<Expression> firstExpressions,
+      List<Expression> secondExpressions,
+      List<Expression> thirdExpressions,
+      List<Expression> resultExpressions) {
+    for (Expression fe : firstExpressions) {
+      for (Expression se : secondExpressions)
+        for (Expression te : thirdExpressions) {
+          switch (operator()) {
+            case "between":
+              resultExpressions.add(new BetweenExpression(fe, se, te));
+              break;
+            default:
+              throw new UnsupportedOperationException();
+          }
+        }
+    }
+  }
+
+  @Override
+  public void collectPaths(Set<PartialPath> pathSet) {
+    firstExpression.collectPaths(pathSet);
+    secondExpression.collectPaths(pathSet);
+    thirdExpression.collectPaths(pathSet);
+  }
+
+  @Override
+  public void constructUdfExecutors(
+      Map<String, UDTFExecutor> expressionName2Executor, ZoneId zoneId) {
+    firstExpression.constructUdfExecutors(expressionName2Executor, zoneId);
+    secondExpression.constructUdfExecutors(expressionName2Executor, zoneId);
+    thirdExpression.constructUdfExecutors(expressionName2Executor, zoneId);
+  }
+
+  @Override
+  public void bindInputLayerColumnIndexWithExpression(UDTFPlan udtfPlan) {
+    firstExpression.bindInputLayerColumnIndexWithExpression(udtfPlan);
+    secondExpression.bindInputLayerColumnIndexWithExpression(udtfPlan);
+    thirdExpression.bindInputLayerColumnIndexWithExpression(udtfPlan);
+    inputColumnIndex = udtfPlan.getReaderIndexByExpressionName(toString());
+  }
+
+  @Override
+  public final void bindInputLayerColumnIndexWithExpression(
+      Map<String, List<InputLocation>> inputLocations) {
+    firstExpression.bindInputLayerColumnIndexWithExpression(inputLocations);
+    secondExpression.bindInputLayerColumnIndexWithExpression(inputLocations);
+    thirdExpression.bindInputLayerColumnIndexWithExpression(inputLocations);
+
+    final String digest = toString();
+    if (inputLocations.containsKey(digest)) {
+      inputColumnIndex = inputLocations.get(digest).get(0).getValueColumnIndex();
+    }
+  }
+
+  @Override
+  public void updateStatisticsForMemoryAssigner(LayerMemoryAssigner memoryAssigner) {
+    firstExpression.updateStatisticsForMemoryAssigner(memoryAssigner);
+    secondExpression.updateStatisticsForMemoryAssigner(memoryAssigner);
+    thirdExpression.updateStatisticsForMemoryAssigner(memoryAssigner);
+    memoryAssigner.increaseExpressionReference(this);
+  }
+
+  @Override
+  public IntermediateLayer constructIntermediateLayer(
+      long queryId,
+      UDTFContext udtfContext,
+      QueryDataSetInputLayer rawTimeSeriesInputLayer,
+      Map<Expression, IntermediateLayer> expressionIntermediateLayerMap,
+      Map<Expression, TSDataType> expressionDataTypeMap,
+      LayerMemoryAssigner memoryAssigner)
+      throws QueryProcessException, IOException {
+    if (!expressionIntermediateLayerMap.containsKey(this)) {
+      float memoryBudgetInMB = memoryAssigner.assign();
+
+      IntermediateLayer firstParentIntermediateLayer =
+          firstExpression.constructIntermediateLayer(
+              queryId,
+              udtfContext,
+              rawTimeSeriesInputLayer,
+              expressionIntermediateLayerMap,
+              expressionDataTypeMap,
+              memoryAssigner);
+      IntermediateLayer secondParentIntermediateLayer =
+          secondExpression.constructIntermediateLayer(
+              queryId,
+              udtfContext,
+              rawTimeSeriesInputLayer,
+              expressionIntermediateLayerMap,
+              expressionDataTypeMap,
+              memoryAssigner);
+      IntermediateLayer thirdParentIntermediateLayer =
+          thirdExpression.constructIntermediateLayer(
+              queryId,
+              udtfContext,
+              rawTimeSeriesInputLayer,
+              expressionIntermediateLayerMap,
+              expressionDataTypeMap,
+              memoryAssigner);
+      Transformer transformer =
+          constructTransformer(
+              firstParentIntermediateLayer.constructPointReader(),
+              secondParentIntermediateLayer.constructPointReader(),
+              thirdParentIntermediateLayer.constructPointReader());
+      expressionDataTypeMap.put(this, transformer.getDataType());
+
+      // SingleInputColumnMultiReferenceIntermediateLayer doesn't support ConstantLayerPointReader
+      // yet. And since a ConstantLayerPointReader won't produce too much IO,
+      // SingleInputColumnSingleReferenceIntermediateLayer could be a better choice.
+      expressionIntermediateLayerMap.put(
+          this,
+          memoryAssigner.getReference(this) == 1 || isConstantOperand()
+              ? new SingleInputColumnSingleReferenceIntermediateLayer(
+                  this, queryId, memoryBudgetInMB, transformer)
+              : new SingleInputColumnMultiReferenceIntermediateLayer(
+                  this, queryId, memoryBudgetInMB, transformer));
+    }
+
+    return expressionIntermediateLayerMap.get(this);
+  }
+
+  @Override
+  public IntermediateLayer constructIntermediateLayer(
+      long queryId,
+      UDTFContext udtfContext,
+      QueryDataSetInputLayer rawTimeSeriesInputLayer,
+      Map<Expression, IntermediateLayer> expressionIntermediateLayerMap,
+      TypeProvider typeProvider,
+      LayerMemoryAssigner memoryAssigner)
+      throws QueryProcessException, IOException {
+    if (!expressionIntermediateLayerMap.containsKey(this)) {
+      float memoryBudgetInMB = memoryAssigner.assign();
+
+      IntermediateLayer firstParentIntermediateLayer =
+          firstExpression.constructIntermediateLayer(
+              queryId,
+              udtfContext,
+              rawTimeSeriesInputLayer,
+              expressionIntermediateLayerMap,
+              typeProvider,
+              memoryAssigner);
+      IntermediateLayer secondParentIntermediateLayer =
+          secondExpression.constructIntermediateLayer(
+              queryId,
+              udtfContext,
+              rawTimeSeriesInputLayer,
+              expressionIntermediateLayerMap,
+              typeProvider,
+              memoryAssigner);
+      IntermediateLayer thirdParentIntermediateLayer =
+          thirdExpression.constructIntermediateLayer(
+              queryId,
+              udtfContext,
+              rawTimeSeriesInputLayer,
+              expressionIntermediateLayerMap,
+              typeProvider,
+              memoryAssigner);
+      Transformer transformer =
+          constructTransformer(
+              firstParentIntermediateLayer.constructPointReader(),
+              secondParentIntermediateLayer.constructPointReader(),
+              thirdParentIntermediateLayer.constructPointReader());
+
+      // SingleInputColumnMultiReferenceIntermediateLayer doesn't support ConstantLayerPointReader
+      // yet. And since a ConstantLayerPointReader won't produce too much IO,
+      // SingleInputColumnSingleReferenceIntermediateLayer could be a better choice.
+      expressionIntermediateLayerMap.put(
+          this,
+          memoryAssigner.getReference(this) == 1 || isConstantOperand()
+              ? new SingleInputColumnSingleReferenceIntermediateLayer(
+                  this, queryId, memoryBudgetInMB, transformer)
+              : new SingleInputColumnMultiReferenceIntermediateLayer(
+                  this, queryId, memoryBudgetInMB, transformer));
+    }
+
+    return expressionIntermediateLayerMap.get(this);
+  }
+
+  protected abstract TernaryTransformer constructTransformer(
+      LayerPointReader firstParentLayerPointReader,
+      LayerPointReader secondParentLayerPointReader,
+      LayerPointReader thirdParentLayerPointReader);
+
+  protected abstract String operator();
+
+  @Override
+  protected void serialize(ByteBuffer byteBuffer) {
+    Expression.serialize(firstExpression, byteBuffer);
+    Expression.serialize(secondExpression, byteBuffer);
+    Expression.serialize(thirdExpression, byteBuffer);
+  }
+
+  @Override
+  protected void serialize(DataOutputStream stream) throws IOException {
+    Expression.serialize(firstExpression, stream);
+    Expression.serialize(secondExpression, stream);
+    Expression.serialize(thirdExpression, stream);
+  }
+}
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 4418d11349..4812d928fb 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
@@ -51,6 +51,7 @@ import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.IsNullExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.LikeExpression;
@@ -1993,6 +1994,20 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
       return parseIsNullExpression(context, inWithoutNull);
     }
 
+    if (context.firstExpression != null
+        && context.secondExpression != null
+        && context.thirdExpression != null) {
+      Expression firstExpression = parseExpression(context.firstExpression, inWithoutNull);
+      Expression secondExpression = parseExpression(context.secondExpression, inWithoutNull);
+      Expression thirdExpression = parseExpression(context.thirdExpression, inWithoutNull);
+
+      if (context.OPERATOR_BETWEEN() != null) {
+        return new BetweenExpression(
+            firstExpression, secondExpression, thirdExpression, context.OPERATOR_NOT() != null);
+      }
+      throw new UnsupportedOperationException();
+    }
+
     if (context.unaryBeforeInExpression != null) {
       return parseInExpression(context, inWithoutNull);
     }
@@ -2108,8 +2123,6 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
     } else if (constantContext.dateExpression() != null) {
       return new ConstantOperand(
           TSDataType.INT64, String.valueOf(parseDateExpression(constantContext.dateExpression())));
-      /*} else if (constantContext.NULL_LITERAL() != null) {
-      return new ConstantOperand(TSDataType.TEXT, "");*/
     } else {
       throw new SQLParserException("Unsupported constant operand: " + text);
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/Transformer.java b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/Transformer.java
index 28fb5f9cae..3bd7f927dd 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/Transformer.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/Transformer.java
@@ -25,6 +25,7 @@ import org.apache.iotdb.db.mpp.transformation.api.YieldableState;
 import org.apache.iotdb.tsfile.utils.Binary;
 
 import java.io.IOException;
+import java.util.Objects;
 
 public abstract class Transformer implements LayerPointReader {
 
@@ -118,4 +119,24 @@ public abstract class Transformer implements LayerPointReader {
   public final boolean isCurrentNull() {
     return currentNull;
   }
+
+  protected static int compare(CharSequence cs1, CharSequence cs2) {
+    if (Objects.requireNonNull(cs1) == Objects.requireNonNull(cs2)) {
+      return 0;
+    }
+
+    if (cs1.getClass() == cs2.getClass() && cs1 instanceof Comparable) {
+      return ((Comparable<Object>) cs1).compareTo(cs2);
+    }
+
+    for (int i = 0, len = Math.min(cs1.length(), cs2.length()); i < len; i++) {
+      char a = cs1.charAt(i);
+      char b = cs2.charAt(i);
+      if (a != b) {
+        return a - b;
+      }
+    }
+
+    return cs1.length() - cs2.length();
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/ternary/BetweenTransformer.java b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/ternary/BetweenTransformer.java
new file mode 100644
index 0000000000..70f8ef0bba
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/ternary/BetweenTransformer.java
@@ -0,0 +1,67 @@
+/*
+ *
+ *  * 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.transformation.dag.transformer.ternary;
+
+import org.apache.iotdb.db.mpp.transformation.api.LayerPointReader;
+
+public class BetweenTransformer extends CompareTernaryTransformer {
+
+  boolean isNotBetween;
+
+  public BetweenTransformer(
+      LayerPointReader firstPointReader,
+      LayerPointReader secondPointReader,
+      LayerPointReader thirdPointReader,
+      boolean isNotBetween) {
+    super(firstPointReader, secondPointReader, thirdPointReader);
+    this.isNotBetween = isNotBetween;
+  }
+
+  @Override
+  protected Evaluator constructNumberEvaluator() {
+    return () ->
+        ((Double.compare(
+                        castCurrentValueToDoubleOperand(firstPointReader, firstPointReaderDataType),
+                        castCurrentValueToDoubleOperand(
+                            secondPointReader, secondPointReaderDataType))
+                    >= 0)
+                && (Double.compare(
+                        castCurrentValueToDoubleOperand(firstPointReader, firstPointReaderDataType),
+                        castCurrentValueToDoubleOperand(thirdPointReader, thirdPointReaderDataType))
+                    <= 0))
+            ^ isNotBetween;
+  }
+
+  @Override
+  protected Evaluator constructTextEvaluator() {
+    return () ->
+        ((compare(
+                        firstPointReader.currentBinary().getStringValue(),
+                        secondPointReader.currentBinary().getStringValue())
+                    >= 0)
+                && (compare(
+                        firstPointReader.currentBinary().getStringValue(),
+                        thirdPointReader.currentBinary().getStringValue())
+                    <= 0))
+            ^ isNotBetween;
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/ternary/CompareTernaryTransformer.java b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/ternary/CompareTernaryTransformer.java
new file mode 100644
index 0000000000..e53c10f5c6
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/ternary/CompareTernaryTransformer.java
@@ -0,0 +1,86 @@
+/*
+ *
+ *  * 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.transformation.dag.transformer.ternary;
+
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.mpp.transformation.api.LayerPointReader;
+import org.apache.iotdb.tsfile.exception.write.UnSupportedDataTypeException;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import java.io.IOException;
+
+public abstract class CompareTernaryTransformer extends TernaryTransformer {
+
+  @FunctionalInterface
+  public interface Evaluator {
+
+    boolean evaluate() throws QueryProcessException, IOException;
+  }
+
+  protected final Evaluator evaluator;
+
+  protected CompareTernaryTransformer(
+      LayerPointReader firstPointReader,
+      LayerPointReader secondPointReader,
+      LayerPointReader thirdPointReader)
+      throws UnSupportedDataTypeException {
+    super(firstPointReader, secondPointReader, thirdPointReader);
+    evaluator =
+        TSDataType.TEXT.equals(firstPointReaderDataType)
+            ? constructTextEvaluator()
+            : constructNumberEvaluator();
+  }
+
+  protected abstract Evaluator constructNumberEvaluator();
+
+  protected abstract Evaluator constructTextEvaluator();
+
+  @Override
+  protected final void checkType() {
+    if ((firstPointReaderDataType).equals(secondPointReaderDataType)
+        && (firstPointReaderDataType).equals(thirdPointReaderDataType)) {
+      return;
+    }
+
+    if (firstPointReaderDataType.equals(TSDataType.BOOLEAN)
+        || secondPointReaderDataType.equals(TSDataType.BOOLEAN)
+        || thirdPointReaderDataType.equals(TSDataType.BOOLEAN)) {
+      throw new UnSupportedDataTypeException(TSDataType.BOOLEAN.toString());
+    }
+
+    if (firstPointReaderDataType.equals(TSDataType.TEXT)
+        || secondPointReaderDataType.equals(TSDataType.TEXT)
+        || thirdPointReaderDataType.equals(TSDataType.TEXT)) {
+      throw new UnSupportedDataTypeException(TSDataType.TEXT.toString());
+    }
+  }
+
+  @Override
+  protected final void transformAndCache() throws QueryProcessException, IOException {
+    cachedBoolean = evaluator.evaluate();
+  }
+
+  @Override
+  public TSDataType getDataType() {
+    return TSDataType.BOOLEAN;
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/ternary/TernaryTransformer.java b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/ternary/TernaryTransformer.java
new file mode 100644
index 0000000000..1e72272cfc
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/transformer/ternary/TernaryTransformer.java
@@ -0,0 +1,265 @@
+/*
+ *
+ *  * 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.transformation.dag.transformer.ternary;
+
+import org.apache.iotdb.db.exception.query.QueryProcessException;
+import org.apache.iotdb.db.mpp.transformation.api.LayerPointReader;
+import org.apache.iotdb.db.mpp.transformation.api.YieldableState;
+import org.apache.iotdb.db.mpp.transformation.dag.transformer.Transformer;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+
+import java.io.IOException;
+
+public abstract class TernaryTransformer extends Transformer {
+  protected final LayerPointReader firstPointReader;
+  protected final LayerPointReader secondPointReader;
+  protected final LayerPointReader thirdPointReader;
+
+  protected final TSDataType firstPointReaderDataType;
+  protected final TSDataType secondPointReaderDataType;
+  protected final TSDataType thirdPointReaderDataType;
+
+  protected final boolean isFirstPointReaderConstant;
+  protected final boolean isSecondPointReaderConstant;
+  protected final boolean isThirdPointReaderConstant;
+
+  protected final boolean isCurrentConstant;
+
+  @Override
+  protected YieldableState yieldValue() throws QueryProcessException, IOException {
+    final YieldableState firstYieldableState = firstPointReader.yield();
+    final YieldableState secondYieldableState = secondPointReader.yield();
+    final YieldableState thirdYieldableState = thirdPointReader.yield();
+
+    if (YieldableState.NOT_YIELDABLE_NO_MORE_DATA.equals(firstYieldableState)
+        || YieldableState.NOT_YIELDABLE_NO_MORE_DATA.equals(secondYieldableState)
+        || YieldableState.NOT_YIELDABLE_NO_MORE_DATA.equals(thirdYieldableState)) {
+      return YieldableState.NOT_YIELDABLE_NO_MORE_DATA;
+    }
+
+    if (YieldableState.NOT_YIELDABLE_WAITING_FOR_DATA.equals(firstYieldableState)
+        || YieldableState.NOT_YIELDABLE_WAITING_FOR_DATA.equals(secondYieldableState)
+        || YieldableState.NOT_YIELDABLE_WAITING_FOR_DATA.equals(thirdYieldableState)) {
+      return YieldableState.NOT_YIELDABLE_WAITING_FOR_DATA;
+    }
+
+    final YieldableState timeYieldState = yieldTime();
+    if (!YieldableState.YIELDABLE.equals(timeYieldState)) {
+      return timeYieldState;
+    }
+
+    if (firstPointReader.isCurrentNull()
+        || secondPointReader.isCurrentNull()
+        || thirdPointReader.isCurrentNull()) {
+      currentNull = true;
+    } else {
+      transformAndCache();
+    }
+
+    firstPointReader.readyForNext();
+    secondPointReader.readyForNext();
+    thirdPointReader.readyForNext();
+    return YieldableState.YIELDABLE;
+  }
+
+  private YieldableState yieldTime() throws IOException, QueryProcessException {
+    if (isCurrentConstant) {
+      return YieldableState.YIELDABLE;
+    }
+
+    long firstTime = isFirstPointReaderConstant ? Long.MIN_VALUE : firstPointReader.currentTime();
+    long secondTime =
+        isSecondPointReaderConstant ? Long.MIN_VALUE : secondPointReader.currentTime();
+    long thirdTime = isThirdPointReaderConstant ? Long.MIN_VALUE : thirdPointReader.currentTime();
+
+    while (firstTime != secondTime || firstTime != thirdTime) { // the logic is similar to MergeSort
+      if (firstTime < secondTime) {
+        if (isFirstPointReaderConstant) {
+          firstTime = secondTime;
+        } else {
+          firstPointReader.readyForNext();
+          final YieldableState firstYieldableState = firstPointReader.yield();
+          if (!YieldableState.YIELDABLE.equals(firstYieldableState)) {
+            return firstYieldableState;
+          }
+          firstTime = firstPointReader.currentTime();
+        }
+      } else if (secondTime < thirdTime) {
+        if (isSecondPointReaderConstant) {
+          secondTime = thirdTime;
+        } else {
+          secondPointReader.readyForNext();
+          final YieldableState secondYieldableState = secondPointReader.yield();
+          if (!YieldableState.YIELDABLE.equals(secondYieldableState)) {
+            return secondYieldableState;
+          }
+          secondTime = secondPointReader.currentTime();
+        }
+      } else {
+        if (isThirdPointReaderConstant) {
+          thirdTime = firstTime;
+        } else {
+          thirdPointReader.readyForNext();
+          final YieldableState thirdYieldableState = thirdPointReader.yield();
+          if (!YieldableState.YIELDABLE.equals(thirdYieldableState)) {
+            return thirdYieldableState;
+          }
+          thirdTime = thirdPointReader.currentTime();
+        }
+      }
+    }
+
+    if (firstTime != Long.MIN_VALUE) {
+      cachedTime = firstTime;
+    }
+    return YieldableState.YIELDABLE;
+  }
+
+  protected TernaryTransformer(
+      LayerPointReader firstPointReader,
+      LayerPointReader secondPointReader,
+      LayerPointReader thirdPointReader) {
+    this.firstPointReader = firstPointReader;
+    this.secondPointReader = secondPointReader;
+    this.thirdPointReader = thirdPointReader;
+    this.firstPointReaderDataType = firstPointReader.getDataType();
+    this.secondPointReaderDataType = secondPointReader.getDataType();
+    this.thirdPointReaderDataType = thirdPointReader.getDataType();
+    this.isFirstPointReaderConstant = firstPointReader.isConstantPointReader();
+    this.isSecondPointReaderConstant = secondPointReader.isConstantPointReader();
+    this.isThirdPointReaderConstant = thirdPointReader.isConstantPointReader();
+    this.isCurrentConstant =
+        isFirstPointReaderConstant && isSecondPointReaderConstant && isThirdPointReaderConstant;
+    checkType();
+  }
+
+  @Override
+  public boolean isConstantPointReader() {
+    return firstPointReader.isConstantPointReader()
+        && secondPointReader.isConstantPointReader()
+        && thirdPointReader.isConstantPointReader();
+  }
+
+  @Override
+  protected boolean cacheValue() throws QueryProcessException, IOException {
+    if (!firstPointReader.next() || !secondPointReader.next() || !thirdPointReader.next()) {
+      return false;
+    }
+
+    if (!cacheTime()) {
+      return false;
+    }
+
+    if (firstPointReader.isCurrentNull()
+        || secondPointReader.isCurrentNull()
+        || thirdPointReader.isCurrentNull()) {
+      currentNull = true;
+    } else {
+      transformAndCache();
+    }
+
+    firstPointReader.readyForNext();
+    secondPointReader.readyForNext();
+    thirdPointReader.readyForNext();
+    return true;
+  }
+
+  protected abstract void transformAndCache() throws QueryProcessException, IOException;
+
+  protected abstract void checkType();
+
+  /**
+   * finds the smallest, unconsumed, same timestamp that exists in {@code firstPointReader}, {@code
+   * secondPointReader} and {@code thirdPointReader}and then caches the timestamp in {@code
+   * cachedTime}.
+   *
+   * @return true if there has a timestamp that meets the requirements
+   */
+  private boolean cacheTime() throws IOException, QueryProcessException {
+    boolean isFirstConstant = firstPointReader.isConstantPointReader();
+    boolean isSecondConstant = secondPointReader.isConstantPointReader();
+    boolean isThirdConstant = thirdPointReader.isConstantPointReader();
+    long firstTime = isFirstConstant ? Long.MIN_VALUE : firstPointReader.currentTime();
+    long secondTime = isSecondConstant ? Long.MIN_VALUE : secondPointReader.currentTime();
+    long thirdTime = isThirdConstant ? Long.MIN_VALUE : secondPointReader.currentTime();
+    // Long.MIN_VALUE is used to determine whether  isFirstConstant && isSecondConstant &&
+    // isThirdConstant = true
+    while (firstTime != secondTime || firstTime != thirdTime) { // the logic is similar to MergeSort
+      if (firstTime < secondTime) {
+        if (isFirstConstant) {
+          firstTime = secondTime;
+        } else {
+          firstPointReader.readyForNext();
+          if (!firstPointReader.next()) {
+            return false;
+          }
+          firstTime = firstPointReader.currentTime();
+        }
+      } else if (secondTime < thirdTime) {
+        if (isSecondConstant) {
+          secondTime = thirdTime;
+        } else {
+          secondPointReader.readyForNext();
+          if (!secondPointReader.next()) {
+            return false;
+          }
+          secondTime = secondPointReader.currentTime();
+        }
+      } else {
+        if (isThirdConstant) {
+          thirdTime = firstTime;
+        } else {
+          thirdPointReader.readyForNext();
+          if (!thirdPointReader.next()) {
+            return false;
+          }
+          thirdTime = secondPointReader.currentTime();
+        }
+      }
+    }
+
+    if (firstTime != Long.MIN_VALUE) {
+      cachedTime = firstTime;
+    }
+    return true;
+  }
+
+  protected static double castCurrentValueToDoubleOperand(
+      LayerPointReader layerPointReader, TSDataType layerPointReaderDataType)
+      throws IOException, QueryProcessException {
+    switch (layerPointReaderDataType) {
+      case INT32:
+        return layerPointReader.currentInt();
+      case INT64:
+        return layerPointReader.currentLong();
+      case FLOAT:
+        return layerPointReader.currentFloat();
+      case DOUBLE:
+        return layerPointReader.currentDouble();
+      case BOOLEAN:
+        return layerPointReader.currentBoolean() ? 1.0d : 0.0d;
+      default:
+        throw new QueryProcessException(
+            "Unsupported data type: " + layerPointReader.getDataType().toString());
+    }
+  }
+}
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 06a0daef9e..378646ea45 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
@@ -46,6 +46,8 @@ import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
+import org.apache.iotdb.db.mpp.plan.expression.ternary.TernaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.LikeExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.LogicNotExpression;
@@ -2857,6 +2859,19 @@ public class IoTDBSqlVisitor extends IoTDBSqlParserBaseVisitor<Operator> {
       throw new UnsupportedOperationException();
     }
 
+    if (context.firstExpression != null
+        && context.secondExpression != null
+        && context.thirdExpression != null) {
+      Expression firstExpression = parseExpression(context.firstExpression, inWithoutNull);
+      Expression secondExpression = parseExpression(context.secondExpression, inWithoutNull);
+      Expression thirdExpression = parseExpression(context.thirdExpression, inWithoutNull);
+
+      if (context.OPERATOR_BETWEEN() != null) {
+        return new BetweenExpression(
+            firstExpression, secondExpression, thirdExpression, context.OPERATOR_NOT() != null);
+      }
+    }
+
     if (context.unaryBeforeInExpression != null) {
       return parseInExpression(context, inWithoutNull);
     }
@@ -2881,8 +2896,7 @@ public class IoTDBSqlVisitor extends IoTDBSqlParserBaseVisitor<Operator> {
     throw new UnsupportedOperationException();
   }
 
-  private Expression parseExpression(
-      IoTDBSqlParser.ExpressionContext context, boolean inWithoutNull) {
+  private Expression parseExpression(ExpressionContext context, boolean inWithoutNull) {
     return parseExpression(context, inWithoutNull, false);
   }
 
@@ -3066,6 +3080,25 @@ public class IoTDBSqlVisitor extends IoTDBSqlParserBaseVisitor<Operator> {
               FilterType.REGEXP,
               parsePathFromExpression(((RegularExpression) predicate).getExpression()),
               ((RegularExpression) predicate).getPatternString());
+    } else if (predicate instanceof BetweenExpression) {
+      filter = new FilterOperator(FilterType.KW_AND);
+      PartialPath partialPath =
+          parsePathFromExpression(((TernaryExpression) predicate).getFirstExpression());
+      filter.addChildOperator(
+          new BasicFunctionOperator(
+              FilterType.GREATERTHANOREQUALTO,
+              partialPath,
+              parseValueFromExpression(((TernaryExpression) predicate).getSecondExpression())));
+      filter.addChildOperator(
+          new BasicFunctionOperator(
+              FilterType.LESSTHANOREQUALTO,
+              partialPath,
+              parseValueFromExpression(((TernaryExpression) predicate).getThirdExpression())));
+      if (((BetweenExpression) predicate).isNotBetween()) {
+        FilterOperator temp = new FilterOperator(FilterType.KW_NOT);
+        temp.addChildOperator(filter);
+        filter = temp;
+      }
     } else if (predicate instanceof InExpression) {
       filter =
           new InOperator(
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/TimeFilter.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/TimeFilter.java
index 3e1b7c0629..e5109bf46b 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/TimeFilter.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/TimeFilter.java
@@ -20,6 +20,7 @@ package org.apache.iotdb.tsfile.read.filter;
 
 import org.apache.iotdb.tsfile.read.filter.basic.Filter;
 import org.apache.iotdb.tsfile.read.filter.factory.FilterType;
+import org.apache.iotdb.tsfile.read.filter.operator.Between;
 import org.apache.iotdb.tsfile.read.filter.operator.Eq;
 import org.apache.iotdb.tsfile.read.filter.operator.Gt;
 import org.apache.iotdb.tsfile.read.filter.operator.GtEq;
@@ -67,6 +68,17 @@ public class TimeFilter {
     return new TimeIn(values, not);
   }
 
+  public static TimeBetween between(long value1, long value2, boolean not) {
+    return new TimeBetween(value1, value2, not);
+  }
+
+  public static class TimeBetween extends Between {
+
+    private TimeBetween(long value1, long value2, boolean not) {
+      super(value1, value2, FilterType.TIME_FILTER, not);
+    }
+  }
+
   public static class TimeIn extends In {
 
     private TimeIn(Set<Long> values, boolean not) {
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterFactory.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterFactory.java
index 11d9349a25..4dff4e6337 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterFactory.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterFactory.java
@@ -22,6 +22,7 @@ import org.apache.iotdb.tsfile.read.filter.GroupByFilter;
 import org.apache.iotdb.tsfile.read.filter.GroupByMonthFilter;
 import org.apache.iotdb.tsfile.read.filter.basic.Filter;
 import org.apache.iotdb.tsfile.read.filter.operator.AndFilter;
+import org.apache.iotdb.tsfile.read.filter.operator.Between;
 import org.apache.iotdb.tsfile.read.filter.operator.Eq;
 import org.apache.iotdb.tsfile.read.filter.operator.Gt;
 import org.apache.iotdb.tsfile.read.filter.operator.GtEq;
@@ -81,6 +82,9 @@ public class FilterFactory {
       case LTEQ:
         filter = new LtEq<>();
         break;
+      case BETWEEN:
+        filter = new Between<>();
+        break;
       case IN:
         filter = new In<>();
         break;
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterSerializeId.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterSerializeId.java
index 85b0a4f5fe..bb3c7e1060 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterSerializeId.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/factory/FilterSerializeId.java
@@ -33,5 +33,6 @@ public enum FilterSerializeId {
   OR,
   IN,
   REGEXP,
-  LIKE
+  LIKE,
+  BETWEEN
 }
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/Between.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/Between.java
new file mode 100644
index 0000000000..0ca994f315
--- /dev/null
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/read/filter/operator/Between.java
@@ -0,0 +1,141 @@
+/*
+ * 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.tsfile.read.filter.operator;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.statistics.Statistics;
+import org.apache.iotdb.tsfile.read.filter.basic.Filter;
+import org.apache.iotdb.tsfile.read.filter.factory.FilterSerializeId;
+import org.apache.iotdb.tsfile.read.filter.factory.FilterType;
+import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class Between<T extends Comparable<T>> implements Filter {
+
+  private static final long serialVersionUID = -537390606419370764L;
+
+  protected T value1;
+
+  protected T value2;
+
+  protected boolean not;
+
+  protected FilterType filterType;
+
+  public Between() {}
+
+  public Between(T value1, T value2, FilterType filterType, boolean not) {
+    this.value1 = value1;
+    this.value2 = value2;
+    this.filterType = filterType;
+    this.not = not;
+  }
+
+  @Override
+  public void serialize(DataOutputStream outputStream) {
+    try {
+      outputStream.write(getSerializeId().ordinal());
+      outputStream.write(filterType.ordinal());
+      ReadWriteIOUtils.write(not, outputStream);
+      ReadWriteIOUtils.writeObject(value1, outputStream);
+      ReadWriteIOUtils.writeObject(value2, outputStream);
+    } catch (IOException ignored) {
+      // ignored
+    }
+  }
+
+  @Override
+  public void deserialize(ByteBuffer buffer) {
+    filterType = FilterType.values()[buffer.get()];
+    not = ReadWriteIOUtils.readBool(buffer);
+    value1 = (T) ReadWriteIOUtils.readObject(buffer);
+    value2 = (T) ReadWriteIOUtils.readObject(buffer);
+  }
+
+  @Override
+  public FilterSerializeId getSerializeId() {
+    return FilterSerializeId.BETWEEN;
+  }
+
+  @Override
+  public boolean satisfy(Statistics statistics) {
+    if (filterType == FilterType.TIME_FILTER) {
+      long time1 = (Long) value1, time2 = (Long) value2;
+      if (not) {
+        return statistics.getStartTime() < time1 || statistics.getEndTime() > time2;
+      } else {
+        return statistics.getEndTime() >= time1 || statistics.getStartTime() <= time2;
+      }
+    } else {
+      if (statistics.getType() == TSDataType.TEXT || statistics.getType() == TSDataType.BOOLEAN) {
+        return true;
+      }
+      if (not) {
+        return ((T) statistics.getMinValue()).compareTo(value1) < 0
+            || ((T) statistics.getMaxValue()).compareTo(value2) > 0;
+      } else {
+        return ((T) statistics.getMaxValue()).compareTo(value1) >= 0
+            && ((T) statistics.getMinValue()).compareTo(value2) <= 0;
+      }
+    }
+  }
+
+  @Override
+  public boolean satisfy(long time, Object value) {
+    Object v = filterType == FilterType.TIME_FILTER ? time : value;
+    return (value1.compareTo((T) v) <= 0 && ((T) v).compareTo(value2) <= 0) ^ not;
+  }
+
+  @Override
+  public boolean satisfyStartEndTime(long startTime, long endTime) {
+    if (filterType == FilterType.TIME_FILTER) {
+      long time1 = (Long) value1, time2 = (Long) value2;
+      if (not) {
+        return startTime < time1 || endTime > time2;
+      } else {
+        return endTime >= time1 && startTime <= time2;
+      }
+    } else {
+      return true;
+    }
+  }
+
+  @Override
+  public boolean containStartEndTime(long startTime, long endTime) {
+    if (filterType == FilterType.TIME_FILTER) {
+      long time1 = (Long) value1, time2 = (Long) value2;
+      if (not) {
+        return endTime < time1 || startTime > time2;
+      } else {
+        return startTime >= time1 && endTime <= time2;
+      }
+    } else {
+      return true;
+    }
+  }
+
+  @Override
+  public Filter copy() {
+    return new Between(value1, value2, filterType, not);
+  }
+}