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 2023/04/27 14:49:25 UTC

[iotdb] branch master updated: [IOTDB-5765] Support ORDER BY expression [FE Part]

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 ba4dea6306 [IOTDB-5765] Support ORDER BY expression [FE Part]
ba4dea6306 is described below

commit ba4dea630696efa88bd3a3e3e0300c64febac75a
Author: YangCaiyin <yc...@gmail.com>
AuthorDate: Thu Apr 27 22:49:18 2023 +0800

    [IOTDB-5765] Support ORDER BY expression [FE Part]
---
 .../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4   |    1 +
 .../antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4  |    8 +
 .../apache/iotdb/db/it/orderBy/IoTDBOrderByIT.java | 1224 ++++++++++++++++++++
 .../execution/operator/process/SortOperator.java   |   28 +-
 .../process/join/merge/MergeSortComparator.java    |  178 +--
 .../process/join/merge/MergeSortKeyComparator.java |   53 +
 .../apache/iotdb/db/mpp/plan/analyze/Analysis.java |   49 +
 .../iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java  |  133 ++-
 .../db/mpp/plan/analyze/ConcatPathRewriter.java    |   22 +-
 .../visitor/ColumnTransformerVisitor.java          |   38 +-
 .../iotdb/db/mpp/plan/parser/ASTVisitor.java       |   44 +-
 .../db/mpp/plan/planner/LogicalPlanBuilder.java    |  143 ++-
 .../db/mpp/plan/planner/LogicalPlanVisitor.java    |   19 +-
 .../db/mpp/plan/planner/OperatorTreeGenerator.java |   12 +-
 .../planner/distribution/DistributionPlanner.java  |    5 +-
 .../planner/distribution/ExchangeNodeAdder.java    |    6 +
 .../plan/planner/distribution/SourceRewriter.java  |   37 +
 .../component/{SortKey.java => NullOrdering.java}  |   19 +-
 .../plan/statement/component/OrderByComponent.java |   56 +-
 .../db/mpp/plan/statement/component/SortItem.java  |   61 +-
 .../db/mpp/plan/statement/component/SortKey.java   |   16 +-
 .../db/mpp/plan/statement/crud/QueryStatement.java |   80 ++
 .../execution/operator/MergeSortOperatorTest.java  |   20 +-
 .../iotdb/db/mpp/plan/analyze/AnalyzeTest.java     |  200 ++++
 .../tsfile/file/metadata/enums/TSDataType.java     |   17 +
 25 files changed, 2184 insertions(+), 285 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 0f4df03ce7..d88e4533c6 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
@@ -623,6 +623,7 @@ orderByClause
 
 orderByAttributeClause
     : sortKey (DESC | ASC)?
+    | expression (DESC | ASC)? (NULLS (FIRST|LAST))?
     ;
 
 sortKey
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 d9eefda093..fb7c5af931 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
@@ -270,6 +270,10 @@ FILE
     : F I L E
     ;
 
+FIRST
+    : F I R S T
+    ;
+
 FLUSH
     : F L U S H
     ;
@@ -414,6 +418,10 @@ NOW
     : N O W
     ;
 
+NULLS
+    : N U L L S
+    ;
+
 OF
     : O F
     ;
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/orderBy/IoTDBOrderByIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/orderBy/IoTDBOrderByIT.java
new file mode 100644
index 0000000000..a39b965a18
--- /dev/null
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/orderBy/IoTDBOrderByIT.java
@@ -0,0 +1,1224 @@
+/*
+ * 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.orderBy;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.ClusterIT;
+import org.apache.iotdb.itbase.category.LocalStandaloneIT;
+
+import org.bouncycastle.util.Arrays;
+import org.junit.AfterClass;
+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.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Objects;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({LocalStandaloneIT.class, ClusterIT.class})
+public class IoTDBOrderByIT {
+
+  // the data can be viewed in
+  // https://docs.google.com/spreadsheets/d/1OWA1bKraArCwWVnuTjuhJ5yLG0PFLdD78gD6FjquepI/edit#gid=0
+  private static final String[] sql =
+      new String[] {
+        "CREATE DATABASE root.sg",
+        "CREATE TIMESERIES root.sg.d.num WITH DATATYPE=INT32, ENCODING=RLE",
+        "CREATE TIMESERIES root.sg.d.bigNum WITH DATATYPE=INT64, ENCODING=RLE",
+        "CREATE TIMESERIES root.sg.d.floatNum WITH DATATYPE=DOUBLE, ENCODING=RLE, 'MAX_POINT_NUMBER'='5'",
+        "CREATE TIMESERIES root.sg.d.str WITH DATATYPE=TEXT, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.sg.d.bool WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(0,3,2947483648,231.2121,\"coconut\",FALSE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(20,2,2147483648,434.12,\"pineapple\",TRUE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(40,1,2247483648,12.123,\"apricot\",TRUE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(80,9,2147483646,43.12,\"apple\",FALSE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(100,8,2147483964,4654.231,\"papaya\",TRUE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536000000,6,2147483650,1231.21,\"banana\",TRUE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536000100,10,3147483648,231.55,\"pumelo\",FALSE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536000500,4,2147493648,213.1,\"peach\",FALSE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536001000,5,2149783648,56.32,\"orange\",FALSE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536010000,7,2147983648,213.112,\"lemon\",TRUE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(31536100000,11,2147468648,54.121,\"pitaya\",FALSE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(41536000000,12,2146483648,45.231,\"strawberry\",FALSE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(41536000020,14,2907483648,231.34,\"cherry\",FALSE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(41536900000,13,2107483648,54.12,\"lychee\",TRUE)",
+        "insert into root.sg.d(timestamp,num,bigNum,floatNum,str,bool) values(51536000000,15,3147483648,235.213,\"watermelon\",TRUE)"
+      };
+
+  private static final String[] sql2 =
+      new String[] {
+        "CREATE TIMESERIES root.sg.d2.num WITH DATATYPE=INT32, ENCODING=RLE",
+        "CREATE TIMESERIES root.sg.d2.bigNum WITH DATATYPE=INT64, ENCODING=RLE",
+        "CREATE TIMESERIES root.sg.d2.floatNum WITH DATATYPE=DOUBLE, ENCODING=RLE, 'MAX_POINT_NUMBER'='5'",
+        "CREATE TIMESERIES root.sg.d2.str WITH DATATYPE=TEXT, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.sg.d2.bool WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(0,3,2947483648,231.2121,\"coconut\",FALSE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(20,2,2147483648,434.12,\"pineapple\",TRUE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(40,1,2247483648,12.123,\"apricot\",TRUE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(80,9,2147483646,43.12,\"apple\",FALSE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(100,8,2147483964,4654.231,\"papaya\",TRUE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536000000,6,2147483650,1231.21,\"banana\",TRUE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536000100,10,3147483648,231.55,\"pumelo\",FALSE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536000500,4,2147493648,213.1,\"peach\",FALSE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536001000,5,2149783648,56.32,\"orange\",FALSE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536010000,7,2147983648,213.112,\"lemon\",TRUE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(31536100000,11,2147468648,54.121,\"pitaya\",FALSE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(41536000000,12,2146483648,45.231,\"strawberry\",FALSE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(41536000020,14,2907483648,231.34,\"cherry\",FALSE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(41536900000,13,2107483648,54.12,\"lychee\",TRUE)",
+        "insert into root.sg.d2(timestamp,num,bigNum,floatNum,str,bool) values(51536000000,15,3147483648,235.213,\"watermelon\",TRUE)"
+      };
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    EnvFactory.getEnv().initClusterEnvironment();
+    insertData();
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvFactory.getEnv().cleanClusterEnvironment();
+  }
+
+  protected static void insertData() {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      for (String sql : sql) {
+        statement.execute(sql);
+      }
+      for (String sql : sql2) {
+        statement.execute(sql);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+    }
+  }
+
+  // ordinal data
+  String[][] res =
+      new String[][] {
+        {"0", "3", "2947483648", "231.2121", "coconut", "false"},
+        {"20", "2", "2147483648", "434.12", "pineapple", "true"},
+        {"40", "1", "2247483648", "12.123", "apricot", "true"},
+        {"80", "9", "2147483646", "43.12", "apple", "false"},
+        {"100", "8", "2147483964", "4654.231", "papaya", "true"},
+        {"31536000000", "6", "2147483650", "1231.21", "banana", "true"},
+        {"31536000100", "10", "3147483648", "231.55", "pumelo", "false"},
+        {"31536000500", "4", "2147493648", "213.1", "peach", "false"},
+        {"31536001000", "5", "2149783648", "56.32", "orange", "false"},
+        {"31536010000", "7", "2147983648", "213.112", "lemon", "true"},
+        {"31536100000", "11", "2147468648", "54.121", "pitaya", "false"},
+        {"41536000000", "12", "2146483648", "45.231", "strawberry", "false"},
+        {"41536000020", "14", "2907483648", "231.34", "cherry", "false"},
+        {"41536900000", "13", "2107483648", "54.12", "lychee", "true"},
+        {"51536000000", "15", "3147483648", "235.213", "watermelon", "true"},
+      };
+
+  private void checkHeader(ResultSetMetaData resultSetMetaData, String[] title)
+      throws SQLException {
+    for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {
+      assertEquals(title[i - 1], resultSetMetaData.getColumnName(i));
+    }
+  }
+
+  private void testNormalOrderBy(String sql, int[] ans) {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        ResultSetMetaData metaData = resultSet.getMetaData();
+        checkHeader(
+            metaData,
+            new String[] {
+              "Time",
+              "root.sg.d.num",
+              "root.sg.d.bigNum",
+              "root.sg.d.floatNum",
+              "root.sg.d.str",
+              "root.sg.d.bool"
+            });
+        int i = 0;
+        while (resultSet.next()) {
+
+          String actualTime = resultSet.getString(1);
+          String actualNum = resultSet.getString(2);
+          String actualBigNum = resultSet.getString(3);
+          double actualFloatNum = resultSet.getDouble(4);
+          String actualStr = resultSet.getString(5);
+          String actualBool = resultSet.getString(6);
+
+          assertEquals(res[ans[i]][0], actualTime);
+          assertEquals(res[ans[i]][1], actualNum);
+          assertEquals(res[ans[i]][2], actualBigNum);
+          assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloatNum, 0.0001);
+          assertEquals(res[ans[i]][4], actualStr);
+          assertEquals(res[ans[i]][5], actualBool);
+
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  // 1. One-level order by test
+  @Test
+  public void orderByTest1() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by num";
+    int[] ans = {2, 1, 0, 7, 8, 5, 9, 4, 3, 6, 10, 11, 13, 12, 14};
+    testNormalOrderBy(sql, ans);
+  }
+
+  @Test
+  public void orderByTest2() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by bigNum";
+    int[] ans = {13, 11, 10, 3, 1, 5, 4, 7, 9, 8, 2, 12, 0, 6, 14};
+    testNormalOrderBy(sql, ans);
+  }
+
+  @Test
+  public void orderByTest3() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by floatNum";
+    int[] ans = {2, 3, 11, 13, 10, 8, 7, 9, 0, 12, 6, 14, 1, 5, 4};
+    testNormalOrderBy(sql, ans);
+  }
+
+  @Test
+  public void orderByTest4() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by str";
+    int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+    testNormalOrderBy(sql, ans);
+  }
+
+  @Test
+  public void orderByTest5() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by num desc";
+    int[] ans = {2, 1, 0, 7, 8, 5, 9, 4, 3, 6, 10, 11, 13, 12, 14};
+    testNormalOrderBy(sql, Arrays.reverse(ans));
+  }
+
+  @Test
+  public void orderByTest6() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by bigNum desc";
+    int[] ans = {6, 14, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+    testNormalOrderBy(sql, ans);
+  }
+
+  @Test
+  public void orderByTest7() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by floatNum desc";
+    int[] ans = {2, 3, 11, 13, 10, 8, 7, 9, 0, 12, 6, 14, 1, 5, 4};
+    testNormalOrderBy(sql, Arrays.reverse(ans));
+  }
+
+  @Test
+  public void orderByTest8() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by str desc";
+    int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+    testNormalOrderBy(sql, Arrays.reverse(ans));
+  }
+
+  @Test
+  public void orderByTest17() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by str desc, str asc";
+    int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+    testNormalOrderBy(sql, Arrays.reverse(ans));
+  }
+
+  @Test
+  public void orderByTest18() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by str, str";
+    int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+    testNormalOrderBy(sql, ans);
+  }
+
+  @Test
+  public void orderByTest15() {
+    String sql = "select num+bigNum,floatNum from root.sg.d order by str";
+    int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        ResultSetMetaData metaData = resultSet.getMetaData();
+        checkHeader(
+            metaData,
+            new String[] {"Time", "root.sg.d.num + root.sg.d.bigNum", "root.sg.d.floatNum"});
+        int i = 0;
+        while (resultSet.next()) {
+
+          String actualTime = resultSet.getString(1);
+          double actualNum = resultSet.getDouble(2);
+          double actualFloat = resultSet.getDouble(3);
+
+          assertEquals(res[ans[i]][0], actualTime);
+          assertEquals(
+              Long.parseLong(res[ans[i]][1]) + Long.parseLong(res[ans[i]][2]), actualNum, 0.0001);
+          assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloat, 0.0001);
+
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  // 2. Multi-level order by test
+  @Test
+  public void orderByTest9() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by bool asc, str asc";
+    int[] ans = {3, 12, 0, 8, 7, 10, 6, 11, 2, 5, 9, 13, 4, 1, 14};
+    testNormalOrderBy(sql, ans);
+  }
+
+  @Test
+  public void orderByTest10() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.sg.d order by bool asc, num desc";
+    int[] ans = {12, 11, 10, 6, 3, 8, 7, 0, 14, 13, 4, 9, 5, 1, 2};
+    testNormalOrderBy(sql, ans);
+  }
+
+  @Test
+  public void orderByTest11() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.sg.d order by bigNum desc, floatNum desc";
+    int[] ans = {14, 6, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+    testNormalOrderBy(sql, ans);
+  }
+
+  @Test
+  public void orderByTest12() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.sg.d order by str desc, floatNum desc";
+    int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+    testNormalOrderBy(sql, Arrays.reverse(ans));
+  }
+
+  @Test
+  public void orderByTest13() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.sg.d order by num+floatNum desc, floatNum desc";
+    int[] ans = {4, 5, 1, 14, 12, 6, 0, 9, 7, 13, 10, 8, 11, 3, 2};
+    testNormalOrderBy(sql, ans);
+  }
+
+  @Test
+  public void orderByTest14() {
+    String sql = "select num+bigNum from root.sg.d order by num+floatNum desc, floatNum desc";
+    int[] ans = {4, 5, 1, 14, 12, 6, 0, 9, 7, 13, 10, 8, 11, 3, 2};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        ResultSetMetaData metaData = resultSet.getMetaData();
+        checkHeader(metaData, new String[] {"Time", "root.sg.d.num + root.sg.d.bigNum"});
+        int i = 0;
+        while (resultSet.next()) {
+
+          String actualTime = resultSet.getString(1);
+          double actualNum = resultSet.getDouble(2);
+
+          assertEquals(res[ans[i]][0], actualTime);
+          assertEquals(
+              Long.parseLong(res[ans[i]][1]) + Long.parseLong(res[ans[i]][2]), actualNum, 0.001);
+
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByTest16() {
+    String sql = "select num+floatNum from root.sg.d order by floatNum+num desc, floatNum desc";
+    int[] ans = {4, 5, 1, 14, 12, 6, 0, 9, 7, 13, 10, 8, 11, 3, 2};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        ResultSetMetaData metaData = resultSet.getMetaData();
+        checkHeader(metaData, new String[] {"Time", "root.sg.d.num + root.sg.d.floatNum"});
+        int i = 0;
+        while (resultSet.next()) {
+
+          String actualTime = resultSet.getString(1);
+          double actualNum = resultSet.getDouble(2);
+
+          assertEquals(res[ans[i]][0], actualTime);
+          assertEquals(
+              Long.parseLong(res[ans[i]][1]) + Double.parseDouble(res[ans[i]][3]),
+              actualNum,
+              0.001);
+
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  // 3. aggregation query
+  @Test
+  public void orderByInAggregationTest() {
+    String sql = "select avg(num) from root.sg.d group by session(10000ms) order by avg(num) desc";
+    double[][] ans = new double[][] {{15.0}, {13.0}, {13.0}, {11.0}, {6.4}, {4.6}};
+    long[] times =
+        new long[] {51536000000L, 41536000000L, 41536900000L, 31536100000L, 31536000000L, 0L};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          long actualTime = resultSet.getLong(1);
+          double actualAvg = resultSet.getDouble(2);
+          assertEquals(times[i], actualTime);
+          assertEquals(ans[i][0], actualAvg, 0.0001);
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByInAggregationTest2() {
+    String sql =
+        "select avg(num) from root.sg.d group by session(10000ms) order by max_value(floatNum)";
+    double[][] ans =
+        new double[][] {
+          {13.0, 54.12},
+          {11.0, 54.121},
+          {13.0, 231.34},
+          {15.0, 235.213},
+          {6.4, 1231.21},
+          {4.6, 4654.231}
+        };
+    long[] times =
+        new long[] {41536900000L, 31536100000L, 41536000000L, 51536000000L, 31536000000L, 0L};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          long actualTime = resultSet.getLong(1);
+          double actualAvg = resultSet.getDouble(2);
+          assertEquals(times[i], actualTime);
+          assertEquals(ans[i][0], actualAvg, 0.0001);
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByInAggregationTest3() {
+    String sql =
+        "select avg(num) from root.sg.d group by session(10000ms) order by avg(num) desc,max_value(floatNum)";
+    double[] ans = new double[] {15.0, 13.0, 13.0, 11.0, 6.4, 4.6};
+    long[] times =
+        new long[] {51536000000L, 41536900000L, 41536000000L, 31536100000L, 31536000000L, 0L};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          long actualTime = resultSet.getLong(1);
+          double actualAvg = resultSet.getDouble(2);
+          assertEquals(times[i], actualTime);
+          assertEquals(ans[i], actualAvg, 0.0001);
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByInAggregationTest4() {
+    String sql =
+        "select avg(num)+avg(floatNum) from root.sg.d group by session(10000ms) order by avg(num)+avg(floatNum)";
+    double[][] ans =
+        new double[][] {{1079.56122}, {395.4584}, {65.121}, {151.2855}, {67.12}, {250.213}};
+    long[] times =
+        new long[] {0L, 31536000000L, 31536100000L, 41536000000L, 41536900000L, 51536000000L};
+    int[] order = new int[] {2, 4, 3, 5, 1, 0};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          long actualTime = resultSet.getLong(1);
+          double actualAvg = resultSet.getDouble(2);
+          assertEquals(times[order[i]], actualTime);
+          assertEquals(ans[order[i]][0], actualAvg, 0.0001);
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByInAggregationTest5() {
+    String sql =
+        "select min_value(bigNum) from root.sg.d group by session(10000ms) order by avg(num)+avg(floatNum)";
+    long[] ans =
+        new long[] {2147483646L, 2147483650L, 2147468648L, 2146483648L, 2107483648L, 3147483648L};
+    long[] times =
+        new long[] {0L, 31536000000L, 31536100000L, 41536000000L, 41536900000L, 51536000000L};
+    int[] order = new int[] {2, 4, 3, 5, 1, 0};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          long actualTime = resultSet.getLong(1);
+          long actualMinValue = resultSet.getLong(2);
+          assertEquals(times[order[i]], actualTime);
+          assertEquals(ans[order[i]], actualMinValue, 0.0001);
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByInAggregationTest6() {
+    String sql =
+        "select min_value(num)+min_value(bigNum) from root.sg.d group by session(10000ms) order by avg(num)+avg(floatNum)";
+    long[] ans =
+        new long[] {2147483647L, 2147483654L, 2147468659L, 2146483660L, 2107483661L, 3147483663L};
+    long[] times =
+        new long[] {0L, 31536000000L, 31536100000L, 41536000000L, 41536900000L, 51536000000L};
+    int[] order = new int[] {2, 4, 3, 5, 1, 0};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          long actualTime = resultSet.getLong(1);
+          double actualMinValue = resultSet.getDouble(2);
+          assertEquals(times[order[i]], actualTime);
+          assertEquals(ans[order[i]], actualMinValue, 0.0001);
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByInAggregationTest7() {
+    String sql =
+        "select avg(num)+min_value(floatNum) from root.sg.d group by session(10000ms) order by max_value(floatNum)";
+    double[][] ans =
+        new double[][] {
+          {13.0, 54.12, 54.12},
+          {11.0, 54.121, 54.121},
+          {13.0, 231.34, 45.231},
+          {15.0, 235.213, 235.213},
+          {6.4, 1231.21, 56.32},
+          {4.6, 4654.231, 12.123}
+        };
+    long[] times =
+        new long[] {41536900000L, 31536100000L, 41536000000L, 51536000000L, 31536000000L, 0L};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          long actualTime = resultSet.getLong(1);
+          double actualAvg = resultSet.getDouble(2);
+          assertEquals(times[i], actualTime);
+          assertEquals(ans[i][0] + ans[i][2], actualAvg, 0.0001);
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByInAggregationTest8() {
+    String sql =
+        "select avg(num)+avg(floatNum) from root.sg.d group by session(10000ms) order by avg(floatNum)+avg(num)";
+    double[][] ans =
+        new double[][] {{1079.56122}, {395.4584}, {65.121}, {151.2855}, {67.12}, {250.213}};
+    long[] times =
+        new long[] {0L, 31536000000L, 31536100000L, 41536000000L, 41536900000L, 51536000000L};
+    int[] order = new int[] {2, 4, 3, 5, 1, 0};
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          long actualTime = resultSet.getLong(1);
+          double actualAvg = resultSet.getDouble(2);
+          assertEquals(times[order[i]], actualTime);
+          assertEquals(ans[order[i]][0], actualAvg, 0.0001);
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  // 4. raw data query with align by device
+  private void testNormalOrderByAlignByDevice(String sql, int[] ans) {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        ResultSetMetaData metaData = resultSet.getMetaData();
+        checkHeader(
+            metaData, new String[] {"Time", "Device", "num", "bigNum", "floatNum", "str", "bool"});
+        int i = 0;
+        int total = 0;
+        String device = "root.sg.d";
+        while (resultSet.next()) {
+
+          String actualTime = resultSet.getString(1);
+          String actualDevice = resultSet.getString(2);
+          String actualNum = resultSet.getString(3);
+          String actualBigNum = resultSet.getString(4);
+          double actualFloatNum = resultSet.getDouble(5);
+          String actualStr = resultSet.getString(6);
+          String actualBool = resultSet.getString(7);
+
+          assertEquals(device, actualDevice);
+          assertEquals(res[ans[i]][0], actualTime);
+          assertEquals(res[ans[i]][1], actualNum);
+          assertEquals(res[ans[i]][2], actualBigNum);
+          assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloatNum, 0.0001);
+          assertEquals(res[ans[i]][4], actualStr);
+          assertEquals(res[ans[i]][5], actualBool);
+
+          if (device.equals("root.sg.d")) {
+            device = "root.sg.d2";
+          } else {
+            device = "root.sg.d";
+            i++;
+          }
+          total++;
+        }
+        assertEquals(i, ans.length);
+        assertEquals(total, ans.length * 2);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest1() {
+    String sql =
+        "select num+bigNum from root.** order by num+floatNum desc, floatNum desc align by device";
+    int[] ans = {4, 5, 1, 14, 12, 6, 0, 9, 7, 13, 10, 8, 11, 3, 2};
+    String device = "root.sg.d";
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          String actualTime = resultSet.getString(1);
+          String actualDevice = resultSet.getString(2);
+          double actualNum = resultSet.getDouble(3);
+
+          assertEquals(device, actualDevice);
+          assertEquals(res[ans[i]][0], actualTime);
+          assertEquals(
+              Long.parseLong(res[ans[i]][1]) + Long.parseLong(res[ans[i]][2]), actualNum, 0.0001);
+          if (device.equals("root.sg.d")) {
+            device = "root.sg.d2";
+          } else {
+            device = "root.sg.d";
+            i++;
+          }
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest2() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.** order by num align by device";
+    int[] ans = {2, 1, 0, 7, 8, 5, 9, 4, 3, 6, 10, 11, 13, 12, 14};
+    testNormalOrderByAlignByDevice(sql, ans);
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest3() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by floatNum align by device";
+    int[] ans = {2, 3, 11, 13, 10, 8, 7, 9, 0, 12, 6, 14, 1, 5, 4};
+    testNormalOrderByAlignByDevice(sql, ans);
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest4() {
+    String sql = "select num,bigNum,floatNum,str,bool from root.** order by str align by device";
+    int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+    testNormalOrderByAlignByDevice(sql, ans);
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest5() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by num desc align by device";
+    int[] ans = {2, 1, 0, 7, 8, 5, 9, 4, 3, 6, 10, 11, 13, 12, 14};
+    testNormalOrderByAlignByDevice(sql, Arrays.reverse(ans));
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest6() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by str desc align by device";
+    int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+    testNormalOrderByAlignByDevice(sql, Arrays.reverse(ans));
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest7() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by bool asc, num desc align by device";
+    int[] ans = {12, 11, 10, 6, 3, 8, 7, 0, 14, 13, 4, 9, 5, 1, 2};
+    testNormalOrderByAlignByDevice(sql, ans);
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest8() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by bigNum desc, floatNum desc align by device";
+    int[] ans = {14, 6, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+    testNormalOrderByAlignByDevice(sql, ans);
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest9() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by str desc, floatNum desc align by device";
+    int[] ans = {3, 2, 5, 12, 0, 9, 13, 8, 4, 7, 1, 10, 6, 11, 14};
+    testNormalOrderByAlignByDevice(sql, Arrays.reverse(ans));
+  }
+
+  private void testNormalOrderByMixAlignBy(String sql, int[] ans) {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        ResultSetMetaData metaData = resultSet.getMetaData();
+        checkHeader(
+            metaData, new String[] {"Time", "Device", "num", "bigNum", "floatNum", "str", "bool"});
+        int i = 0;
+        int total = 0;
+        String device = "root.sg.d";
+        while (resultSet.next()) {
+
+          String actualTime = resultSet.getString(1);
+          String actualDevice = resultSet.getString(2);
+          String actualNum = resultSet.getString(3);
+          String actualBigNum = resultSet.getString(4);
+          double actualFloatNum = resultSet.getDouble(5);
+          String actualStr = resultSet.getString(6);
+          String actualBool = resultSet.getString(7);
+
+          assertEquals(device, actualDevice);
+          assertEquals(res[ans[i]][0], actualTime);
+          assertEquals(res[ans[i]][1], actualNum);
+          assertEquals(res[ans[i]][2], actualBigNum);
+          assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloatNum, 0.0001);
+          assertEquals(res[ans[i]][4], actualStr);
+          assertEquals(res[ans[i]][5], actualBool);
+
+          if (device.equals("root.sg.d2")) {
+            i++;
+            device = "root.sg.d";
+          } else {
+            device = "root.sg.d2";
+          }
+
+          total++;
+        }
+        assertEquals(total, ans.length * 2);
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  private void testDeviceViewOrderByMixAlignBy(String sql, int[] ans) {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        ResultSetMetaData metaData = resultSet.getMetaData();
+        checkHeader(
+            metaData, new String[] {"Time", "Device", "num", "bigNum", "floatNum", "str", "bool"});
+        int i = 0;
+        int total = 0;
+        String device = "root.sg.d2";
+        while (resultSet.next()) {
+
+          String actualTime = resultSet.getString(1);
+          String actualDevice = resultSet.getString(2);
+          String actualNum = resultSet.getString(3);
+          String actualBigNum = resultSet.getString(4);
+          double actualFloatNum = resultSet.getDouble(5);
+          String actualStr = resultSet.getString(6);
+          String actualBool = resultSet.getString(7);
+
+          assertEquals(device, actualDevice);
+          assertEquals(res[ans[i]][0], actualTime);
+          assertEquals(res[ans[i]][1], actualNum);
+          assertEquals(res[ans[i]][2], actualBigNum);
+          assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloatNum, 0.0001);
+          assertEquals(res[ans[i]][4], actualStr);
+          assertEquals(res[ans[i]][5], actualBool);
+
+          i++;
+          total++;
+          if (total == ans.length) {
+            i = 0;
+            if (device.equals("root.sg.d2")) {
+              device = "root.sg.d";
+            } else {
+              device = "root.sg.d2";
+            }
+          }
+        }
+        assertEquals(total, ans.length * 2);
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  private void orderByBigNumAlignByDevice(String sql, int[] ans) {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        ResultSetMetaData metaData = resultSet.getMetaData();
+        checkHeader(
+            metaData, new String[] {"Time", "Device", "num", "bigNum", "floatNum", "str", "bool"});
+        int i = 0;
+        int total = 0;
+        String device = "root.sg.d";
+        while (resultSet.next()) {
+
+          String actualTime = resultSet.getString(1);
+          String actualDevice = resultSet.getString(2);
+          String actualNum = resultSet.getString(3);
+          String actualBigNum = resultSet.getString(4);
+          double actualFloatNum = resultSet.getDouble(5);
+          String actualStr = resultSet.getString(6);
+          String actualBool = resultSet.getString(7);
+
+          if (total < 4) {
+            i = total % 2;
+            if (total < 2) {
+              device = "root.sg.d2";
+            } else {
+              device = "root.sg.d";
+            }
+          }
+
+          assertEquals(device, actualDevice);
+          assertEquals(res[ans[i]][0], actualTime);
+          assertEquals(res[ans[i]][1], actualNum);
+          assertEquals(res[ans[i]][2], actualBigNum);
+          assertEquals(Double.parseDouble(res[ans[i]][3]), actualFloatNum, 0.0001);
+          assertEquals(res[ans[i]][4], actualStr);
+          assertEquals(res[ans[i]][5], actualBool);
+
+          if (device.equals("root.sg.d2")) {
+            device = "root.sg.d";
+          } else {
+            i++;
+            device = "root.sg.d2";
+          }
+
+          total++;
+        }
+        assertEquals(i, ans.length);
+        assertEquals(total, ans.length * 2);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest12() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by bigNum desc, device desc, time asc align by device";
+    int[] ans = {6, 14, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+    orderByBigNumAlignByDevice(sql, ans);
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest13() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by bigNum desc, time desc, device asc align by device";
+    int[] ans = {14, 6, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+    testNormalOrderByMixAlignBy(sql, ans);
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest14() {
+    int[] ans = {14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by time desc, bigNum desc, device asc align by device";
+    testNormalOrderByMixAlignBy(sql, ans);
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest15() {
+    int[] ans = {6, 14, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by device desc, bigNum desc, time asc align by device";
+    testDeviceViewOrderByMixAlignBy(sql, ans);
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest16() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by device desc, time asc, bigNum desc align by device";
+    int[] ans = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
+    testDeviceViewOrderByMixAlignBy(sql, ans);
+  }
+
+  @Test
+  public void alignByDeviceOrderByTest17() {
+    String sql =
+        "select num,bigNum,floatNum,str,bool from root.** order by bigNum desc, device desc, num asc, time asc align by device";
+    int[] ans = {6, 14, 0, 12, 2, 8, 9, 7, 4, 5, 1, 3, 10, 11, 13};
+    orderByBigNumAlignByDevice(sql, ans);
+  }
+
+  // 5. aggregation query align by device
+  @Test
+  public void orderByInAggregationAlignByDeviceTest() {
+    String sql =
+        "select avg(num) from root.** group by session(10000ms) order by avg(num) align by device";
+
+    double[] ans = {4.6, 4.6, 6.4, 6.4, 11.0, 11.0, 13.0, 13.0, 13.0, 13.0, 15.0, 15.0};
+    long[] times =
+        new long[] {
+          0L,
+          0L,
+          31536000000L,
+          31536000000L,
+          31536100000L,
+          31536100000L,
+          41536000000L,
+          41536900000L,
+          41536000000L,
+          41536900000L,
+          51536000000L,
+          51536000000L
+        };
+    String[] device =
+        new String[] {
+          "root.sg.d",
+          "root.sg.d2",
+          "root.sg.d",
+          "root.sg.d2",
+          "root.sg.d",
+          "root.sg.d2",
+          "root.sg.d",
+          "root.sg.d",
+          "root.sg.d2",
+          "root.sg.d2",
+          "root.sg.d",
+          "root.sg.d2"
+        };
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          long actualTime = resultSet.getLong(1);
+          String actualDevice = resultSet.getString(2);
+          double actualAvg = resultSet.getDouble(3);
+
+          assertEquals(device[i], actualDevice);
+          assertEquals(times[i], actualTime);
+          assertEquals(ans[i], actualAvg, 0.0001);
+          i++;
+        }
+        assertEquals(i, ans.length);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest2() {
+    String sql = "select avg(num) from root.** order by avg(num) align by device";
+    String value = "8";
+    checkSingleDouble(sql, value, true);
+  }
+
+  private void checkSingleDouble(String sql, Object value, boolean deviceAsc) {
+    String device = "root.sg.d";
+    if (!deviceAsc) device = "root.sg.d2";
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          String deviceName = resultSet.getString(1);
+          double actualVal = resultSet.getDouble(2);
+          assertEquals(deviceName, device);
+          assertEquals(Double.parseDouble(value.toString()), actualVal, 1);
+          if (device.equals("root.sg.d")) device = "root.sg.d2";
+          else device = "root.sg.d";
+          i++;
+        }
+        assertEquals(i, 2);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest3() {
+    String sql =
+        "select avg(num)+avg(bigNum) from root.** order by max_value(floatNum) align by device";
+    long value = 2388936669L + 8;
+    checkSingleDouble(sql, value, true);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest4() {
+
+    String sql =
+        "select avg(num)+avg(bigNum) from root.** order by max_value(floatNum)+min_value(num) align by device";
+    long value = 2388936669L + 8;
+    checkSingleDouble(sql, value, true);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest5() {
+    String sql =
+        "select avg(num) from root.** order by max_value(floatNum)+avg(num) align by device";
+    String value = "8";
+    checkSingleDouble(sql, value, true);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest6() {
+    String sql =
+        "select avg(num) from root.** order by max_value(floatNum)+avg(num), device asc, time desc align by device";
+    String value = "8";
+    checkSingleDouble(sql, value, true);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest7() {
+    String sql =
+        "select avg(num) from root.** order by max_value(floatNum)+avg(num), time asc, device desc align by device";
+    String value = "8";
+    checkSingleDouble(sql, value, false);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest8() {
+    String sql =
+        "select avg(num) from root.** order by time asc, max_value(floatNum)+avg(num), device desc align by device";
+    String value = "8";
+    checkSingleDouble(sql, value, false);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest9() {
+    String sql =
+        "select avg(num) from root.** order by device asc, max_value(floatNum)+avg(num), time desc align by device";
+    String value = "8";
+    checkSingleDouble(sql, value, true);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest10() {
+    String sql =
+        "select avg(num) from root.** order by max_value(floatNum) desc,time asc, avg(num) asc, device desc align by device";
+    String value = "8";
+    checkSingleDouble(sql, value, false);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest11() {
+    String sql =
+        "select avg(num) from root.** order by max_value(floatNum) desc,device asc, avg(num) asc, time desc align by device";
+    String value = "8";
+    checkSingleDouble(sql, value, true);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest12() {
+    String sql =
+        "select avg(num+floatNum) from root.** order by time,avg(num+floatNum) align by device";
+    String value = "537.34154";
+    checkSingleDouble(sql, value, true);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest13() {
+    String sql = "select avg(num) from root.** order by time,avg(num+floatNum) align by device";
+    String value = "8";
+    checkSingleDouble(sql, value, true);
+  }
+
+  @Test
+  public void orderByInAggregationAlignByDeviceTest14() {
+    String sql = "select avg(num+floatNum) from root.** order by time,avg(num) align by device";
+    String value = "537.34154";
+    checkSingleDouble(sql, value, true);
+  }
+
+  String[][] UDFRes =
+      new String[][] {
+        {"0", "3", "0", "0"},
+        {"20", "2", "0", "0"},
+        {"40", "1", "0", "0"},
+        {"80", "9", "0", "0"},
+        {"100", "8", "0", "0"},
+        {"31536000000", "6", "0", "0"},
+        {"31536000100", "10", "0", "0"},
+        {"31536000500", "4", "0", "0"},
+        {"31536001000", "5", "0", "0"},
+        {"31536010000", "7", "0", "0"},
+        {"31536100000", "11", "0", "0"},
+        {"41536000000", "12", "2146483648", "0"},
+        {"41536000020", "14", "0", "14"},
+        {"41536900000", "13", "2107483648", "0"},
+        {"51536000000", "15", "0", "15"},
+      };
+
+  // UDF Test
+  private void orderByUDFTest(String sql, int[] ans) {
+    try (Connection connection = EnvFactory.getEnv().getConnection();
+        Statement statement = connection.createStatement()) {
+      try (ResultSet resultSet = statement.executeQuery(sql)) {
+        int i = 0;
+        while (resultSet.next()) {
+          String time = resultSet.getString(1);
+          String num = resultSet.getString(2);
+          String topK = resultSet.getString(3);
+          String bottomK = resultSet.getString(4);
+
+          assertEquals(time, UDFRes[ans[i]][0]);
+          assertEquals(num, UDFRes[ans[i]][1]);
+          if (Objects.equals(UDFRes[ans[i]][3], "0")) {
+            assertNull(topK);
+          } else {
+            assertEquals(topK, UDFRes[ans[i]][3]);
+          }
+
+          if (Objects.equals(UDFRes[ans[i]][2], "0")) {
+            assertNull(bottomK);
+          } else {
+            assertEquals(bottomK, UDFRes[ans[i]][2]);
+          }
+
+          i++;
+        }
+        assertEquals(i, 15);
+      }
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail();
+    }
+  }
+
+  @Test
+  public void orderByUDFTest1() {
+    String sql =
+        "select num, top_k(num, 'k'='2'), bottom_k(bigNum, 'k'='2') from root.sg.d order by top_k(num, 'k'='2') nulls first, bottom_k(bigNum, 'k'='2') nulls first";
+    int[] ans = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 11, 12, 14};
+    orderByUDFTest(sql, ans);
+  }
+
+  @Test
+  public void orderByUDFTest2() {
+    String sql =
+        "select num, top_k(num, 'k'='2'), bottom_k(bigNum, 'k'='2') from root.sg.d order by top_k(num, 'k'='2'), bottom_k(bigNum, 'k'='2')";
+    int[] ans = {12, 14, 13, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+    orderByUDFTest(sql, ans);
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/SortOperator.java b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/SortOperator.java
index ae61601063..6e9d185251 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/SortOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/SortOperator.java
@@ -65,6 +65,16 @@ public class SortOperator implements ProcessOperator {
 
   @Override
   public TsBlock next() throws Exception {
+
+    if (!inputOperator.hasNextWithTimer()) {
+      if (cachedData.size() > 1) {
+        cachedData.sort(comparator);
+      }
+      TsBlock result = buildTsBlock();
+      cachedData = null;
+      return result;
+    }
+
     TsBlock tsBlock = inputOperator.nextWithTimer();
     if (tsBlock == null) {
       return null;
@@ -73,17 +83,7 @@ public class SortOperator implements ProcessOperator {
     for (int i = 0; i < tsBlock.getPositionCount(); i++) {
       cachedData.add(new MergeSortKey(tsBlock, i));
     }
-    // child has more data, can't calculate
-    if (inputOperator.hasNextWithTimer()) {
-      return null;
-    }
-
-    if (cachedData.size() > 1) {
-      cachedData.sort(comparator);
-    }
-    TsBlock result = buildTsBlock();
-    cachedData = null;
-    return result;
+    return null;
   }
 
   private TsBlock buildTsBlock() {
@@ -95,6 +95,10 @@ public class SortOperator implements ProcessOperator {
           int row = mergeSortKey.rowIndex;
           timeColumnBuilder.writeLong(tsBlock.getTimeByIndex(row));
           for (int i = 0; i < valueColumnBuilders.length; i++) {
+            if (tsBlock.getColumn(i).isNull(row)) {
+              valueColumnBuilders[i].appendNull();
+              continue;
+            }
             valueColumnBuilders[i].write(tsBlock.getColumn(i), row);
           }
           tsBlockBuilder.declarePosition();
@@ -104,7 +108,7 @@ public class SortOperator implements ProcessOperator {
 
   @Override
   public boolean hasNext() throws Exception {
-    return inputOperator.hasNextWithTimer();
+    return inputOperator.hasNextWithTimer() || cachedData != null;
   }
 
   @Override
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortComparator.java b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortComparator.java
index 16b66b8dd3..3d8991bf24 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortComparator.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortComparator.java
@@ -19,9 +19,9 @@
 
 package org.apache.iotdb.db.mpp.execution.operator.process.join.merge;
 
+import org.apache.iotdb.db.mpp.plan.statement.component.NullOrdering;
 import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
 import org.apache.iotdb.db.mpp.plan.statement.component.SortItem;
-import org.apache.iotdb.db.mpp.plan.statement.component.SortKey;
 import org.apache.iotdb.db.utils.datastructure.MergeSortKey;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 
@@ -33,153 +33,33 @@ import java.util.List;
 
 public class MergeSortComparator {
 
-  public static final Comparator<MergeSortKey> ASC_TIME_ASC_DEVICE =
-      (MergeSortKey o1, MergeSortKey o2) -> {
-        int timeComparing =
-            Long.compare(
-                o1.tsBlock.getTimeByIndex(o1.rowIndex), o2.tsBlock.getTimeByIndex(o2.rowIndex));
-        return timeComparing == 0
-            ? o1.tsBlock
-                .getColumn(0)
-                .getBinary(o1.rowIndex)
-                .compareTo(o2.tsBlock.getColumn(0).getBinary(o2.rowIndex))
-            : timeComparing;
-      };
-  public static final Comparator<MergeSortKey> ASC_TIME_DESC_DEVICE =
-      (MergeSortKey o1, MergeSortKey o2) -> {
-        int timeComparing =
-            Long.compare(
-                o1.tsBlock.getTimeByIndex(o1.rowIndex), o2.tsBlock.getTimeByIndex(o2.rowIndex));
-        return timeComparing == 0
-            ? o2.tsBlock
-                .getColumn(0)
-                .getBinary(o2.rowIndex)
-                .compareTo(o1.tsBlock.getColumn(0).getBinary(o1.rowIndex))
-            : timeComparing;
-      };
-  public static final Comparator<MergeSortKey> DESC_TIME_ASC_DEVICE =
-      (MergeSortKey o1, MergeSortKey o2) -> {
-        int timeComparing =
-            Long.compare(
-                o2.tsBlock.getTimeByIndex(o2.rowIndex), o1.tsBlock.getTimeByIndex(o1.rowIndex));
-        return timeComparing == 0
-            ? o1.tsBlock
-                .getColumn(0)
-                .getBinary(o1.rowIndex)
-                .compareTo(o2.tsBlock.getColumn(0).getBinary(o2.rowIndex))
-            : timeComparing;
-      };
-  public static final Comparator<MergeSortKey> DESC_TIME_DESC_DEVICE =
-      (MergeSortKey o1, MergeSortKey o2) -> {
-        int timeComparing =
-            Long.compare(
-                o2.tsBlock.getTimeByIndex(o2.rowIndex), o1.tsBlock.getTimeByIndex(o1.rowIndex));
-        return timeComparing == 0
-            ? o2.tsBlock
-                .getColumn(0)
-                .getBinary(o2.rowIndex)
-                .compareTo(o1.tsBlock.getColumn(0).getBinary(o1.rowIndex))
-            : timeComparing;
-      };
+  private static final Comparator<MergeSortKey> TIME_ASC_COMPARATOR =
+      Comparator.comparingLong(
+          (MergeSortKey sortKey) -> sortKey.tsBlock.getTimeByIndex(sortKey.rowIndex));
 
-  public static final Comparator<MergeSortKey> ASC_DEVICE_ASC_TIME =
-      (MergeSortKey o1, MergeSortKey o2) -> {
-        int deviceComparing =
-            o1.tsBlock
-                .getColumn(0)
-                .getBinary(o1.rowIndex)
-                .compareTo(o2.tsBlock.getColumn(0).getBinary(o2.rowIndex));
-        return deviceComparing == 0
-            ? Long.compare(
-                o1.tsBlock.getTimeByIndex(o1.rowIndex), o2.tsBlock.getTimeByIndex(o2.rowIndex))
-            : deviceComparing;
-      };
-  public static final Comparator<MergeSortKey> ASC_DEVICE_DESC_TIME =
-      (MergeSortKey o1, MergeSortKey o2) -> {
-        int deviceComparing =
-            o1.tsBlock
-                .getColumn(0)
-                .getBinary(o1.rowIndex)
-                .compareTo(o2.tsBlock.getColumn(0).getBinary(o2.rowIndex));
-        return deviceComparing == 0
-            ? Long.compare(
-                o2.tsBlock.getTimeByIndex(o2.rowIndex), o1.tsBlock.getTimeByIndex(o1.rowIndex))
-            : deviceComparing;
-      };
-  public static final Comparator<MergeSortKey> DESC_DEVICE_ASC_TIME =
-      (MergeSortKey o1, MergeSortKey o2) -> {
-        int deviceComparing =
-            o2.tsBlock
-                .getColumn(0)
-                .getBinary(o2.rowIndex)
-                .compareTo(o1.tsBlock.getColumn(0).getBinary(o1.rowIndex));
-        return deviceComparing == 0
-            ? Long.compare(
-                o1.tsBlock.getTimeByIndex(o1.rowIndex), o2.tsBlock.getTimeByIndex(o2.rowIndex))
-            : deviceComparing;
-      };
-  public static final Comparator<MergeSortKey> DESC_DEVICE_DESC_TIME =
-      (MergeSortKey o1, MergeSortKey o2) -> {
-        int deviceComparing =
-            o2.tsBlock
-                .getColumn(0)
-                .getBinary(o2.rowIndex)
-                .compareTo(o1.tsBlock.getColumn(0).getBinary(o1.rowIndex));
-        return deviceComparing == 0
-            ? Long.compare(
-                o2.tsBlock.getTimeByIndex(o2.rowIndex), o1.tsBlock.getTimeByIndex(o1.rowIndex))
-            : deviceComparing;
-      };
+  private static final Comparator<MergeSortKey> TIME_DESC_COMPARATOR =
+      Comparator.comparingLong(
+              (MergeSortKey sortKey) -> sortKey.tsBlock.getTimeByIndex(sortKey.rowIndex))
+          .reversed();
 
+  /** @param indexList -1 for time column */
   public static Comparator<MergeSortKey> getComparator(
       List<SortItem> sortItemList, List<Integer> indexList, List<TSDataType> dataTypeList) {
-    // specified for order by time, device or order by device, time
-    if (sortItemList.size() == 2
-        && ((sortItemList.get(0).getSortKey() == SortKey.TIME
-                && sortItemList.get(1).getSortKey() == SortKey.DEVICE)
-            || (sortItemList.get(0).getSortKey() == SortKey.DEVICE
-                && sortItemList.get(1).getSortKey() == SortKey.TIME))) {
-      if (sortItemList.get(0).getOrdering() == Ordering.ASC) {
-        if (sortItemList.get(1).getOrdering() == Ordering.ASC) {
-          if (sortItemList.get(0).getSortKey() == SortKey.TIME) return ASC_TIME_ASC_DEVICE;
-          else return ASC_DEVICE_ASC_TIME;
-        } else {
-          if (sortItemList.get(0).getSortKey() == SortKey.TIME) return ASC_TIME_DESC_DEVICE;
-          else return ASC_DEVICE_DESC_TIME;
-        }
-      } else {
-        if (sortItemList.get(1).getOrdering() == Ordering.ASC) {
-          if (sortItemList.get(0).getSortKey() == SortKey.TIME) return DESC_TIME_ASC_DEVICE;
-          else return DESC_DEVICE_ASC_TIME;
-        } else {
-          if (sortItemList.get(0).getSortKey() == SortKey.TIME) return DESC_TIME_DESC_DEVICE;
-          else return DESC_DEVICE_DESC_TIME;
-        }
-      }
-    } else { // generally order by
-      // use code-gen compile this comparator
 
-      // currently only show queries use merge sort and will only contain TIME, QUERYID, DATANODEID,
-      // ELAPSEDTIME, STATEMENT
-      return genComparatorChain(sortItemList, indexList, dataTypeList);
-    }
-  }
-
-  /** @param indexList -1 for time column */
-  private static ComparatorChain<MergeSortKey> genComparatorChain(
-      List<SortItem> sortItemList, List<Integer> indexList, List<TSDataType> dataTypeList) {
+    // use code-gen compile this comparator
     List<Comparator<MergeSortKey>> list = new ArrayList<>(indexList.size());
     for (int i = 0; i < indexList.size(); i++) {
       int index = indexList.get(i);
       TSDataType dataType = dataTypeList.get(i);
       boolean asc = sortItemList.get(i).getOrdering() == Ordering.ASC;
-      list.add(genSingleComparator(asc, index, dataType));
+      boolean nullFirst = sortItemList.get(i).getNullOrdering() == NullOrdering.FIRST;
+      list.add(genSingleComparator(asc, index, dataType, nullFirst));
     }
     return new ComparatorChain<>(list);
   }
 
-  private static Comparator<MergeSortKey> genSingleComparator(
-      boolean asc, int index, TSDataType dataType) {
+  public static Comparator<MergeSortKey> getComparator(
+      TSDataType dataType, int index, boolean asc) {
     Comparator<MergeSortKey> comparator;
     switch (dataType) {
       case INT32:
@@ -189,16 +69,10 @@ public class MergeSortComparator {
                     sortKey.tsBlock.getColumn(index).getInt(sortKey.rowIndex));
         break;
       case INT64:
-        if (index == -1) {
-          comparator =
-              Comparator.comparingLong(
-                  (MergeSortKey sortKey) -> sortKey.tsBlock.getTimeByIndex(sortKey.rowIndex));
-        } else {
-          comparator =
-              Comparator.comparingLong(
-                  (MergeSortKey sortKey) ->
-                      sortKey.tsBlock.getColumn(index).getLong(sortKey.rowIndex));
-        }
+        comparator =
+            Comparator.comparingLong(
+                (MergeSortKey sortKey) ->
+                    sortKey.tsBlock.getColumn(index).getLong(sortKey.rowIndex));
         break;
       case FLOAT:
         comparator =
@@ -218,12 +92,28 @@ public class MergeSortComparator {
                 (MergeSortKey sortKey) ->
                     sortKey.tsBlock.getColumn(index).getBinary(sortKey.rowIndex));
         break;
+      case BOOLEAN:
+        comparator =
+            Comparator.comparing(
+                (MergeSortKey sortKey) ->
+                    sortKey.tsBlock.getColumn(index).getBoolean(sortKey.rowIndex));
+        break;
       default:
         throw new IllegalArgumentException("Data type: " + dataType + " cannot be ordered");
     }
     if (!asc) {
       comparator = comparator.reversed();
     }
+
     return comparator;
   }
+
+  private static Comparator<MergeSortKey> genSingleComparator(
+      boolean asc, int index, TSDataType dataType, boolean nullFirst) {
+
+    if (index == -1) {
+      return asc ? TIME_ASC_COMPARATOR : TIME_DESC_COMPARATOR;
+    }
+    return new MergeSortKeyComparator(index, nullFirst, getComparator(dataType, index, asc));
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortKeyComparator.java b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortKeyComparator.java
new file mode 100644
index 0000000000..8e8e8c6f85
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/join/merge/MergeSortKeyComparator.java
@@ -0,0 +1,53 @@
+/*
+ * 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.execution.operator.process.join.merge;
+
+import org.apache.iotdb.db.utils.datastructure.MergeSortKey;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+public class MergeSortKeyComparator implements Comparator<MergeSortKey>, Serializable {
+
+  private final boolean nullFirst;
+  private final int index;
+  private final Comparator<MergeSortKey> originalComparator;
+
+  public MergeSortKeyComparator(
+      int index, boolean nullFirst, Comparator<MergeSortKey> originalComparator) {
+    this.nullFirst = nullFirst;
+    this.index = index;
+    this.originalComparator = originalComparator;
+  }
+
+  @Override
+  public int compare(MergeSortKey o1, MergeSortKey o2) {
+    boolean o1IsNull = o1.tsBlock.getColumn(index).isNull(o1.rowIndex);
+    boolean o2IsNull = o2.tsBlock.getColumn(index).isNull(o2.rowIndex);
+
+    if (!o1IsNull && !o2IsNull) {
+      return originalComparator.compare(o1, o2);
+    } else if (o1IsNull) {
+      return o2IsNull ? 0 : (nullFirst ? -1 : 1);
+    } else {
+      return nullFirst ? 1 : -1;
+    }
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
index 3c4fd6e9ed..6b3f4780a6 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/Analysis.java
@@ -39,6 +39,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.GroupByTimeParameter;
 import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.IntoPathDescriptor;
 import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.OrderByParameter;
 import org.apache.iotdb.db.mpp.plan.statement.Statement;
+import org.apache.iotdb.db.mpp.plan.statement.component.SortItem;
 import org.apache.iotdb.db.mpp.plan.statement.crud.QueryStatement;
 import org.apache.iotdb.db.mpp.plan.statement.sys.ShowQueriesStatement;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
@@ -141,6 +142,12 @@ public class Analysis {
   // expression of group by that need to be calculated
   private Map<String, Expression> deviceToGroupByExpression;
 
+  // expression of order by that need to be calculated
+  private Map<String, Set<Expression>> deviceToOrderByExpressions;
+
+  // the sortItems used in order by push down of align  by device
+  private Map<String, List<SortItem>> deviceToSortItems;
+
   // e.g. [s1,s2,s3] is query, but [s1, s3] exists in device1, then device1 -> [1, 3], s1 is 1 but
   // not 0 because device is the first column
   private Map<String, List<Integer>> deviceViewInputIndexesMap;
@@ -166,6 +173,15 @@ public class Analysis {
 
   private Expression havingExpression;
 
+  // The expressions in order by clause
+  // In align by device orderByExpression is the deviceView of expression which doesn't have
+  // device-prefix
+  // for example, in device root.sg1.d1, [root.sg1.d1.s1] is expression and [s1] is the device-view
+  // one.
+  private Set<Expression> orderByExpressions;
+
+  private boolean orderByExpressionInDeviceView = false;
+
   // parameter of `FILL` clause
   private FillDescriptor fillDescriptor;
 
@@ -617,4 +633,37 @@ public class Analysis {
   public Map<NodeRef<Expression>, TSDataType> getExpressionTypes() {
     return expressionTypes;
   }
+
+  public void setOrderByExpressions(Set<Expression> orderByExpressions) {
+    this.orderByExpressions = orderByExpressions;
+  }
+
+  public Set<Expression> getOrderByExpressions() {
+    return orderByExpressions;
+  }
+
+  public Map<String, Set<Expression>> getDeviceToOrderByExpressions() {
+    return deviceToOrderByExpressions;
+  }
+
+  public void setDeviceToOrderByExpressions(
+      Map<String, Set<Expression>> deviceToOrderByExpressions) {
+    this.deviceToOrderByExpressions = deviceToOrderByExpressions;
+  }
+
+  public void setOrderByExpressionInDeviceView(boolean orderByExpressionInDeviceView) {
+    this.orderByExpressionInDeviceView = orderByExpressionInDeviceView;
+  }
+
+  public boolean isOrderByExpressionInDeviceView() {
+    return orderByExpressionInDeviceView;
+  }
+
+  public Map<String, List<SortItem>> getDeviceToSortItems() {
+    return deviceToSortItems;
+  }
+
+  public void setDeviceToSortItems(Map<String, List<SortItem>> deviceToSortItems) {
+    this.deviceToSortItems = deviceToSortItems;
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
index 52ad29445d..30162d3ad0 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeVisitor.java
@@ -289,6 +289,7 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
 
         analyzeDeviceToGroupBy(analysis, queryStatement, schemaTree, deviceSet);
         Map<String, Set<Expression>> deviceToAggregationExpressions = new HashMap<>();
+        analyzeDeviceToOrderBy(analysis, queryStatement, schemaTree, deviceSet);
         analyzeHaving(
             analysis, queryStatement, schemaTree, deviceSet, deviceToAggregationExpressions);
         analyzeDeviceToAggregation(analysis, queryStatement, deviceToAggregationExpressions);
@@ -313,6 +314,7 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
 
         analyzeGroupBy(analysis, queryStatement, schemaTree);
         analyzeHaving(analysis, queryStatement, schemaTree);
+        analyzeOrderBy(analysis, queryStatement, schemaTree);
 
         analyzeGroupByLevel(analysis, queryStatement, outputExpressionMap, outputExpressions);
         analyzeGroupByTag(analysis, queryStatement, outputExpressions);
@@ -413,7 +415,7 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
     if (orderByParameter != null && !orderByParameter.getSortItemList().isEmpty()) {
       List<SortItem> sortItemList = orderByParameter.getSortItemList();
       checkState(
-          sortItemList.size() == 1 && sortItemList.get(0).getSortKey() == SortKey.TIMESERIES,
+          sortItemList.size() == 1 && sortItemList.get(0).getSortKey().equals(SortKey.TIMESERIES),
           "Last queries only support sorting by timeseries now.");
       boolean isAscending = sortItemList.get(0).getOrdering() == Ordering.ASC;
       sourceExpressions =
@@ -880,6 +882,18 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
           .computeIfAbsent(deviceName, key -> new LinkedHashSet<>())
           .addAll(aggregationExpressions);
     }
+
+    if (queryStatement.hasOrderByExpression()) {
+      Map<String, Set<Expression>> deviceToOrderByExpressions =
+          analysis.getDeviceToOrderByExpressions();
+      for (String deviceName : deviceToOrderByExpressions.keySet()) {
+        for (Expression orderByExpression : deviceToOrderByExpressions.get(deviceName)) {
+          deviceToAggregationExpressions
+              .computeIfAbsent(deviceName, key -> new LinkedHashSet<>())
+              .addAll(ExpressionAnalyzer.searchAggregationExpressions(orderByExpression));
+        }
+      }
+    }
   }
 
   private void analyzeAggregation(Analysis analysis, QueryStatement queryStatement) {
@@ -902,6 +916,12 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
         aggregationExpressions.addAll(
             ExpressionAnalyzer.searchAggregationExpressions(analysis.getHavingExpression()));
       }
+      if (queryStatement.hasOrderByExpression()) {
+        for (Expression expression : analysis.getOrderByExpressions()) {
+          aggregationExpressions.addAll(
+              ExpressionAnalyzer.searchAggregationExpressions(expression));
+        }
+      }
       analysis.setAggregationExpressions(aggregationExpressions);
     }
   }
@@ -928,6 +948,15 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
       }
     } else {
       deviceToSourceTransformExpressions = analysis.getDeviceToSelectExpressions();
+      if (queryStatement.hasOrderByExpression()) {
+        Map<String, Set<Expression>> deviceToOrderByExpressions =
+            analysis.getDeviceToOrderByExpressions();
+        for (String deviceName : deviceToOrderByExpressions.keySet()) {
+          for (Expression expression : deviceToOrderByExpressions.get(deviceName)) {
+            deviceToSourceTransformExpressions.get(deviceName).add(expression);
+          }
+        }
+      }
     }
     analysis.setDeviceToSourceTransformExpressions(deviceToSourceTransformExpressions);
   }
@@ -939,11 +968,14 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
         // for AggregationExpression, only the first Expression of input need to transform
         sourceTransformExpressions.add(expression.getExpressions().get(0));
       }
+      if (queryStatement.hasGroupByExpression()) {
+        sourceTransformExpressions.add(analysis.getGroupByExpression());
+      }
     } else {
-      sourceTransformExpressions = analysis.getSelectExpressions();
-    }
-    if (queryStatement.hasGroupByExpression()) {
-      sourceTransformExpressions.add(analysis.getGroupByExpression());
+      sourceTransformExpressions.addAll(analysis.getSelectExpressions());
+      if (queryStatement.hasOrderByExpression()) {
+        sourceTransformExpressions.addAll(analysis.getOrderByExpressions());
+      }
     }
     analysis.setSourceTransformExpressions(sourceTransformExpressions);
   }
@@ -1090,8 +1122,17 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
         deviceViewOutputExpressions.addAll(
             ExpressionAnalyzer.searchAggregationExpressions(analysis.getHavingExpression()));
       }
+      if (queryStatement.hasOrderByExpression()) {
+        for (Expression orderByExpression : analysis.getOrderByExpressions()) {
+          deviceViewOutputExpressions.addAll(
+              ExpressionAnalyzer.searchAggregationExpressions(orderByExpression));
+        }
+      }
     } else {
-      deviceViewOutputExpressions = selectExpressions;
+      deviceViewOutputExpressions.addAll(selectExpressions);
+      if (queryStatement.hasOrderByExpression()) {
+        deviceViewOutputExpressions.addAll(analysis.getOrderByExpressions());
+      }
     }
     analysis.setDeviceViewOutputExpressions(deviceViewOutputExpressions);
 
@@ -1180,10 +1221,35 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
     analysis.setRespDatasetHeader(new DatasetHeader(columnHeaders, isIgnoreTimestamp));
   }
 
+  // For last query
   private void analyzeOrderBy(Analysis analysis, QueryStatement queryStatement) {
     analysis.setMergeOrderParameter(new OrderByParameter(queryStatement.getSortItemList()));
   }
 
+  private void analyzeOrderBy(
+      Analysis analysis, QueryStatement queryStatement, ISchemaTree schemaTree) {
+    if (!queryStatement.hasOrderByExpression()) return;
+
+    Set<Expression> orderByExpressions = new LinkedHashSet<>();
+    for (Expression expressionForItem : queryStatement.getExpressionSortItemList()) {
+      // Expression in a sortItem only indicates one column
+      List<Expression> expressions =
+          ExpressionAnalyzer.removeWildcardInExpression(expressionForItem, schemaTree);
+      if (expressions.size() != 1) {
+        throw new SemanticException("One sort item in order by should only indicate one value");
+      }
+      expressionForItem = expressions.get(0);
+      TSDataType dataType = analyzeExpression(analysis, expressionForItem);
+      if (!dataType.isComparable()) {
+        throw new SemanticException(
+            String.format("The data type of sort item %s is not comparable", dataType));
+      }
+      orderByExpressions.add(expressionForItem);
+    }
+    analysis.setOrderByExpressions(orderByExpressions);
+    queryStatement.updateSortItems(orderByExpressions);
+  }
+
   private TSDataType analyzeExpression(Analysis analysis, Expression expression) {
     ExpressionTypeAnalyzer.analyzeExpression(analysis, expression);
     return analysis.getType(expression);
@@ -1258,6 +1324,56 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
     }
   }
 
+  private void analyzeDeviceToOrderBy(
+      Analysis analysis,
+      QueryStatement queryStatement,
+      ISchemaTree schemaTree,
+      Set<PartialPath> deviceSet) {
+
+    if (!queryStatement.hasOrderByExpression()) {
+      return;
+    }
+
+    Map<String, Set<Expression>> deviceToOrderByExpressions = new LinkedHashMap<>();
+    Map<String, List<SortItem>> deviceToSortItems = new LinkedHashMap<>();
+    // build the device-view outputColumn for the sortNode above the deviceViewNode
+    Set<Expression> deviceViewOrderByExpression = new LinkedHashSet<>();
+    for (PartialPath device : deviceSet) {
+      Set<Expression> orderByExpressionsForOneDevice = new LinkedHashSet<>();
+      for (Expression expressionForItem : queryStatement.getExpressionSortItemList()) {
+        List<Expression> expressions =
+            ExpressionAnalyzer.concatDeviceAndRemoveWildcard(expressionForItem, device, schemaTree);
+        if (expressions.size() != 1) {
+          throw new SemanticException(
+              String.format(
+                  "One sort item in order by should only indicate one value, got %s value(s)",
+                  expressions.size()));
+        }
+        expressionForItem = expressions.get(0);
+        TSDataType dataType = analyzeExpression(analysis, expressionForItem);
+        if (!dataType.isComparable()) {
+          throw new SemanticException(
+              String.format("The data type of sort item %s is not comparable", dataType));
+        }
+
+        Expression devicerViewExpression =
+            ExpressionAnalyzer.getMeasurementExpression(expressionForItem);
+        analyzeExpression(analysis, devicerViewExpression);
+
+        deviceViewOrderByExpression.add(devicerViewExpression);
+        orderByExpressionsForOneDevice.add(expressionForItem);
+      }
+      deviceToSortItems.put(
+          device.getFullPath(), queryStatement.getUpdatedSortItems(orderByExpressionsForOneDevice));
+      deviceToOrderByExpressions.put(device.getFullPath(), orderByExpressionsForOneDevice);
+    }
+
+    analysis.setOrderByExpressions(deviceViewOrderByExpression);
+    queryStatement.updateSortItems(deviceViewOrderByExpression);
+    analysis.setDeviceToSortItems(deviceToSortItems);
+    analysis.setDeviceToOrderByExpressions(deviceToOrderByExpressions);
+  }
+
   private void analyzeGroupBy(
       Analysis analysis, QueryStatement queryStatement, ISchemaTree schemaTree) {
 
@@ -2589,9 +2705,8 @@ public class AnalyzeVisitor extends StatementVisitor<Analysis, MPPQueryContext>
         schemaTree
             .getMatchedDevices(new PartialPath(devicePattern))
             .forEach(
-                deviceSchemaInfo -> {
-                  deduplicatedDevicePaths.add(deviceSchemaInfo.getDevicePath().getFullPath());
-                });
+                deviceSchemaInfo ->
+                    deduplicatedDevicePaths.add(deviceSchemaInfo.getDevicePath().getFullPath()));
       } catch (IllegalPathException ignored) {
         // won't happen
       }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java
index 1dca937376..c1a611cafc 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ConcatPathRewriter.java
@@ -61,6 +61,12 @@ public class ConcatPathRewriter {
             prefixPaths,
             patternTree);
       }
+      if (queryStatement.hasOrderByExpression()) {
+        for (Expression sortItemExpression : queryStatement.getExpressionSortItemList()) {
+          ExpressionAnalyzer.constructPatternTreeFromExpression(
+              sortItemExpression, prefixPaths, patternTree);
+        }
+      }
     } else {
       // concat SELECT with FROM
       List<ResultColumn> resultColumns =
@@ -68,7 +74,7 @@ public class ConcatPathRewriter {
               queryStatement.getSelectComponent(), prefixPaths, queryStatement.isGroupByLevel());
       queryStatement.getSelectComponent().setResultColumns(resultColumns);
 
-      // concat GROUP BY VARIATION with FROM
+      // concat GROUP BY with FROM
       if (queryStatement.hasGroupByExpression()) {
         queryStatement
             .getGroupByComponent()
@@ -77,6 +83,11 @@ public class ConcatPathRewriter {
                     queryStatement.getGroupByComponent().getControlColumnExpression(),
                     prefixPaths));
       }
+      if (queryStatement.hasOrderByExpression()) {
+        List<Expression> sortItemExpressions = queryStatement.getExpressionSortItemList();
+        sortItemExpressions.replaceAll(
+            expression -> contactOrderByWithFrom(expression, prefixPaths));
+      }
     }
 
     // concat WHERE with FROM
@@ -124,4 +135,13 @@ public class ConcatPathRewriter {
     }
     return resultExpressions.get(0);
   }
+
+  private Expression contactOrderByWithFrom(Expression expression, List<PartialPath> prefixPaths) {
+    List<Expression> resultExpressions =
+        ExpressionAnalyzer.concatExpressionWithSuffixPaths(expression, prefixPaths, patternTree);
+    if (resultExpressions.size() != 1) {
+      throw new IllegalStateException("Expression in order by should indicate one value");
+    }
+    return resultExpressions.get(0);
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ColumnTransformerVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ColumnTransformerVisitor.java
index 90cc519cbe..2a11d8e896 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ColumnTransformerVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ColumnTransformerVisitor.java
@@ -94,7 +94,8 @@ public class ColumnTransformerVisitor
   @Override
   public ColumnTransformer visitUnaryExpression(
       UnaryExpression unaryExpression, ColumnTransformerVisitorContext context) {
-    if (!context.cache.containsKey(unaryExpression)) {
+    if (!context.cache.containsKey(unaryExpression)
+        && !generateIdentityColumnTransformerIfPossible(unaryExpression, context)) {
       if (context.hasSeen.containsKey(unaryExpression)) {
         IdentityColumnTransformer identity =
             new IdentityColumnTransformer(
@@ -125,7 +126,8 @@ public class ColumnTransformerVisitor
   @Override
   public ColumnTransformer visitBinaryExpression(
       BinaryExpression binaryExpression, ColumnTransformerVisitorContext context) {
-    if (!context.cache.containsKey(binaryExpression)) {
+    if (!context.cache.containsKey(binaryExpression)
+        && !generateIdentityColumnTransformerIfPossible(binaryExpression, context)) {
       if (context.hasSeen.containsKey(binaryExpression)) {
         IdentityColumnTransformer identity =
             new IdentityColumnTransformer(
@@ -160,7 +162,8 @@ public class ColumnTransformerVisitor
   @Override
   public ColumnTransformer visitTernaryExpression(
       TernaryExpression ternaryExpression, ColumnTransformerVisitorContext context) {
-    if (!context.cache.containsKey(ternaryExpression)) {
+    if (!context.cache.containsKey(ternaryExpression)
+        && !generateIdentityColumnTransformerIfPossible(ternaryExpression, context)) {
       if (context.hasSeen.containsKey(ternaryExpression)) {
         IdentityColumnTransformer identity =
             new IdentityColumnTransformer(
@@ -199,7 +202,8 @@ public class ColumnTransformerVisitor
   public ColumnTransformer visitFunctionExpression(
       FunctionExpression functionExpression, ColumnTransformerVisitorContext context) {
     List<Expression> expressions = functionExpression.getExpressions();
-    if (!context.cache.containsKey(functionExpression)) {
+    if (!context.cache.containsKey(functionExpression)
+        && !generateIdentityColumnTransformerIfPossible(functionExpression, context)) {
       if (context.hasSeen.containsKey(functionExpression)) {
         IdentityColumnTransformer identity =
             new IdentityColumnTransformer(
@@ -344,7 +348,8 @@ public class ColumnTransformerVisitor
   @Override
   public ColumnTransformer visitCaseWhenThenExpression(
       CaseWhenThenExpression caseWhenThenExpression, ColumnTransformerVisitorContext context) {
-    if (!context.cache.containsKey(caseWhenThenExpression)) {
+    if (!context.cache.containsKey(caseWhenThenExpression)
+        && !generateIdentityColumnTransformerIfPossible(caseWhenThenExpression, context)) {
       if (context.hasSeen.containsKey(caseWhenThenExpression)) {
         IdentityColumnTransformer identity =
             new IdentityColumnTransformer(
@@ -381,6 +386,29 @@ public class ColumnTransformerVisitor
     return res;
   }
 
+  /**
+   * the input could be calculated expressions that we can use directly and we do not need to do
+   * further calculation if so
+   *
+   * @return true if the expression has been calculated
+   */
+  private boolean generateIdentityColumnTransformerIfPossible(
+      Expression expression, ColumnTransformerVisitorContext context) {
+    List<InputLocation> inputLocations =
+        context.inputLocations.get(expression.getExpressionString());
+    if (inputLocations != null) {
+      IdentityColumnTransformer identity =
+          new IdentityColumnTransformer(
+              TypeFactory.getType(context.getType(expression)),
+              inputLocations.get(0).getValueColumnIndex());
+      // add to leafList
+      context.leafList.add(identity);
+      context.cache.put(expression, identity);
+      return true;
+    }
+    return false;
+  }
+
   private ColumnTransformer getConcreteUnaryColumnTransformer(
       Expression expression, ColumnTransformer childColumnTransformer, Type returnType) {
     switch (expression.getExpressionType()) {
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 9475f5ca5b..4e9986d4fb 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
@@ -85,6 +85,7 @@ import org.apache.iotdb.db.mpp.plan.statement.component.GroupByVariationComponen
 import org.apache.iotdb.db.mpp.plan.statement.component.HavingCondition;
 import org.apache.iotdb.db.mpp.plan.statement.component.IntoComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.IntoItem;
+import org.apache.iotdb.db.mpp.plan.statement.component.NullOrdering;
 import org.apache.iotdb.db.mpp.plan.statement.component.OrderByComponent;
 import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
 import org.apache.iotdb.db.mpp.plan.statement.component.ResultColumn;
@@ -1372,32 +1373,49 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
   // ---- Order By Clause
   // all SortKeys should be contained by limitSet
   private OrderByComponent parseOrderByClause(
-      IoTDBSqlParser.OrderByClauseContext ctx, ImmutableSet<SortKey> limitSet) {
+      IoTDBSqlParser.OrderByClauseContext ctx, ImmutableSet<String> limitSet) {
     OrderByComponent orderByComponent = new OrderByComponent();
-    Set<SortKey> sortKeySet = new HashSet<>();
+    Set<String> sortKeySet = new HashSet<>();
     for (IoTDBSqlParser.OrderByAttributeClauseContext orderByAttributeClauseContext :
         ctx.orderByAttributeClause()) {
-      SortItem sortItem = parseOrderByAttributeClause(orderByAttributeClauseContext);
-
-      SortKey sortKey = sortItem.getSortKey();
-      if (!limitSet.contains(sortKey)) {
-        throw new SemanticException(
-            String.format("ORDER BY: sort key[%s] is not contained in '%s'", sortKey, limitSet));
+      // if the order by clause is unique, then the following sort keys will be ignored
+      if (orderByComponent.isUnique()) {
+        break;
       }
+      SortItem sortItem = parseOrderByAttributeClause(orderByAttributeClauseContext, limitSet);
+
+      String sortKey = sortItem.getSortKey();
       if (sortKeySet.contains(sortKey)) {
-        throw new SemanticException(String.format("ORDER BY: duplicate sort key '%s'", sortKey));
+        continue;
       } else {
         sortKeySet.add(sortKey);
+      }
+
+      if (sortItem.isExpression()) {
+        orderByComponent.addExpressionSortItem(sortItem);
+      } else {
         orderByComponent.addSortItem(sortItem);
       }
     }
     return orderByComponent;
   }
 
-  private SortItem parseOrderByAttributeClause(IoTDBSqlParser.OrderByAttributeClauseContext ctx) {
-    return new SortItem(
-        SortKey.valueOf(ctx.sortKey().getText().toUpperCase()),
-        ctx.DESC() != null ? Ordering.DESC : Ordering.ASC);
+  private SortItem parseOrderByAttributeClause(
+      IoTDBSqlParser.OrderByAttributeClauseContext ctx, ImmutableSet<String> limitSet) {
+    if (ctx.sortKey() != null) {
+      String sortKey = ctx.sortKey().getText().toUpperCase();
+      if (!limitSet.contains(sortKey)) {
+        throw new SemanticException(
+            String.format("ORDER BY: sort key[%s] is not contained in '%s'", sortKey, limitSet));
+      }
+      return new SortItem(sortKey, ctx.DESC() != null ? Ordering.DESC : Ordering.ASC);
+    } else {
+      Expression sortExpression = parseExpression(ctx.expression(), true);
+      return new SortItem(
+          sortExpression,
+          ctx.DESC() != null ? Ordering.DESC : Ordering.ASC,
+          ctx.FIRST() != null ? NullOrdering.FIRST : NullOrdering.LAST);
+    }
   }
 
   // ---- Fill Clause
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java
index e36e29aece..8ac7b9ac8b 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanBuilder.java
@@ -90,6 +90,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.OrderByParameter;
 import org.apache.iotdb.db.mpp.plan.statement.component.Ordering;
 import org.apache.iotdb.db.mpp.plan.statement.component.SortItem;
 import org.apache.iotdb.db.mpp.plan.statement.component.SortKey;
+import org.apache.iotdb.db.mpp.plan.statement.crud.QueryStatement;
 import org.apache.iotdb.db.mpp.plan.statement.sys.ShowQueriesStatement;
 import org.apache.iotdb.db.utils.SchemaUtils;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
@@ -100,10 +101,10 @@ import org.apache.commons.lang.Validate;
 
 import java.time.ZoneId;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -535,56 +536,32 @@ public class LogicalPlanBuilder {
       Map<String, PlanNode> deviceNameToSourceNodesMap,
       Set<Expression> deviceViewOutputExpressions,
       Map<String, List<Integer>> deviceToMeasurementIndexesMap,
-      List<SortItem> sortItemList) {
+      Set<Expression> selectExpression,
+      QueryStatement queryStatement) {
     List<String> outputColumnNames =
         deviceViewOutputExpressions.stream()
             .map(Expression::getExpressionString)
             .collect(Collectors.toList());
 
-    int timePriority = -1, devicePriority = -1;
-    for (int i = 0; i < sortItemList.size(); i++) {
-      SortKey sortKey = sortItemList.get(i).getSortKey();
-      if (sortKey == SortKey.TIME) {
-        timePriority = sortItemList.size() - i;
-      } else if (sortKey == SortKey.DEVICE) {
-        devicePriority = sortItemList.size() - i;
-      }
+    List<SortItem> sortItemList = queryStatement.getSortItemList();
+
+    if (sortItemList.isEmpty()) {
+      sortItemList = new ArrayList<>();
+    }
+    if (!queryStatement.isOrderByDevice()) {
+      sortItemList.add(new SortItem(SortKey.DEVICE, Ordering.ASC));
+    }
+    if (!queryStatement.isOrderByTime()) {
+      sortItemList.add(new SortItem(SortKey.TIME, Ordering.ASC));
     }
-    Ordering deviceOrdering =
-        devicePriority == -1
-            ? Ordering.ASC
-            : sortItemList.get(sortItemList.size() - devicePriority).getOrdering();
-    Ordering timeOrdering =
-        timePriority == -1
-            ? Ordering.ASC
-            : sortItemList.get(sortItemList.size() - timePriority).getOrdering();
-
-    if ((timePriority == -1 && devicePriority == -1) || devicePriority > timePriority) {
-      DeviceViewNode deviceViewNode =
-          new DeviceViewNode(
-              context.getQueryId().genPlanNodeId(),
-              new OrderByParameter(
-                  Arrays.asList(
-                      new SortItem(SortKey.DEVICE, deviceOrdering),
-                      new SortItem(SortKey.TIME, timeOrdering))),
-              outputColumnNames,
-              deviceToMeasurementIndexesMap);
 
-      for (Map.Entry<String, PlanNode> entry : deviceNameToSourceNodesMap.entrySet()) {
-        String deviceName = entry.getKey();
-        PlanNode subPlan = entry.getValue();
-        deviceViewNode.addChildDeviceNode(deviceName, subPlan);
-      }
-      this.root = deviceViewNode;
-    } else {
+    OrderByParameter orderByParameter = new OrderByParameter(sortItemList);
+
+    // order by time, device can be optimized by SingleDeviceViewNode and MergeSortNode
+    if (queryStatement.isOrderByBasedOnTime() && !queryStatement.hasOrderByExpression()) {
       MergeSortNode mergeSortNode =
           new MergeSortNode(
-              context.getQueryId().genPlanNodeId(),
-              new OrderByParameter(
-                  Arrays.asList(
-                      new SortItem(SortKey.TIME, timeOrdering),
-                      new SortItem(SortKey.DEVICE, deviceOrdering))),
-              outputColumnNames);
+              context.getQueryId().genPlanNodeId(), orderByParameter, outputColumnNames);
       for (Map.Entry<String, PlanNode> entry : deviceNameToSourceNodesMap.entrySet()) {
         String deviceName = entry.getKey();
         PlanNode subPlan = entry.getValue();
@@ -598,10 +575,38 @@ public class LogicalPlanBuilder {
         mergeSortNode.addChild(singleDeviceViewNode);
       }
       this.root = mergeSortNode;
+    } else {
+      DeviceViewNode deviceViewNode =
+          new DeviceViewNode(
+              context.getQueryId().genPlanNodeId(),
+              orderByParameter,
+              outputColumnNames,
+              deviceToMeasurementIndexesMap);
+
+      for (Map.Entry<String, PlanNode> entry : deviceNameToSourceNodesMap.entrySet()) {
+        String deviceName = entry.getKey();
+        PlanNode subPlan = entry.getValue();
+        deviceViewNode.addChildDeviceNode(deviceName, subPlan);
+      }
+      this.root = deviceViewNode;
     }
 
     context.getTypeProvider().setType(DEVICE, TSDataType.TEXT);
     updateTypeProvider(deviceViewOutputExpressions);
+
+    if (queryStatement.needPushDownSort()) {
+      if (selectExpression.size() != deviceViewOutputExpressions.size()) {
+        this.root =
+            new TransformNode(
+                context.getQueryId().genPlanNodeId(),
+                root,
+                selectExpression.toArray(new Expression[0]),
+                queryStatement.isGroupByTime(),
+                queryStatement.getSelectComponent().getZoneId(),
+                queryStatement.getResultTimeOrder());
+      }
+    }
+
     return this;
   }
 
@@ -906,17 +911,24 @@ public class LogicalPlanBuilder {
     return this;
   }
 
-  public LogicalPlanBuilder planHaving(
+  public LogicalPlanBuilder planHavingAndTransform(
       Expression havingExpression,
       Set<Expression> selectExpressions,
+      Set<Expression> orderByExpression,
       boolean isGroupByTime,
       ZoneId zoneId,
       Ordering scanOrder) {
+
+    Set<Expression> outputExpressions = new HashSet<>(selectExpressions);
+    if (orderByExpression != null) {
+      outputExpressions.addAll(orderByExpression);
+    }
+
     if (havingExpression != null) {
       return planFilterAndTransform(
-          havingExpression, selectExpressions, isGroupByTime, zoneId, scanOrder);
+          havingExpression, outputExpressions, isGroupByTime, zoneId, scanOrder);
     } else {
-      return planTransform(selectExpressions, isGroupByTime, zoneId, scanOrder);
+      return planTransform(outputExpressions, isGroupByTime, zoneId, scanOrder);
     }
   }
 
@@ -1200,4 +1212,45 @@ public class LogicalPlanBuilder {
     this.root = new ShowQueriesNode(context.getQueryId().genPlanNodeId(), dataNodeLocation);
     return this;
   }
+
+  public LogicalPlanBuilder planOrderBy(
+      Set<Expression> orderByExpressions, List<SortItem> sortItemList) {
+
+    updateTypeProvider(orderByExpressions);
+    OrderByParameter orderByParameter = new OrderByParameter(sortItemList);
+    if (orderByParameter.isEmpty()) {
+      return this;
+    }
+    this.root = new SortNode(context.getQueryId().genPlanNodeId(), root, orderByParameter);
+    return this;
+  }
+
+  public LogicalPlanBuilder planOrderBy(
+      QueryStatement queryStatement,
+      Set<Expression> orderByExpressions,
+      Set<Expression> selectExpression) {
+    // only the order by clause having expression needs a sortNode
+    if (!queryStatement.hasOrderByExpression()) {
+      return this;
+    }
+
+    updateTypeProvider(orderByExpressions);
+    OrderByParameter orderByParameter = new OrderByParameter(queryStatement.getSortItemList());
+    if (orderByParameter.isEmpty()) {
+      return this;
+    }
+    this.root = new SortNode(context.getQueryId().genPlanNodeId(), root, orderByParameter);
+
+    if (root.getOutputColumnNames().size() != selectExpression.size()) {
+      this.root =
+          new TransformNode(
+              context.getQueryId().genPlanNodeId(),
+              root,
+              selectExpression.toArray(new Expression[0]),
+              queryStatement.isGroupByTime(),
+              queryStatement.getSelectComponent().getZoneId(),
+              queryStatement.getResultTimeOrder());
+    }
+    return this;
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
index 8895c253c1..a8cb30fac9 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/LogicalPlanVisitor.java
@@ -144,6 +144,13 @@ public class LogicalPlanVisitor extends StatementVisitor<PlanNode, MPPQueryConte
                         : null,
                     analysis.getDeviceViewInputIndexesMap().get(deviceName),
                     context));
+        // sortOperator push down
+        if (queryStatement.needPushDownSort()) {
+          subPlanBuilder =
+              subPlanBuilder.planOrderBy(
+                  analysis.getDeviceToOrderByExpressions().get(deviceName),
+                  analysis.getDeviceToSortItems().get(deviceName));
+        }
         deviceToSubPlanMap.put(deviceName, subPlanBuilder.getRoot());
       }
       // convert to ALIGN BY DEVICE view
@@ -152,7 +159,8 @@ public class LogicalPlanVisitor extends StatementVisitor<PlanNode, MPPQueryConte
               deviceToSubPlanMap,
               analysis.getDeviceViewOutputExpressions(),
               analysis.getDeviceViewInputIndexesMap(),
-              queryStatement.getSortItemList());
+              analysis.getSelectExpressions(),
+              queryStatement);
     } else {
       planBuilder =
           planBuilder.withNewRoot(
@@ -169,14 +177,21 @@ public class LogicalPlanVisitor extends StatementVisitor<PlanNode, MPPQueryConte
 
     if (queryStatement.isAggregationQuery()) {
       planBuilder =
-          planBuilder.planHaving(
+          planBuilder.planHavingAndTransform(
               analysis.getHavingExpression(),
               analysis.getSelectExpressions(),
+              analysis.getOrderByExpressions(),
               queryStatement.isGroupByTime(),
               queryStatement.getSelectComponent().getZoneId(),
               queryStatement.getResultTimeOrder());
     }
 
+    if (!queryStatement.needPushDownSort()) {
+      planBuilder =
+          planBuilder.planOrderBy(
+              queryStatement, analysis.getOrderByExpressions(), analysis.getSelectExpressions());
+    }
+
     // other upstream node
     planBuilder =
         planBuilder
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/OperatorTreeGenerator.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/OperatorTreeGenerator.java
index 5c79bfcdd9..d51bedfd86 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/OperatorTreeGenerator.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/OperatorTreeGenerator.java
@@ -797,7 +797,7 @@ public class OperatorTreeGenerator extends PlanVisitor<Operator, LocalExecutionP
     TimeSelector selector = null;
     TimeComparator timeComparator = null;
     for (SortItem sortItem : node.getMergeOrderParameter().getSortItemList()) {
-      if (sortItem.getSortKey() == SortKey.TIME) {
+      if (Objects.equals(sortItem.getSortKey(), SortKey.TIME)) {
         Ordering ordering = sortItem.getOrdering();
         if (ordering == Ordering.ASC) {
           selector = new TimeSelector(node.getChildren().size() << 1, true);
@@ -853,12 +853,12 @@ public class OperatorTreeGenerator extends PlanVisitor<Operator, LocalExecutionP
       List<TSDataType> sortItemDataTypeList) {
     sortItemList.forEach(
         sortItem -> {
-          if (sortItem.getSortKey() == SortKey.TIME) {
+          if (sortItem.getSortKey().equals(SortKey.TIME)) {
             sortItemIndexList.add(-1);
             sortItemDataTypeList.add(TSDataType.INT64);
           } else {
             for (int i = 0; i < outputColumnNames.size(); i++) {
-              if (sortItem.getSortKey().toString().equalsIgnoreCase(outputColumnNames.get(i))) {
+              if (sortItem.getSortKey().equalsIgnoreCase(outputColumnNames.get(i))) {
                 sortItemIndexList.add(i);
                 sortItemDataTypeList.add(dataTypes.get(i));
                 break;
@@ -1601,7 +1601,8 @@ public class OperatorTreeGenerator extends PlanVisitor<Operator, LocalExecutionP
             .addOperatorContext(
                 context.getNextOperatorId(),
                 node.getPlanNodeId(),
-                DeviceViewIntoOperator.class.getSimpleName());
+                SortOperator.class.getSimpleName());
+
     List<TSDataType> dataTypes = getOutputColumnTypes(node, context.getTypeProvider());
 
     List<SortItem> sortItemList = node.getOrderByParameter().getSortItemList();
@@ -2201,7 +2202,8 @@ public class OperatorTreeGenerator extends PlanVisitor<Operator, LocalExecutionP
     List<SortItem> sortItemList = node.getMergeOrderParameter().getSortItemList();
     checkArgument(
         sortItemList.isEmpty()
-            || (sortItemList.size() == 1 && sortItemList.get(0).getSortKey() == SortKey.TIMESERIES),
+            || (sortItemList.size() == 1
+                && Objects.equals(sortItemList.get(0).getSortKey(), SortKey.TIMESERIES)),
         "Last query only support order by timeseries asc/desc");
 
     context.setLastQueryTimeFilter(node.getTimeFilter());
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/DistributionPlanner.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/DistributionPlanner.java
index 2f1e85aedf..8d35c5290f 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/DistributionPlanner.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/DistributionPlanner.java
@@ -40,7 +40,6 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.node.sink.IdentitySinkNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.sink.MultiChildrenSinkNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.sink.ShuffleSinkNode;
 import org.apache.iotdb.db.mpp.plan.statement.component.OrderByComponent;
-import org.apache.iotdb.db.mpp.plan.statement.component.SortKey;
 import org.apache.iotdb.db.mpp.plan.statement.crud.QueryStatement;
 
 import org.apache.commons.lang3.Validate;
@@ -164,8 +163,8 @@ public class DistributionPlanner {
     OrderByComponent orderByComponent = queryStatement.getOrderByComponent();
     return nodeGroupContext.isAlignByDevice()
         && orderByComponent != null
-        && !(orderByComponent.getSortItemList().isEmpty()
-            || orderByComponent.getSortItemList().get(0).getSortKey().equals(SortKey.DEVICE));
+        && (!orderByComponent.getSortItemList().isEmpty()
+            && (orderByComponent.isBasedOnTime() && !queryStatement.hasOrderByExpression()));
   }
 
   public PlanNode optimize(PlanNode rootWithExchange) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/ExchangeNodeAdder.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/ExchangeNodeAdder.java
index 56dfda1cd7..edd96379b1 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/ExchangeNodeAdder.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/ExchangeNodeAdder.java
@@ -44,6 +44,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.MergeSortNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.MultiChildProcessNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SingleDeviceViewNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SlidingWindowAggregationNode;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SortNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.TimeJoinNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.TransformNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.last.LastQueryCollectNode;
@@ -269,6 +270,11 @@ public class ExchangeNodeAdder extends PlanVisitor<PlanNode, NodeGroupContext> {
     return processMultiChildNode(node, context);
   }
 
+  @Override
+  public PlanNode visitSort(SortNode node, NodeGroupContext context) {
+    return processOneChildNode(node, context);
+  }
+
   private PlanNode processMultiChildNode(MultiChildProcessNode node, NodeGroupContext context) {
     if (analysis.isVirtualSource()) {
       return processMultiChildNodeByLocation(node, context);
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/SourceRewriter.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/SourceRewriter.java
index 16771c6ad1..9bff84a243 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/SourceRewriter.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/planner/distribution/SourceRewriter.java
@@ -44,6 +44,7 @@ import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.MergeSortNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.MultiChildProcessNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SingleDeviceViewNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SlidingWindowAggregationNode;
+import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.SortNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.TimeJoinNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.last.LastQueryCollectNode;
 import org.apache.iotdb.db.mpp.plan.planner.plan.node.process.last.LastQueryMergeNode;
@@ -195,6 +196,10 @@ public class SourceRewriter extends SimplePlanNodeRewriter<DistributionPlanConte
       return deviceViewNodeList;
     }
 
+    if (analysis.isOrderByExpressionInDeviceView()) {
+      return deviceViewNodeList;
+    }
+
     MergeSortNode mergeSortNode =
         new MergeSortNode(
             context.queryContext.getQueryId().genPlanNodeId(),
@@ -264,6 +269,38 @@ public class SourceRewriter extends SimplePlanNodeRewriter<DistributionPlanConte
     }
   }
 
+  @Override
+  public List<PlanNode> visitSort(SortNode node, DistributionPlanContext context) {
+
+    if (node.getChild() instanceof DeviceViewNode) {
+      analysis.setOrderByExpressionInDeviceView(true);
+    }
+
+    List<PlanNode> children = rewrite(node.getChild(), context);
+    if (children.size() == 1) {
+      node.setChild(children.get(0));
+      return Collections.singletonList(node);
+    }
+
+    MergeSortNode mergeSortNode =
+        new MergeSortNode(
+            context.queryContext.getQueryId().genPlanNodeId(),
+            node.getOrderByParameter(),
+            node.getOutputColumnNames());
+
+    for (PlanNode child : children) {
+      SortNode sortNode = cloneSortNodeWithOutChild(node, context);
+      sortNode.setChild(child);
+      mergeSortNode.addChild(sortNode);
+    }
+    return Collections.singletonList(mergeSortNode);
+  }
+
+  private SortNode cloneSortNodeWithOutChild(SortNode node, DistributionPlanContext context) {
+    return new SortNode(
+        context.queryContext.getQueryId().genPlanNodeId(), node.getOrderByParameter());
+  }
+
   @Override
   public List<PlanNode> visitSchemaQueryMerge(
       SchemaQueryMergeNode node, DistributionPlanContext context) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/NullOrdering.java
similarity index 79%
copy from server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java
copy to server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/NullOrdering.java
index b39d16fbba..0821fbfdc3 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/NullOrdering.java
@@ -19,12 +19,15 @@
 
 package org.apache.iotdb.db.mpp.plan.statement.component;
 
-public enum SortKey {
-  TIME,
-  TIMESERIES,
-  DEVICE,
-  QUERYID,
-  DATANODEID,
-  ELAPSEDTIME,
-  STATEMENT
+public enum NullOrdering {
+  FIRST,
+  LAST;
+
+  public boolean isFirst() {
+    return this == FIRST;
+  }
+
+  public NullOrdering reverse() {
+    return this == NullOrdering.FIRST ? NullOrdering.LAST : NullOrdering.FIRST;
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/OrderByComponent.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/OrderByComponent.java
index a3784773ad..5847d07917 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/OrderByComponent.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/OrderByComponent.java
@@ -19,6 +19,7 @@
 
 package org.apache.iotdb.db.mpp.plan.statement.component;
 
+import org.apache.iotdb.db.mpp.plan.expression.Expression;
 import org.apache.iotdb.db.mpp.plan.statement.StatementNode;
 
 import java.util.ArrayList;
@@ -30,6 +31,7 @@ import static com.google.common.base.Preconditions.checkState;
 public class OrderByComponent extends StatementNode {
 
   private final List<SortItem> sortItemList;
+  private final List<Expression> sortItemExpressionList;
 
   private boolean orderByTime = false;
   private int timeOrderPriority = -1;
@@ -42,26 +44,54 @@ public class OrderByComponent extends StatementNode {
 
   public OrderByComponent() {
     this.sortItemList = new ArrayList<>();
+    this.sortItemExpressionList = new ArrayList<>();
   }
 
   public void addSortItem(SortItem sortItem) {
     this.sortItemList.add(sortItem);
-    if (sortItem.getSortKey() == SortKey.TIME) {
-      orderByTime = true;
-      timeOrderPriority = sortItemList.size() - 1;
-    } else if (sortItem.getSortKey() == SortKey.TIMESERIES) {
-      orderByTimeseries = true;
-      timeseriesOrderPriority = sortItemList.size() - 1;
-    } else {
-      orderByDevice = true;
-      deviceOrderPriority = sortItemList.size() - 1;
+    switch (sortItem.getSortKey()) {
+      case SortKey.TIME:
+        orderByTime = true;
+        timeOrderPriority = sortItemList.size() - 1;
+        break;
+      case SortKey.TIMESERIES:
+        orderByTimeseries = true;
+        timeseriesOrderPriority = sortItemList.size() - 1;
+        break;
+      case SortKey.DEVICE:
+        orderByDevice = true;
+        deviceOrderPriority = sortItemList.size() - 1;
+        break;
     }
   }
 
+  // if the sortItem can specify one unique time series
+  public boolean isUnique() {
+    return orderByDevice && orderByTime;
+  }
+
+  // if the first sortItem is device
+  public boolean isBasedOnDevice() {
+    return orderByDevice && deviceOrderPriority == 0;
+  }
+
+  public boolean isBasedOnTime() {
+    return orderByTime && timeOrderPriority == 0;
+  }
+
+  public void addExpressionSortItem(SortItem sortItem) {
+    this.sortItemList.add(sortItem);
+    this.sortItemExpressionList.add(sortItem.getExpression());
+  }
+
   public List<SortItem> getSortItemList() {
     return sortItemList;
   }
 
+  public List<Expression> getExpressionSortItemList() {
+    return sortItemExpressionList;
+  }
+
   public boolean isOrderByTime() {
     return orderByTime;
   }
@@ -89,6 +119,14 @@ public class OrderByComponent extends StatementNode {
     return sortItemList.get(deviceOrderPriority).getOrdering();
   }
 
+  public boolean isDeviceOrderInitialized() {
+    return deviceOrderPriority != -1;
+  }
+
+  public boolean isTimeOrderInitialized() {
+    return timeOrderPriority != -1;
+  }
+
   public String toSQLString() {
     StringBuilder sqlBuilder = new StringBuilder();
     sqlBuilder.append("ORDER BY ");
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortItem.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortItem.java
index 71e169b22d..8b9612f566 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortItem.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortItem.java
@@ -19,6 +19,7 @@
 
 package org.apache.iotdb.db.mpp.plan.statement.component;
 
+import org.apache.iotdb.db.mpp.plan.expression.Expression;
 import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
 
 import java.io.DataOutputStream;
@@ -28,15 +29,31 @@ import java.util.Objects;
 
 public class SortItem {
 
-  private final SortKey sortKey;
+  private String sortKey;
   private final Ordering ordering;
+  private final NullOrdering nullOrdering;
 
-  public SortItem(SortKey sortKey, Ordering ordering) {
+  // only used in analyzing period
+  private Expression expression;
+
+  public SortItem(String sortKey, Ordering ordering) {
+    this(sortKey, ordering, NullOrdering.LAST);
+  }
+
+  public SortItem(String sortKey, Ordering ordering, NullOrdering nullOrdering) {
     this.sortKey = sortKey;
     this.ordering = ordering;
+    this.nullOrdering = nullOrdering;
   }
 
-  public SortKey getSortKey() {
+  public SortItem(Expression expression, Ordering ordering, NullOrdering nullOrdering) {
+    this.expression = expression;
+    this.ordering = ordering;
+    this.nullOrdering = nullOrdering;
+    this.sortKey = expression.getExpressionString();
+  }
+
+  public String getSortKey() {
     return sortKey;
   }
 
@@ -44,24 +61,44 @@ public class SortItem {
     return ordering;
   }
 
+  public NullOrdering getNullOrdering() {
+    return nullOrdering;
+  }
+
+  public void setExpression(Expression expression) {
+    this.expression = expression;
+    this.sortKey = expression.getExpressionString();
+  }
+
+  public Expression getExpression() {
+    return expression;
+  }
+
+  public boolean isExpression() {
+    return expression != null;
+  }
+
   public SortItem reverse() {
-    return new SortItem(getSortKey(), getOrdering().reverse());
+    return new SortItem(getSortKey(), getOrdering().reverse(), getNullOrdering().reverse());
   }
 
   public void serialize(ByteBuffer byteBuffer) {
-    ReadWriteIOUtils.write(sortKey.ordinal(), byteBuffer);
+    ReadWriteIOUtils.write(sortKey, byteBuffer);
     ReadWriteIOUtils.write(ordering.ordinal(), byteBuffer);
+    ReadWriteIOUtils.write(nullOrdering.ordinal(), byteBuffer);
   }
 
   public void serialize(DataOutputStream stream) throws IOException {
-    ReadWriteIOUtils.write(sortKey.ordinal(), stream);
+    ReadWriteIOUtils.write(sortKey, stream);
     ReadWriteIOUtils.write(ordering.ordinal(), stream);
+    ReadWriteIOUtils.write(nullOrdering.ordinal(), stream);
   }
 
   public static SortItem deserialize(ByteBuffer byteBuffer) {
-    SortKey sortKey = SortKey.values()[ReadWriteIOUtils.readInt(byteBuffer)];
+    String sortKey = ReadWriteIOUtils.readString(byteBuffer);
     Ordering ordering = Ordering.values()[ReadWriteIOUtils.readInt(byteBuffer)];
-    return new SortItem(sortKey, ordering);
+    NullOrdering nullOrdering = NullOrdering.values()[ReadWriteIOUtils.readInt(byteBuffer)];
+    return new SortItem(sortKey, ordering, nullOrdering);
   }
 
   @Override
@@ -73,15 +110,17 @@ public class SortItem {
       return false;
     }
     SortItem sortItem = (SortItem) o;
-    return sortKey == sortItem.sortKey && ordering == sortItem.ordering;
+    return Objects.equals(sortKey, sortItem.sortKey)
+        && ordering == sortItem.ordering
+        && nullOrdering == sortItem.nullOrdering;
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(sortKey, ordering);
+    return Objects.hash(sortKey, ordering, nullOrdering);
   }
 
   public String toSQLString() {
-    return getSortKey().toString() + " " + getOrdering().toString();
+    return getSortKey() + " " + getOrdering().toString() + " " + getNullOrdering().toString();
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java
index b39d16fbba..650f6a3178 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/component/SortKey.java
@@ -19,12 +19,12 @@
 
 package org.apache.iotdb.db.mpp.plan.statement.component;
 
-public enum SortKey {
-  TIME,
-  TIMESERIES,
-  DEVICE,
-  QUERYID,
-  DATANODEID,
-  ELAPSEDTIME,
-  STATEMENT
+public class SortKey {
+  public static final String TIME = "TIME";
+  public static final String TIMESERIES = "TIMESERIES";
+  public static final String DEVICE = "DEVICE";
+  public static final String QUERYID = "QUERYID";
+  public static final String DATANODEID = "DATANODEID";
+  public static final String ELAPSEDTIME = "ELAPSEDTIME";
+  public static final String STATEMENT = "STATEMENT";
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
index 9f6d158037..00ecae2d99 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/statement/crud/QueryStatement.java
@@ -311,6 +311,10 @@ public class QueryStatement extends Statement {
     return isGroupByVariation() || isGroupByCondition() || isGroupByCount();
   }
 
+  public boolean hasOrderByExpression() {
+    return !getExpressionSortItemList().isEmpty();
+  }
+
   public boolean isAlignByTime() {
     return resultSetFormat == ResultSetFormat.ALIGN_BY_TIME;
   }
@@ -357,6 +361,19 @@ public class QueryStatement extends Statement {
     return orderByComponent.getDeviceOrder();
   }
 
+  // push down only support raw data query currently
+  public boolean needPushDownSort() {
+    return !isAggregationQuery() && hasOrderByExpression() && isOrderByBasedOnDevice();
+  }
+
+  public boolean isOrderByBasedOnDevice() {
+    return orderByComponent != null && orderByComponent.isBasedOnDevice();
+  }
+
+  public boolean isOrderByBasedOnTime() {
+    return orderByComponent != null && orderByComponent.isBasedOnTime();
+  }
+
   public List<SortItem> getSortItemList() {
     if (orderByComponent == null) {
       return Collections.emptyList();
@@ -364,6 +381,45 @@ public class QueryStatement extends Statement {
     return orderByComponent.getSortItemList();
   }
 
+  public List<Expression> getExpressionSortItemList() {
+    if (orderByComponent == null) {
+      return Collections.emptyList();
+    }
+    return orderByComponent.getExpressionSortItemList();
+  }
+
+  //  update the sortItems with expressionSortItems
+  public void updateSortItems(Set<Expression> orderByExpressions) {
+    Expression[] sortItemExpressions = orderByExpressions.toArray(new Expression[0]);
+    List<SortItem> sortItems = getSortItemList();
+    int expressionIndex = 0;
+    for (int i = 0; i < sortItems.size() && expressionIndex < sortItemExpressions.length; i++) {
+      SortItem sortItem = sortItems.get(i);
+      if (sortItem.isExpression()) {
+        sortItem.setExpression(sortItemExpressions[expressionIndex]);
+        expressionIndex++;
+      }
+    }
+  }
+
+  public List<SortItem> getUpdatedSortItems(Set<Expression> orderByExpressions) {
+    Expression[] sortItemExpressions = orderByExpressions.toArray(new Expression[0]);
+    List<SortItem> sortItems = getSortItemList();
+    List<SortItem> newSortItems = new ArrayList<>();
+    int expressionIndex = 0;
+    for (int i = 0; i < sortItems.size() && expressionIndex < sortItemExpressions.length; i++) {
+      SortItem sortItem = sortItems.get(i);
+      SortItem newSortItem =
+          new SortItem(sortItem.getSortKey(), sortItem.getOrdering(), sortItem.getNullOrdering());
+      if (sortItem.isExpression()) {
+        newSortItem.setExpression(sortItemExpressions[expressionIndex]);
+        expressionIndex++;
+      }
+      newSortItems.add(newSortItem);
+    }
+    return newSortItems;
+  }
+
   public boolean hasFill() {
     return fillComponent != null;
   }
@@ -416,6 +472,23 @@ public class QueryStatement extends Statement {
                 ? resultColumn.getAlias()
                 : resultColumn.getExpression().getExpressionString());
       }
+      for (Expression expression : getExpressionSortItemList()) {
+        if (expression instanceof FunctionExpression) {
+          if (!expression.isBuiltInAggregationFunctionExpression()) {
+            throw new SemanticException("Raw data and aggregation hybrid query is not supported.");
+          }
+        } else {
+          if (expression instanceof TimeSeriesOperand) {
+            throw new SemanticException("Raw data and aggregation hybrid query is not supported.");
+          }
+          for (Expression subExpression : expression.getExpressions()) {
+            if (!subExpression.isBuiltInAggregationFunctionExpression()) {
+              throw new SemanticException(
+                  "Raw data and aggregation hybrid query is not supported.");
+            }
+          }
+        }
+      }
       if (isGroupByTag()) {
         if (hasHaving()) {
           throw new SemanticException("Having clause is not supported yet in GROUP BY TAGS query");
@@ -443,6 +516,13 @@ public class QueryStatement extends Statement {
         throw new SemanticException(
             "Common queries and aggregated queries are not allowed to appear at the same time");
       }
+      for (Expression expression : getExpressionSortItemList()) {
+        for (Expression subExpression : expression.getExpressions()) {
+          if (subExpression.isBuiltInAggregationFunctionExpression()) {
+            throw new SemanticException("Raw data and aggregation hybrid query is not supported.");
+          }
+        }
+      }
     }
 
     if (hasWhere()) {
diff --git a/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/MergeSortOperatorTest.java b/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/MergeSortOperatorTest.java
index c4e1dae0d3..a8c51f77f8 100644
--- a/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/MergeSortOperatorTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/mpp/execution/operator/MergeSortOperatorTest.java
@@ -328,8 +328,8 @@ public class MergeSortOperatorTest {
                   Arrays.asList(
                       new SortItem(SortKey.TIME, timeOrdering),
                       new SortItem(SortKey.DEVICE, deviceOrdering)),
-                  null,
-                  null));
+                  Arrays.asList(-1, 0),
+                  Arrays.asList(TSDataType.INT64, TSDataType.TEXT)));
       mergeSortOperator
           .getOperatorContext()
           .setMaxRunTime(new Duration(500, TimeUnit.MILLISECONDS));
@@ -796,8 +796,8 @@ public class MergeSortOperatorTest {
                   Arrays.asList(
                       new SortItem(SortKey.TIME, timeOrdering),
                       new SortItem(SortKey.DEVICE, deviceOrdering)),
-                  null,
-                  null));
+                  Arrays.asList(-1, 0),
+                  Arrays.asList(TSDataType.INT64, TSDataType.TEXT)));
       mergeSortOperator1
           .getOperatorContext()
           .setMaxRunTime(new Duration(500, TimeUnit.MILLISECONDS));
@@ -810,8 +810,8 @@ public class MergeSortOperatorTest {
                   Arrays.asList(
                       new SortItem(SortKey.TIME, timeOrdering),
                       new SortItem(SortKey.DEVICE, deviceOrdering)),
-                  null,
-                  null));
+                  Arrays.asList(-1, 0),
+                  Arrays.asList(TSDataType.INT64, TSDataType.TEXT)));
       mergeSortOperator2
           .getOperatorContext()
           .setMaxRunTime(new Duration(500, TimeUnit.MILLISECONDS));
@@ -825,8 +825,8 @@ public class MergeSortOperatorTest {
                   Arrays.asList(
                       new SortItem(SortKey.TIME, timeOrdering),
                       new SortItem(SortKey.DEVICE, deviceOrdering)),
-                  null,
-                  null));
+                  Arrays.asList(-1, 0),
+                  Arrays.asList(TSDataType.INT64, TSDataType.TEXT)));
       mergeSortOperator
           .getOperatorContext()
           .setMaxRunTime(new Duration(500, TimeUnit.MILLISECONDS));
@@ -1269,8 +1269,8 @@ public class MergeSortOperatorTest {
                   Arrays.asList(
                       new SortItem(SortKey.DEVICE, deviceOrdering),
                       new SortItem(SortKey.TIME, timeOrdering)),
-                  null,
-                  null));
+                  Arrays.asList(0, -1),
+                  Arrays.asList(TSDataType.TEXT, TSDataType.INT64)));
       mergeSortOperator
           .getOperatorContext()
           .setMaxRunTime(new Duration(500, TimeUnit.MILLISECONDS));
diff --git a/server/src/test/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeTest.java b/server/src/test/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeTest.java
index ac16f04ec3..670553df0f 100644
--- a/server/src/test/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeTest.java
+++ b/server/src/test/java/org/apache/iotdb/db/mpp/plan/analyze/AnalyzeTest.java
@@ -758,6 +758,179 @@ public class AnalyzeTest {
     }
   }
 
+  @Test
+  public void testOrderByAnalyze() {
+    String sql = "select s1 from root.sg.d1 order by root.sg.d1.s1 + root.sg.d1.s2";
+    String sql2 = "select avg(s1) from root.sg.d1 order by avg(root.sg.d1.s1) + avg(root.sg.d1.s2)";
+    try {
+      Analysis actualAnalysis = analyzeSQL(sql);
+      Analysis expectedAnalysis = new Analysis();
+      expectedAnalysis.setOrderByExpressions(
+          Sets.newHashSet(
+              new AdditionExpression(
+                  new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+                  new TimeSeriesOperand(new PartialPath("root.sg.d1.s2")))));
+      expectedAnalysis.setSourceTransformExpressions(
+          Sets.newHashSet(
+              new AdditionExpression(
+                  new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+                  new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))),
+              new TimeSeriesOperand(new PartialPath("root.sg.d1.s1"))));
+      expectedAnalysis.setSourceExpressions(
+          Sets.newHashSet(
+              new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+              new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))));
+      assert actualAnalysis != null;
+      orderByAnalysisEqualTest(actualAnalysis, expectedAnalysis);
+
+      actualAnalysis = analyzeSQL(sql2);
+      expectedAnalysis = new Analysis();
+      expectedAnalysis.setOrderByExpressions(
+          Sets.newHashSet(
+              new AdditionExpression(
+                  new FunctionExpression(
+                      "avg",
+                      new LinkedHashMap<>(),
+                      Collections.singletonList(
+                          new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")))),
+                  new FunctionExpression(
+                      "avg",
+                      new LinkedHashMap<>(),
+                      Collections.singletonList(
+                          new TimeSeriesOperand(new PartialPath("root.sg.d1.s2")))))));
+      expectedAnalysis.setAggregationExpressions(
+          Sets.newHashSet(
+              new FunctionExpression(
+                  "avg",
+                  new LinkedHashMap<>(),
+                  Collections.singletonList(
+                      new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")))),
+              new FunctionExpression(
+                  "avg",
+                  new LinkedHashMap<>(),
+                  Collections.singletonList(
+                      new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))))));
+      expectedAnalysis.setSourceTransformExpressions(
+          Sets.newHashSet(
+              new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+              new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))));
+      expectedAnalysis.setSourceExpressions(
+          Sets.newHashSet(
+              new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+              new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))));
+      assert actualAnalysis != null;
+      orderByAnalysisEqualTest(actualAnalysis, expectedAnalysis);
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(sql + ", " + e.getMessage());
+    }
+  }
+
+  @Test
+  public void testAlignByDeviceOrderByAnalyze() {
+    String sql = "select s1 from root.sg.d1 order by s1 + s2 align by device";
+    String sql2 = "select avg(s1) from root.sg.d1 order by avg(s1) + avg(s2) align by device";
+    try {
+      Analysis actualAnalysis = analyzeSQL(sql);
+      Analysis expectedAnalysis = new Analysis();
+      expectedAnalysis.setOrderByExpressions(
+          Sets.newHashSet(
+              new AdditionExpression(
+                  new TimeSeriesOperand(new PartialPath("s1")),
+                  new TimeSeriesOperand(new PartialPath("s2")))));
+      expectedAnalysis.setDeviceToOrderByExpressions(
+          ImmutableMap.of(
+              "root.sg.d1",
+              Sets.newHashSet(
+                  new AdditionExpression(
+                      new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+                      new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))))));
+      expectedAnalysis.setDeviceToSourceTransformExpressions(
+          ImmutableMap.of(
+              "root.sg.d1",
+              Sets.newHashSet(
+                  new AdditionExpression(
+                      new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+                      new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))),
+                  new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")))));
+      expectedAnalysis.setDeviceToAggregationExpressions(ImmutableMap.of());
+      expectedAnalysis.setDeviceViewOutputExpressions(
+          Sets.newHashSet(
+              new TimeSeriesOperand(new PartialPath("Device")),
+              new TimeSeriesOperand(new PartialPath("s1")),
+              new AdditionExpression(
+                  new TimeSeriesOperand(new PartialPath("s1")),
+                  new TimeSeriesOperand(new PartialPath("s2")))));
+      assert actualAnalysis != null;
+      orderByAlignByDeviceAnalysisEqualTest(actualAnalysis, expectedAnalysis);
+
+      actualAnalysis = analyzeSQL(sql2);
+      expectedAnalysis = new Analysis();
+      expectedAnalysis.setOrderByExpressions(
+          Sets.newHashSet(
+              new AdditionExpression(
+                  new FunctionExpression(
+                      "avg",
+                      new LinkedHashMap<>(),
+                      Collections.singletonList(new TimeSeriesOperand(new PartialPath("s1")))),
+                  new FunctionExpression(
+                      "avg",
+                      new LinkedHashMap<>(),
+                      Collections.singletonList(new TimeSeriesOperand(new PartialPath("s2")))))));
+      expectedAnalysis.setDeviceToOrderByExpressions(
+          ImmutableMap.of(
+              "root.sg.d1",
+              Sets.newHashSet(
+                  new AdditionExpression(
+                      new FunctionExpression(
+                          "avg",
+                          new LinkedHashMap<>(),
+                          Collections.singletonList(
+                              new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")))),
+                      new FunctionExpression(
+                          "avg",
+                          new LinkedHashMap<>(),
+                          Collections.singletonList(
+                              new TimeSeriesOperand(new PartialPath("root.sg.d1.s2"))))))));
+      expectedAnalysis.setDeviceToAggregationExpressions(
+          ImmutableMap.of(
+              "root.sg.d1",
+              Sets.newHashSet(
+                  new FunctionExpression(
+                      "avg",
+                      new LinkedHashMap<>(),
+                      Collections.singletonList(
+                          new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")))),
+                  new FunctionExpression(
+                      "avg",
+                      new LinkedHashMap<>(),
+                      Collections.singletonList(
+                          new TimeSeriesOperand(new PartialPath("root.sg.d1.s2")))))));
+      expectedAnalysis.setDeviceToSourceTransformExpressions(
+          ImmutableMap.of(
+              "root.sg.d1",
+              Sets.newHashSet(
+                  new TimeSeriesOperand(new PartialPath("root.sg.d1.s1")),
+                  new TimeSeriesOperand(new PartialPath("root.sg.d1.s2")))));
+      expectedAnalysis.setDeviceViewOutputExpressions(
+          Sets.newHashSet(
+              new TimeSeriesOperand(new PartialPath("Device")),
+              new FunctionExpression(
+                  "avg",
+                  new LinkedHashMap<>(),
+                  Collections.singletonList(new TimeSeriesOperand(new PartialPath("s1")))),
+              new FunctionExpression(
+                  "avg",
+                  new LinkedHashMap<>(),
+                  Collections.singletonList(new TimeSeriesOperand(new PartialPath("s2"))))));
+      assert actualAnalysis != null;
+      orderByAlignByDeviceAnalysisEqualTest(actualAnalysis, expectedAnalysis);
+    } catch (Exception e) {
+      e.printStackTrace();
+      fail(sql + ", " + e.getMessage());
+    }
+  }
+
   @Test
   public void testAlias1() {
     Analysis analysis = analyzeSQL("select s1 as a, s2 as b from root.sg.d2.a, root.sg.d2.b");
@@ -853,4 +1026,31 @@ public class AnalyzeTest {
     assertEquals(expectedAnalysis.getRespDatasetHeader(), actualAnalysis.getRespDatasetHeader());
     assertEquals(expectedAnalysis.getGlobalTimeFilter(), actualAnalysis.getGlobalTimeFilter());
   }
+
+  private void orderByAnalysisEqualTest(Analysis actualAnalysis, Analysis expectedAnalysis) {
+    assertEquals(expectedAnalysis.getOrderByExpressions(), actualAnalysis.getOrderByExpressions());
+    assertEquals(
+        expectedAnalysis.getAggregationExpressions(), actualAnalysis.getAggregationExpressions());
+    assertEquals(
+        expectedAnalysis.getSourceTransformExpressions(),
+        actualAnalysis.getSourceTransformExpressions());
+    assertEquals(expectedAnalysis.getSourceExpressions(), actualAnalysis.getSourceExpressions());
+  }
+
+  private void orderByAlignByDeviceAnalysisEqualTest(
+      Analysis actualAnalysis, Analysis expectedAnalysis) {
+    assertEquals(
+        expectedAnalysis.getDeviceViewOutputExpressions(),
+        actualAnalysis.getDeviceViewOutputExpressions());
+    assertEquals(
+        expectedAnalysis.getDeviceToOrderByExpressions(),
+        actualAnalysis.getDeviceToOrderByExpressions());
+    assertEquals(expectedAnalysis.getOrderByExpressions(), actualAnalysis.getOrderByExpressions());
+    assertEquals(
+        expectedAnalysis.getDeviceToAggregationExpressions(),
+        actualAnalysis.getDeviceToAggregationExpressions());
+    assertEquals(
+        expectedAnalysis.getDeviceToSourceTransformExpressions(),
+        actualAnalysis.getDeviceToSourceTransformExpressions());
+  }
 }
diff --git a/tsfile/src/main/java/org/apache/iotdb/tsfile/file/metadata/enums/TSDataType.java b/tsfile/src/main/java/org/apache/iotdb/tsfile/file/metadata/enums/TSDataType.java
index a7dc943280..f4b88db27c 100644
--- a/tsfile/src/main/java/org/apache/iotdb/tsfile/file/metadata/enums/TSDataType.java
+++ b/tsfile/src/main/java/org/apache/iotdb/tsfile/file/metadata/enums/TSDataType.java
@@ -143,4 +143,21 @@ public enum TSDataType {
         throw new UnSupportedDataTypeException(this.toString());
     }
   }
+
+  /** @return whether a comparable datatype */
+  public boolean isComparable() {
+    switch (this) {
+      case INT32:
+      case INT64:
+      case FLOAT:
+      case DOUBLE:
+      case TEXT:
+      case BOOLEAN:
+        return true;
+      case VECTOR:
+        return false;
+      default:
+        throw new UnSupportedDataTypeException(this.toString());
+    }
+  }
 }