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/06 13:34:55 UTC

[iotdb] branch master updated: [IOTDB-5638] Support CASE WHEN expression

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 ad04cbaaf0 [IOTDB-5638] Support CASE WHEN expression
ad04cbaaf0 is described below

commit ad04cbaaf09ca32eaed355c8cf293105ea53a111
Author: Li Yu Heng <li...@126.com>
AuthorDate: Thu Apr 6 21:34:44 2023 +0800

    [IOTDB-5638] Support CASE WHEN expression
---
 .../org/apache/iotdb/db/qp/sql/IdentifierParser.g4 |   5 +-
 .../org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4   |   9 +
 .../antlr4/org/apache/iotdb/db/qp/sql/SqlLexer.g4  |  15 +
 docs/UserGuide/Operators-Functions/Conditional.md  | 351 +++++++++
 .../UserGuide/Operators-Functions/Conditional.md   | 347 ++++++++
 docs/zh/UserGuide/Operators-Functions/Overview.md  |   8 +
 .../iotdb/db/it/query/IoTDBCaseWhenThenIT.java     | 876 +++++++++++++++++++++
 .../operator/process/FilterAndProjectOperator.java |  22 +
 .../db/mpp/plan/analyze/ExpressionAnalyzer.java    |  97 ++-
 .../mpp/plan/analyze/ExpressionTypeAnalyzer.java   |  49 ++
 .../iotdb/db/mpp/plan/analyze/ExpressionUtils.java |  26 +
 .../iotdb/db/mpp/plan/expression/Expression.java   |  10 +
 .../db/mpp/plan/expression/ExpressionFactory.java  |  15 +
 .../db/mpp/plan/expression/ExpressionType.java     |   4 +
 .../plan/expression/binary/BinaryExpression.java   |   3 +-
 .../plan/expression/binary/WhenThenExpression.java |  73 ++
 .../expression/other/CaseWhenThenExpression.java   | 172 ++++
 .../visitor/CartesianProductVisitor.java           |  27 +
 .../plan/expression/visitor/CollectVisitor.java    |   7 +
 .../visitor/ColumnTransformerVisitor.java          |  44 ++
 .../visitor/ExpressionAnalyzeVisitor.java          |   2 +-
 .../plan/expression/visitor/ExpressionVisitor.java |  10 +
 .../visitor/IntermediateLayerVisitor.java          |   7 +
 .../expression/visitor/ReconstructVisitor.java     |   9 +
 .../iotdb/db/mpp/plan/parser/ASTVisitor.java       |  43 +
 .../dag/column/CaseWhenThenColumnTransformer.java  | 132 ++++
 .../other/CaseWhenThenExpressionTest.java          |  73 ++
 27 files changed, 2421 insertions(+), 15 deletions(-)

diff --git a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4 b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
index 6f27f903d9..116df06fa6 100644
--- a/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
+++ b/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IdentifierParser.g4
@@ -83,6 +83,7 @@ keyWords
     | DISCARD
     | DROP
     | ELAPSEDTIME
+    | ELSE
     | END
     | ENDTIME
     | EVERY
@@ -143,6 +144,7 @@ keyWords
     | PREVIOUS
     | PREVIOUSUNTILLAST
     | PRIVILEGES
+    | PRIVILEGE_VALUE
     | PROCESSLIST
     | PROPERTY
     | PRUNE
@@ -187,6 +189,7 @@ keyWords
     | TAGS
     | TASK
     | TEMPLATE
+    | THEN
     | TIMEOUT
     | TIMESERIES
     | TIMESLOTID
@@ -210,9 +213,9 @@ keyWords
     | VARIATION
     | VERIFY
     | VERSION
+    | WHEN
     | WHERE
     | WITH
     | WITHOUT
     | WRITABLE
-    | PRIVILEGE_VALUE
     ;
\ No newline at end of file
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 9a703e8946..67bdf2be21 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
@@ -1054,6 +1054,7 @@ expression
     | fullPathInExpression
     | scalarFunctionExpression
     | functionName LR_BRACKET expression (COMMA expression)* RR_BRACKET
+    | caseWhenThenExpression
     | (PLUS | MINUS | OPERATOR_NOT) expressionAfterUnaryOperator=expression
     | leftExpression=expression (STAR | DIV | MOD) rightExpression=expression
     | leftExpression=expression (PLUS | MINUS) rightExpression=expression
@@ -1066,6 +1067,14 @@ expression
     | leftExpression=expression OPERATOR_OR rightExpression=expression
     ;
 
+caseWhenThenExpression
+    : CASE caseExpression=expression? whenThenExpression+ (ELSE elseExpression=expression)? END
+    ;
+
+whenThenExpression
+    : WHEN whenExpression=expression THEN thenExpression=expression
+    ;
+
 functionName
     : identifier
     | COUNT
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 3f99561539..3bb4f3c3d9 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
@@ -818,6 +818,21 @@ WRITABLE
     : W R I T A B L E
     ;
 
+CASE
+    : C A S E
+    ;
+
+WHEN
+    : W H E N
+    ;
+
+THEN
+    : T H E N
+    ;
+
+ELSE
+    : E L S E
+    ;
 
 // Privileges Keywords
 
diff --git a/docs/UserGuide/Operators-Functions/Conditional.md b/docs/UserGuide/Operators-Functions/Conditional.md
new file mode 100644
index 0000000000..1e426c4ce9
--- /dev/null
+++ b/docs/UserGuide/Operators-Functions/Conditional.md
@@ -0,0 +1,351 @@
+<!--
+
+    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.
+
+-->
+
+## Conditional Expressions
+
+### CASE
+
+#### Introduction
+
+The CASE expression is a kind of conditional expression that can be used to return different values based on specific conditions, similar to the if-else statements in other languages.
+
+The CASE expression consists of the following parts:
+
+- CASE keyword: Indicates the start of the CASE expression.
+- WHEN-THEN clauses: There may be multiple clauses used to define conditions and give results. This clause is divided into two parts, WHEN and THEN. The WHEN part defines the condition, and the THEN part defines the result expression. If the WHEN condition is true, the corresponding THEN result is returned.
+- ELSE clause: If none of the WHEN conditions is true, the result in the ELSE clause will be returned. The ELSE clause can be omitted.
+- END keyword: Indicates the end of the CASE expression.
+
+The CASE expression is a scalar operation that can be used in combination with any other scalar operation or aggregate function.
+
+In the following text, all THEN parts and ELSE clauses will be collectively referred to as result clauses.
+
+#### Syntax
+
+The CASE expression supports two formats.
+
+- Format 1:
+    ```sql
+    CASE 
+        WHEN condition1 THEN expression1
+        [WHEN condition2 THEN expression2] ...
+        [ELSE expression_end]
+    END
+    ```
+  The `condition`s will be evaluated one by one.
+
+  The first `condition` that is true will return the corresponding expression.
+
+- Format 2:
+    ```sql
+    CASE caseValue
+        WHEN whenValue1 THEN expression1
+        [WHEN whenValue2 THEN expression2] ...
+        [ELSE expression_end]
+    END
+    ```
+  The `caseValue` will be evaluated first, and then the `whenValue`s will be evaluated one by one. The first `whenValue` that is equal to the `caseValue` will return the corresponding `expression`.
+
+  Format 2 will be transformed into an equivalent Format 1 by iotdb.
+
+  For example, the above SQL statement will be transformed into:
+
+    ```sql
+    CASE 
+        WHEN caseValue=whenValue1 THEN expression1
+        [WHEN caseValue=whenValue1 THEN expression1] ...
+        [ELSE expression_end]
+    END
+    ```
+
+If none of the conditions are true, or if none of the `whenValue`s match the `caseValue`, the `expression_end` will be returned.
+
+If there is no ELSE clause, `null` will be returned.
+
+#### Notes
+
+- In format 1, all WHEN clauses must return a BOOLEAN type.
+- In format 2, all WHEN clauses must be able to be compared to the CASE clause.
+- All result clauses in a CASE expression must satisfy certain conditions for their return value types:
+  - BOOLEAN types cannot coexist with other types and will cause an error if present.
+  - TEXT types cannot coexist with other types and will cause an error if present.
+  - The other four numeric types can coexist, and the final result will be of DOUBLE type, with possible precision loss during conversion.
+  - If necessary, you can use the CAST function to convert the result to a type that can coexist with others.
+- The CASE expression does not implement lazy evaluation, meaning that all clauses will be evaluated.
+- The CASE expression does not support mixing with UDFs.
+- Aggregate functions cannot be used within a CASE expression, but the result of a CASE expression can be used as input for an aggregate function.
+- When using the CLI, because the CASE expression string can be lengthy, it is recommended to provide an alias for the expression using AS.
+
+#### Using Examples
+
+##### Example 1
+
+The CASE expression can be used to analyze data in a visual way. For example:
+- The preparation of a certain chemical product requires that the temperature and pressure be within specific ranges.
+- During the preparation process, sensors will detect the temperature and pressure, forming two time-series T (temperature) and P (pressure) in IoTDB.
+In this application scenario, the CASE expression can indicate which time parameters are appropriate, which are not, and why they are not.
+
+data:
+```sql
+IoTDB> select * from root.test1
++-----------------------------+------------+------------+
+|                         Time|root.test1.P|root.test1.T|
++-----------------------------+------------+------------+
+|2023-03-29T11:25:54.724+08:00|   1000000.0|      1025.0|
+|2023-03-29T11:26:13.445+08:00|   1000094.0|      1040.0|
+|2023-03-29T11:27:36.988+08:00|   1000095.0|      1041.0|
+|2023-03-29T11:27:56.446+08:00|   1000095.0|      1059.0|
+|2023-03-29T11:28:20.838+08:00|   1200000.0|      1040.0|
++-----------------------------+------------+------------+
+```
+
+SQL statements:
+```sql
+select T, P, case
+when 1000<T and T<1050 and 1000000<P and P<1100000 then "good!"
+when T<=1000 or T>=1050 then "bad temperature"
+when P<=1000000 or P>=1100000 then "bad pressure"
+end as `result`
+from root.test1
+```
+
+
+output:
+```
++-----------------------------+------------+------------+---------------+
+|                         Time|root.test1.T|root.test1.P|         result|
++-----------------------------+------------+------------+---------------+
+|2023-03-29T11:25:54.724+08:00|      1025.0|   1000000.0|   bad pressure|
+|2023-03-29T11:26:13.445+08:00|      1040.0|   1000094.0|          good!|
+|2023-03-29T11:27:36.988+08:00|      1041.0|   1000095.0|          good!|
+|2023-03-29T11:27:56.446+08:00|      1059.0|   1000095.0|bad temperature|
+|2023-03-29T11:28:20.838+08:00|      1040.0|   1200000.0|   bad pressure|
++-----------------------------+------------+------------+---------------+
+```
+
+
+##### Example 2
+
+The CASE expression can achieve flexible result transformation, such as converting strings with a certain pattern to other strings.
+
+data:
+```sql
+IoTDB> select * from root.test2
++-----------------------------+--------------+
+|                         Time|root.test2.str|
++-----------------------------+--------------+
+|2023-03-27T18:23:33.427+08:00|         abccd|
+|2023-03-27T18:23:39.389+08:00|         abcdd|
+|2023-03-27T18:23:43.463+08:00|       abcdefg|
++-----------------------------+--------------+
+```
+
+SQL statements:
+```sql
+select str, case
+when str like "%cc%" then "has cc"
+when str like "%dd%" then "has dd"
+else "no cc and dd" end as `result`
+from root.test2
+```
+
+output:
+```
++-----------------------------+--------------+------------+
+|                         Time|root.test2.str|      result|
++-----------------------------+--------------+------------+
+|2023-03-27T18:23:33.427+08:00|         abccd|      has cc|
+|2023-03-27T18:23:39.389+08:00|         abcdd|      has dd|
+|2023-03-27T18:23:43.463+08:00|       abcdefg|no cc and dd|
++-----------------------------+--------------+------------+
+```
+
+##### Example 3: work with aggregation functions
+
+###### valid: aggregation function ← CASE expression
+
+The CASE expression can be used as a parameter for aggregate functions. For example, used in conjunction with the COUNT function, it can implement statistics based on multiple conditions simultaneously.
+
+data:
+```sql
+IoTDB> select * from root.test3
++-----------------------------+------------+
+|                         Time|root.test3.x|
++-----------------------------+------------+
+|2023-03-27T18:11:11.300+08:00|         0.0|
+|2023-03-27T18:11:14.658+08:00|         1.0|
+|2023-03-27T18:11:15.981+08:00|         2.0|
+|2023-03-27T18:11:17.668+08:00|         3.0|
+|2023-03-27T18:11:19.112+08:00|         4.0|
+|2023-03-27T18:11:20.822+08:00|         5.0|
+|2023-03-27T18:11:22.462+08:00|         6.0|
+|2023-03-27T18:11:24.174+08:00|         7.0|
+|2023-03-27T18:11:25.858+08:00|         8.0|
+|2023-03-27T18:11:27.979+08:00|         9.0|
++-----------------------------+------------+
+```
+
+SQL statements:
+
+```sql
+select
+count(case when x<=1 then 1 end) as `(-∞,1]`,
+count(case when 1<x and x<=3 then 1 end) as `(1,3]`,
+count(case when 3<x and x<=7 then 1 end) as `(3,7]`,
+count(case when 7<x then 1 end) as `(7,+∞)`
+from root.test3
+```
+
+output:
+```
++------+-----+-----+------+
+|(-∞,1]|(1,3]|(3,7]|(7,+∞)|
++------+-----+-----+------+
+|     2|    2|    4|     2|
++------+-----+-----+------+
+```
+
+###### invalid: CASE expression ← aggregation function 
+
+Using aggregation function in CASE expression is not supported
+
+SQL statements:
+```sql
+select case when x<=1 then avg(x) else sum(x) end from root.test3
+```
+
+output:
+```
+Msg: 701: Raw data and aggregation result hybrid calculation is not supported.
+```
+
+##### Example 4: kind 2
+
+Here is a simple example that uses the format 2 syntax. 
+If all conditions are equality tests, it is recommended to use format 2 to simplify SQL statements.
+
+data:
+```sql
+IoTDB> select * from root.test4
++-----------------------------+------------+
+|                         Time|root.test4.x|
++-----------------------------+------------+
+|1970-01-01T08:00:00.001+08:00|         1.0|
+|1970-01-01T08:00:00.002+08:00|         2.0|
+|1970-01-01T08:00:00.003+08:00|         3.0|
+|1970-01-01T08:00:00.004+08:00|         4.0|
++-----------------------------+------------+
+```
+
+SQL statements:
+```sql
+select x, case x when 1 then "one" when 2 then "two" else "other" end from root.test4
+```
+
+output:
+```
++-----------------------------+------------+-----------------------------------------------------------------------------------+
+|                         Time|root.test4.x|CASE WHEN root.test4.x = 1 THEN "one" WHEN root.test4.x = 2 THEN "two" ELSE "other"|
++-----------------------------+------------+-----------------------------------------------------------------------------------+
+|1970-01-01T08:00:00.001+08:00|         1.0|                                                                                one|
+|1970-01-01T08:00:00.002+08:00|         2.0|                                                                                two|
+|1970-01-01T08:00:00.003+08:00|         3.0|                                                                              other|
+|1970-01-01T08:00:00.004+08:00|         4.0|                                                                              other|
++-----------------------------+------------+-----------------------------------------------------------------------------------+
+```
+
+##### Example 5: type of return clauses
+
+The result clause of a CASE expression needs to satisfy certain type restrictions.
+
+In this example, we continue to use the data from Example 4.
+
+###### Invalid: BOOLEAN cannot coexist with other types
+
+SQL statements:
+```sql
+select x, case x when 1 then true when 2 then 2 end from root.test4
+```
+
+output:
+```
+Msg: 701: CASE expression: BOOLEAN and other types cannot exist at same time
+```
+
+###### Valid: Only BOOLEAN type exists
+
+SQL statements:
+```sql
+select x, case x when 1 then true when 2 then false end as `result` from root.test4
+```
+
+output:
+```
++-----------------------------+------------+------+
+|                         Time|root.test4.x|result|
++-----------------------------+------------+------+
+|1970-01-01T08:00:00.001+08:00|         1.0|  true|
+|1970-01-01T08:00:00.002+08:00|         2.0| false|
+|1970-01-01T08:00:00.003+08:00|         3.0|  null|
+|1970-01-01T08:00:00.004+08:00|         4.0|  null|
++-----------------------------+------------+------+
+```
+
+###### Invalid:TEXT cannot coexist with other types
+
+SQL statements:
+```sql
+select x, case x when 1 then 1 when 2 then "str" end from root.test4
+```
+
+output:
+```
+Msg: 701: CASE expression: TEXT and other types cannot exist at same time
+```
+
+###### Valid: Only TEXT type exists
+
+See in Example 1.
+
+###### Valid: Numerical types coexist
+
+SQL statements:
+```sql
+select x, case x
+when 1 then 1
+when 2 then 222222222222222
+when 3 then 3.3
+when 4 then 4.4444444444444
+end as `result`
+from root.test4
+```
+
+output:
+```
++-----------------------------+------------+-------------------+
+|                         Time|root.test4.x|             result|
++-----------------------------+------------+-------------------+
+|1970-01-01T08:00:00.001+08:00|         1.0|                1.0|
+|1970-01-01T08:00:00.002+08:00|         2.0|2.22222222222222E14|
+|1970-01-01T08:00:00.003+08:00|         3.0|  3.299999952316284|
+|1970-01-01T08:00:00.004+08:00|         4.0|   4.44444465637207|
++-----------------------------+------------+-------------------+
+```
\ No newline at end of file
diff --git a/docs/zh/UserGuide/Operators-Functions/Conditional.md b/docs/zh/UserGuide/Operators-Functions/Conditional.md
new file mode 100644
index 0000000000..abeb2621fb
--- /dev/null
+++ b/docs/zh/UserGuide/Operators-Functions/Conditional.md
@@ -0,0 +1,347 @@
+<!--
+
+    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.
+
+-->
+
+## 条件表达式
+
+### CASE
+
+#### 介绍
+
+CASE表达式是一种条件表达式,可用于根据特定条件返回不同的值,功能类似于其它语言中的if-else。
+CASE表达式由以下部分组成:
+- CASE关键字:表示开始CASE表达式。
+- WHEN-THEN子句:可能存在多个,用于定义条件与给出结果。此子句又分为WHEN和THEN两个部分,WHEN部分表示条件,THEN部分表示结果表达式。如果WHEN条件为真,则返回对应的THEN结果。
+- ELSE子句:如果没有任何WHEN-THEN子句的条件为真,则返回ELSE子句中的结果。可以不存在ELSE子句。
+- END关键字:表示结束CASE表达式。
+
+CASE表达式是一种标量运算,可以配合任何其它的标量运算或聚合函数使用。
+  
+下文把所有THEN部分和ELSE子句并称为结果子句。
+
+#### 语法示例
+
+CASE表达式支持两种格式。
+
+语法示例如下:
+- 格式1:
+```sql
+  CASE
+    WHEN condition1 THEN expression1
+    [WHEN condition2 THEN expression2] ...
+    [ELSE expression_end]
+  END
+```
+  从上至下检查WHEN子句中的condition。
+
+  condition为真时返回对应THEN子句中的expression,condition为假时继续检查下一个WHEN子句中的condition。
+- 格式2:
+```sql
+  CASE caseValue
+    WHEN whenValue1 THEN expression1
+    [WHEN whenValue2 THEN expression2] ...
+    [ELSE expression_end]
+  END
+```
+
+  从上至下检查WHEN子句中的whenValue是否与caseValue相等。
+
+  满足caseValue=whenValue时返回对应THEN子句中的expression,不满足时继续检查下一个WHEN子句中的whenValue。
+
+  格式2会被iotdb转换成等效的格式1,例如以上sql语句会转换成:
+```sql
+  CASE
+    WHEN caseValue=whenValue1 THEN expression1
+    [WHEN caseValue=whenValue1 THEN expression1] ...
+    [ELSE expression_end]
+  END
+```
+
+如果格式1中的condition均不为真,或格式2中均不满足caseVaule=whenValue,则返回ELSE子句中的expression_end;不存在ELSE子句则返回null。
+
+#### 注意事项
+
+- 格式1中,所有WHEN子句必须返回BOOLEAN类型。
+- 格式2中,所有WHEN子句必须能够与CASE子句进行判等。
+- 一个CASE表达式中所有结果子句的返回值类型需要满足一定的条件:
+  - BOOLEAN类型不能与其它类型共存,存在其它类型会报错。
+  - TEXT类型不能与其它类型共存,存在其它类型会报错。
+  - 其它四种数值类型可以共存,最终结果会为DOUBLE类型,转换过程可能会存在精度损失。
+- CASE表达式没有实现惰性计算,即所有子句都会被计算。
+- CASE表达式不支持与UDF混用。
+- CASE表达式内部不能存在聚合函数,但CASE表达式的结果可以提供给聚合函数。
+- 使用CLI时,由于CASE表达式字符串较长,推荐用as为表达式提供别名。
+
+#### 使用示例
+
+##### 示例1
+
+CASE表达式可对数据进行直观地分析,例如:
+
+- 某种化学产品的制备需要温度和压力都处于特定范围之内
+- 在制备过程中传感器会侦测温度和压力,在iotdb中形成T(temperature)和P(pressure)两个时间序列
+
+这种应用场景下,CASE表达式可以指出哪些时间的参数是合适的,哪些时间的参数不合适,以及为什么不合适。
+  
+数据:
+```sql
+IoTDB> select * from root.test1
++-----------------------------+------------+------------+
+|                         Time|root.test1.P|root.test1.T|
++-----------------------------+------------+------------+
+|2023-03-29T11:25:54.724+08:00|   1000000.0|      1025.0|
+|2023-03-29T11:26:13.445+08:00|   1000094.0|      1040.0|
+|2023-03-29T11:27:36.988+08:00|   1000095.0|      1041.0|
+|2023-03-29T11:27:56.446+08:00|   1000095.0|      1059.0|
+|2023-03-29T11:28:20.838+08:00|   1200000.0|      1040.0|
++-----------------------------+------------+------------+
+```
+
+SQL语句:
+```sql
+select T, P, case
+when 1000<T and T<1050 and 1000000<P and P<1100000 then "good!"
+when T<=1000 or T>=1050 then "bad temperature"
+when P<=1000000 or P>=1100000 then "bad pressure"
+end as `result`
+from root.test1
+```
+
+
+输出:
+```
++-----------------------------+------------+------------+---------------+
+|                         Time|root.test1.T|root.test1.P|         result|
++-----------------------------+------------+------------+---------------+
+|2023-03-29T11:25:54.724+08:00|      1025.0|   1000000.0|   bad pressure|
+|2023-03-29T11:26:13.445+08:00|      1040.0|   1000094.0|          good!|
+|2023-03-29T11:27:36.988+08:00|      1041.0|   1000095.0|          good!|
+|2023-03-29T11:27:56.446+08:00|      1059.0|   1000095.0|bad temperature|
+|2023-03-29T11:28:20.838+08:00|      1040.0|   1200000.0|   bad pressure|
++-----------------------------+------------+------------+---------------+
+```
+
+
+##### 示例2
+
+CASE表达式可实现结果的自由转换,例如将具有某种模式的字符串转换成另一种字符串。
+
+数据:
+```sql
+IoTDB> select * from root.test2
++-----------------------------+--------------+
+|                         Time|root.test2.str|
++-----------------------------+--------------+
+|2023-03-27T18:23:33.427+08:00|         abccd|
+|2023-03-27T18:23:39.389+08:00|         abcdd|
+|2023-03-27T18:23:43.463+08:00|       abcdefg|
++-----------------------------+--------------+
+```
+
+SQL语句:
+```sql
+select str, case
+when str like "%cc%" then "has cc"
+when str like "%dd%" then "has dd"
+else "no cc and dd" end as `result`
+from root.test2
+```
+
+输出:
+```
++-----------------------------+--------------+------------+
+|                         Time|root.test2.str|      result|
++-----------------------------+--------------+------------+
+|2023-03-27T18:23:33.427+08:00|         abccd|      has cc|
+|2023-03-27T18:23:39.389+08:00|         abcdd|      has dd|
+|2023-03-27T18:23:43.463+08:00|       abcdefg|no cc and dd|
++-----------------------------+--------------+------------+
+```
+
+##### 示例3:搭配聚合函数
+
+###### 合法:聚合函数←CASE表达式
+
+CASE表达式可作为聚合函数的参数。例如,与聚合函数COUNT搭配,可实现同时按多个条件进行数据统计。
+
+数据:
+```sql
+IoTDB> select * from root.test3
++-----------------------------+------------+
+|                         Time|root.test3.x|
++-----------------------------+------------+
+|2023-03-27T18:11:11.300+08:00|         0.0|
+|2023-03-27T18:11:14.658+08:00|         1.0|
+|2023-03-27T18:11:15.981+08:00|         2.0|
+|2023-03-27T18:11:17.668+08:00|         3.0|
+|2023-03-27T18:11:19.112+08:00|         4.0|
+|2023-03-27T18:11:20.822+08:00|         5.0|
+|2023-03-27T18:11:22.462+08:00|         6.0|
+|2023-03-27T18:11:24.174+08:00|         7.0|
+|2023-03-27T18:11:25.858+08:00|         8.0|
+|2023-03-27T18:11:27.979+08:00|         9.0|
++-----------------------------+------------+
+```
+
+SQL语句:
+
+```sql
+select
+count(case when x<=1 then 1 end) as `(-∞,1]`,
+count(case when 1<x and x<=3 then 1 end) as `(1,3]`,
+count(case when 3<x and x<=7 then 1 end) as `(3,7]`,
+count(case when 7<x then 1 end) as `(7,+∞)`
+from root.test3
+```
+
+输出:
+```
++------+-----+-----+------+
+|(-∞,1]|(1,3]|(3,7]|(7,+∞)|
++------+-----+-----+------+
+|     2|    2|    4|     2|
++------+-----+-----+------+
+```
+
+###### 非法:CASE表达式←聚合函数
+
+不支持在CASE表达式内部使用聚合函数。
+
+SQL语句:
+```sql
+select case when x<=1 then avg(x) else sum(x) end from root.test3
+```
+
+输出:
+```
+Msg: 701: Raw data and aggregation result hybrid calculation is not supported.
+```
+
+##### 示例4:格式2
+
+一个使用格式2的简单例子。如果所有条件都为判等,则推荐使用格式2,以简化SQL语句。
+
+数据:
+```sql
+IoTDB> select * from root.test4
++-----------------------------+------------+
+|                         Time|root.test4.x|
++-----------------------------+------------+
+|1970-01-01T08:00:00.001+08:00|         1.0|
+|1970-01-01T08:00:00.002+08:00|         2.0|
+|1970-01-01T08:00:00.003+08:00|         3.0|
+|1970-01-01T08:00:00.004+08:00|         4.0|
++-----------------------------+------------+
+```
+
+SQL语句:
+```sql
+select x, case x when 1 then "one" when 2 then "two" else "other" end from root.test4
+```
+
+输出:
+```
++-----------------------------+------------+-----------------------------------------------------------------------------------+
+|                         Time|root.test4.x|CASE WHEN root.test4.x = 1 THEN "one" WHEN root.test4.x = 2 THEN "two" ELSE "other"|
++-----------------------------+------------+-----------------------------------------------------------------------------------+
+|1970-01-01T08:00:00.001+08:00|         1.0|                                                                                one|
+|1970-01-01T08:00:00.002+08:00|         2.0|                                                                                two|
+|1970-01-01T08:00:00.003+08:00|         3.0|                                                                              other|
+|1970-01-01T08:00:00.004+08:00|         4.0|                                                                              other|
++-----------------------------+------------+-----------------------------------------------------------------------------------+
+```
+
+##### 示例5:结果子句类型
+
+CASE表达式的结果子句的返回值需要满足一定的类型限制。
+
+此示例中,继续使用示例4中的数据。
+
+###### 非法:BOOLEAN与其它类型共存
+
+SQL语句:
+```sql
+select x, case x when 1 then true when 2 then 2 end from root.test4
+```
+
+输出:
+```
+Msg: 701: CASE expression: BOOLEAN and other types cannot exist at same time
+```
+
+###### 合法:只存在BOOLEAN类型
+
+SQL语句:
+```sql
+select x, case x when 1 then true when 2 then false end as `result` from root.test4
+```
+
+输出:
+```
++-----------------------------+------------+------+
+|                         Time|root.test4.x|result|
++-----------------------------+------------+------+
+|1970-01-01T08:00:00.001+08:00|         1.0|  true|
+|1970-01-01T08:00:00.002+08:00|         2.0| false|
+|1970-01-01T08:00:00.003+08:00|         3.0|  null|
+|1970-01-01T08:00:00.004+08:00|         4.0|  null|
++-----------------------------+------------+------+
+```
+
+###### 非法:TEXT与其它类型共存
+
+SQL语句:
+```sql
+select x, case x when 1 then 1 when 2 then "str" end from root.test4
+```
+
+输出:
+```
+Msg: 701: CASE expression: TEXT and other types cannot exist at same time
+```
+
+###### 合法:只存在TEXT类型
+
+见示例1。
+
+###### 合法:数值类型共存
+
+SQL语句:
+```sql
+select x, case x
+when 1 then 1
+when 2 then 222222222222222
+when 3 then 3.3
+when 4 then 4.4444444444444
+end as `result`
+from root.test4
+```
+
+输出:
+```
++-----------------------------+------------+-------------------+
+|                         Time|root.test4.x|             result|
++-----------------------------+------------+-------------------+
+|1970-01-01T08:00:00.001+08:00|         1.0|                1.0|
+|1970-01-01T08:00:00.002+08:00|         2.0|2.22222222222222E14|
+|1970-01-01T08:00:00.003+08:00|         3.0|  3.299999952316284|
+|1970-01-01T08:00:00.004+08:00|         4.0|   4.44444465637207|
++-----------------------------+------------+-------------------+
+```
\ No newline at end of file
diff --git a/docs/zh/UserGuide/Operators-Functions/Overview.md b/docs/zh/UserGuide/Operators-Functions/Overview.md
index 5a425f871b..e58a66a962 100644
--- a/docs/zh/UserGuide/Operators-Functions/Overview.md
+++ b/docs/zh/UserGuide/Operators-Functions/Overview.md
@@ -232,6 +232,14 @@ OR, |, ||
 | EQUAL_SIZE_BUCKET_OUTLIER_SAMPLE   | INT32 / INT64 / FLOAT / DOUBLE | `proportion`取值范围为`(0, 1]`,默认为`0.1`<br>`type`取值为`avg`或`stendis`或`cos`或`prenextdis`,默认为`avg`<br>`number`取值应大于0,默认`3`| INT32 / INT64 / FLOAT / DOUBLE | 返回符合采样比例和桶内采样个数的等分桶离群值采样                |
 | M4     | INT32 / INT64 / FLOAT / DOUBLE | 包含固定点数的窗口和滑动时间窗口使用不同的属性参数。包含固定点数的窗口使用属性`windowSize`和`slidingStep`。滑动时间窗口使用属性`timeInterval`、`slidingStep`、`displayWindowBegin`和`displayWindowEnd`。更多细节见下文。 | INT32 / INT64 / FLOAT / DOUBLE | 返回每个窗口内的第一个点(`first`)、最后一个点(`last`)、最小值点(`bottom`)、最大值点(`top`)。在一个窗口内的聚合点输出之前,M4会将它们按照时间戳递增排序并且去重。 |
 
+## 条件表达式
+
+| 表达式名称                     | 含义        |
+|---------------------------|-----------|
+| `CASE` | 类似if else |
+
+详细说明及示例见文档 [条件表达式](./Conditional.md)
+
 ## 数据质量函数库
 
 对基于时序数据的应用而言,数据质量至关重要。基于用户自定义函数能力,IoTDB 提供了一系列关于数据质量的函数,包括数据画像、数据质量评估与修复等,能够满足工业领域对数据质量的需求。
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java
new file mode 100644
index 0000000000..7e3e375467
--- /dev/null
+++ b/integration-test/src/test/java/org/apache/iotdb/db/it/query/IoTDBCaseWhenThenIT.java
@@ -0,0 +1,876 @@
+/*
+ * 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.query;
+
+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.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.assertTestFail;
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareData;
+import static org.apache.iotdb.db.it.utils.TestUtils.resultSetEqualTest;
+import static org.apache.iotdb.itbase.constant.TestConstant.DEVICE;
+import static org.apache.iotdb.itbase.constant.TestConstant.TIMESTAMP_STR;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({LocalStandaloneIT.class, ClusterIT.class})
+public class IoTDBCaseWhenThenIT {
+  private static final String[] SQLs =
+      new String[] {
+        // normal cases
+        "CREATE DATABASE root.sg",
+        "CREATE TIMESERIES root.sg.d1.s1 WITH DATATYPE=INT32, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.sg.d1.s2 WITH DATATYPE=INT64, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.sg.d2.s3 WITH DATATYPE=FLOAT, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.sg.d2.s4 WITH DATATYPE=DOUBLE, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.sg.d1.s5 WITH DATATYPE=BOOLEAN, ENCODING=PLAIN",
+        "CREATE TIMESERIES root.sg.d1.s6 WITH DATATYPE=TEXT, ENCODING=PLAIN",
+        "INSERT INTO root.sg.d1(timestamp,s1) values(0,         0) ",
+        "INSERT INTO root.sg.d1(timestamp,s1) values(1000000,   11)",
+        "INSERT INTO root.sg.d1(timestamp,s1) values(20000000,  22)",
+        "INSERT INTO root.sg.d1(timestamp,s1) values(210000000, 33)",
+        "INSERT INTO root.sg.d2(timestamp,s3) values(0,         0) ",
+        "INSERT INTO root.sg.d2(timestamp,s3) values(1000000,   11)",
+        "INSERT INTO root.sg.d2(timestamp,s3) values(20000000,  22)",
+        "INSERT INTO root.sg.d2(timestamp,s3) values(210000000, 33)",
+        "INSERT INTO root.sg.d2(timestamp,s4) values(0,         44)",
+        "INSERT INTO root.sg.d2(timestamp,s4) values(1000000,   55)",
+        "INSERT INTO root.sg.d2(timestamp,s4) values(20000000,  66)",
+        "INSERT INTO root.sg.d2(timestamp,s4) values(210000000, 77)",
+      };
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    EnvFactory.getEnv().initClusterEnvironment();
+    prepareData(SQLs);
+    List<String> moreSQLs = new ArrayList<>();
+    for (int i = 0; i < 100; i++) {
+      moreSQLs.add(String.format("INSERT INTO root.sg.d1(timestamp,s2) values(%d, %d)", i, i));
+    }
+    prepareData(moreSQLs);
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvFactory.getEnv().cleanClusterEnvironment();
+  }
+
+  @Test
+  public void testKind1Basic() {
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 > 22 THEN 999 ELSE 9999 END"
+        };
+    String[] retArray =
+        new String[] {"0,99.0,", "1000000,9999.0,", "20000000,9999.0,", "210000000,999.0,"};
+    resultSetEqualTest(
+        "select case when s1=0 then 99 when s1>22 then 999 else 9999 end from root.sg.d1",
+        expectedHeader,
+        retArray);
+
+    // without ELSE clause
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR, "CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 > 22 THEN 999 END"
+        };
+    retArray = new String[] {"0,99.0,", "1000000,null,", "20000000,null,", "210000000,999.0,"};
+    resultSetEqualTest(
+        "select case when s1=0 then 99 when s1>22 then 999 end from root.sg.d1",
+        expectedHeader,
+        retArray);
+  }
+
+  @Test
+  public void testKind2Basic() {
+    String sql = "select case s1 when 0 then 99 when 22 then 999 else 9999 end from root.sg.d1";
+    String[] expectedHeader =
+        new String[] {
+          "Time",
+          "CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 = 22 THEN 999 ELSE 9999 END",
+        };
+    String[] retArray =
+        new String[] {
+          "0,99.0,", "1000000,9999.0,", "20000000,999.0,", "210000000,9999.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // without ELSE clause
+    sql = "select case s1 when 0 then 99 when 22 then 999 end from root.sg.d1";
+    expectedHeader =
+        new String[] {
+          "Time", "CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 = 22 THEN 999 END",
+        };
+    retArray =
+        new String[] {
+          "0,99.0,", "1000000,null,", "20000000,999.0,", "210000000,null,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+  }
+
+  @Test
+  public void testKind1InputTypeRestrict() {
+    // WHEN clause must return BOOLEAN
+    String sql = "select case when s1+1 then 20 else 22 end from root.sg.d1";
+    String msg =
+        "701: The expression in the WHEN clause must return BOOLEAN. expression: root.sg.d1.s1 + 1, actual data type: DOUBLE.";
+    assertTestFail(sql, msg);
+  }
+
+  @Test
+  public void testKind2InputTypeRestrict() {
+    // the expression in CASE clause must be able to be equated with the expression in WHEN clause
+    String sql = "select case s1 when \"1\" then 20 else 22 end from root.sg.d1";
+    String msg =
+        "701: Invalid input expression data type. expression: root.sg.d1.s1, actual data type: INT32, expected data type(s): [TEXT].";
+    assertTestFail(sql, msg);
+  }
+
+  @Test
+  public void testKind1OutputTypeRestrict() {
+    // BOOLEAN and other types cannot exist at the same time
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN root.sg.d1.s1 <= 0 THEN true WHEN root.sg.d1.s1 = 11 THEN false ELSE true END"
+        };
+    String[] retArray =
+        new String[] {"0,true,", "1000000,false,", "20000000,true,", "210000000,true,"};
+    // success
+    resultSetEqualTest(
+        "select case when s1<=0 then true when s1=11 then false else true end from root.sg.d1",
+        expectedHeader,
+        retArray);
+    // fail
+    assertTestFail(
+        "select case when s1<=0 then true else 22 end from root.sg.d1",
+        "701: CASE expression: BOOLEAN and other types cannot exist at the same time");
+
+    // TEXT and other types cannot exist at the same time
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN root.sg.d1.s1 <= 0 THEN \"good\" WHEN root.sg.d1.s1 = 11 THEN \"bad\" ELSE \"okok\" END"
+        };
+    retArray = new String[] {"0,good,", "1000000,bad,", "20000000,okok,", "210000000,okok,"};
+    // success
+    resultSetEqualTest(
+        "select case when s1<=0 then \"good\" when s1=11 then \"bad\" else \"okok\" end from root.sg.d1",
+        expectedHeader,
+        retArray);
+    // fail
+    assertTestFail(
+        "select case when s1<=0 then \"good\" else 22 end from root.sg.d1",
+        "701: CASE expression: TEXT and other types cannot exist at the same time");
+
+    // 4 numerical types(INT LONG FLOAT DOUBLE) can exist at the same time
+    expectedHeader = new String[] {TIMESTAMP_STR, "result"};
+    retArray =
+        new String[] {
+          "0,99.0,", "1000000,99.9000015258789,", "20000000,8.589934588E9,", "210000000,1000.0,"
+        };
+    resultSetEqualTest(
+        "select case when s1=0 then 99 when s1=11 then 99.9 when s1=22 then 8589934588 when s1=33 then 999.9999999999 else 10086 end as `result` from root.sg.d1",
+        expectedHeader,
+        retArray);
+  }
+
+  @Test
+  public void testKind2OutputTypeRestrict() {
+    // BOOLEAN and other types cannot exist at the same time
+    String[] expectedHeader =
+        new String[] {
+          "Time",
+          "CASE WHEN root.sg.d1.s1 = 0 THEN true WHEN root.sg.d1.s1 = 11 THEN false ELSE true END",
+        };
+    String[] retArray =
+        new String[] {
+          "0,true,", "1000000,false,", "20000000,true,", "210000000,true,",
+        };
+    // success
+    resultSetEqualTest(
+        "select case s1 when 0 then true when 11 then false else true end from root.sg.d1",
+        expectedHeader,
+        retArray);
+    // fail
+    assertTestFail(
+        "select case s1 when 0 then true else 22 end from root.sg.d1",
+        "701: CASE expression: BOOLEAN and other types cannot exist at the same time");
+
+    // TEXT and other types cannot exist at the same time
+    expectedHeader =
+        new String[] {
+          "Time",
+          "CASE WHEN root.sg.d1.s1 = 0 THEN \"good\" WHEN root.sg.d1.s1 = 11 THEN \"bad\" ELSE \"okok\" END",
+        };
+    retArray = new String[] {"0,good,", "1000000,bad,", "20000000,okok,", "210000000,okok,"};
+    // success
+    resultSetEqualTest(
+        "select case s1 when 0 then \"good\" when 11 then \"bad\" else \"okok\" end from root.sg.d1",
+        expectedHeader,
+        retArray);
+    // fail
+    assertTestFail(
+        "select case s1 when 0 then \"good\" else 22 end from root.sg.d1",
+        "701: CASE expression: TEXT and other types cannot exist at the same time");
+
+    // 4 numerical types(INT LONG FLOAT DOUBLE) can exist at the same time
+    expectedHeader = new String[] {TIMESTAMP_STR, "result"};
+    retArray =
+        new String[] {
+          "0,99.0,", "1000000,99.9000015258789,", "20000000,8.589934588E9,", "210000000,1000.0,"
+        };
+    resultSetEqualTest(
+        "select case s1 when 0 then 99 when 11 then 99.9 when 22 then 8589934588 when 33 then 999.9999999999 else 10086 end as `result` from root.sg.d1",
+        expectedHeader,
+        retArray);
+  }
+
+  /** 100 branches */
+  @Test
+  public void testKind1LargeNumberBranches() {
+    StringBuilder sqlBuilder = new StringBuilder(), expectedHeaderBuilder = new StringBuilder();
+    List<String> retList = new ArrayList<>();
+    sqlBuilder.append("select case ");
+    expectedHeaderBuilder.append("CASE ");
+    for (int i = 0; i < 100; i++) {
+      sqlBuilder.append(String.format("when s2=%d then s2*%d ", i, i * 100));
+      expectedHeaderBuilder.append(
+          String.format("WHEN root.sg.d1.s2 = %d THEN root.sg.d1.s2 * %d ", i, i * 100));
+      retList.add(String.format("%d,%d.0,", i, i * i * 100));
+    }
+    sqlBuilder.append("end from root.sg.d1");
+    expectedHeaderBuilder.append("END");
+    String[] expectedHeader = new String[] {TIMESTAMP_STR, expectedHeaderBuilder.toString()};
+    resultSetEqualTest(sqlBuilder.toString(), expectedHeader, retList.toArray(new String[] {}));
+  }
+
+  /** 100 branches */
+  @Test
+  public void testKind2LargeNumberBranches() {
+    StringBuilder sqlBuilder = new StringBuilder(), expectedHeaderBuilder = new StringBuilder();
+    List<String> retList = new ArrayList<>();
+    sqlBuilder.append("select case s2 ");
+    expectedHeaderBuilder.append("CASE ");
+    for (int i = 0; i < 100; i++) {
+      sqlBuilder.append(String.format("when %d then s2*%d ", i, i * 100));
+      expectedHeaderBuilder.append(
+          String.format("WHEN root.sg.d1.s2 = %d THEN root.sg.d1.s2 * %d ", i, i * 100));
+      retList.add(String.format("%d,%d.0,", i, i * i * 100));
+    }
+    sqlBuilder.append("end from root.sg.d1");
+    expectedHeaderBuilder.append("END");
+    String[] expectedHeader = new String[] {TIMESTAMP_STR, expectedHeaderBuilder.toString()};
+    resultSetEqualTest(sqlBuilder.toString(), expectedHeader, retList.toArray(new String[] {}));
+  }
+
+  @Test
+  public void testKind1UsedInOtherOperation() {
+    String sql;
+    String[] expectedHeader;
+    String[] retArray;
+
+    // use in scalar operation
+
+    // multiply
+    sql = "select 2 * case when s1=0 then 99 when s1=22.0 then 999 else 9999 end from root.sg.d1";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "2 * CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 = 22.0 THEN 999 ELSE 9999 END"
+        };
+    retArray =
+        new String[] {"0,198.0,", "1000000,19998.0,", "20000000,1998.0,", "210000000,19998.0,"};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // add
+    sql =
+        "select "
+            + "case when s1=0 then 99 when s1=22.0 then 999 else 9999 end "
+            + "+"
+            + "case when s1=11 then 99 else 9999 end "
+            + "from root.sg.d1, root.sg.d2";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 = 22.0 THEN 999 ELSE 9999 END + CASE WHEN root.sg.d1.s1 = 11 THEN 99 ELSE 9999 END"
+        };
+    retArray =
+        new String[] {
+          "0,10098.0,", "1000000,10098.0,", "20000000,10998.0,", "210000000,19998.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // function
+    sql =
+        "select diff(case when s1=0 then 99 when s1>22 then 999 else 9999 end) as `result` from root.sg.d1";
+    expectedHeader = new String[] {TIMESTAMP_STR, "result"};
+    retArray = new String[] {"0,null,", "1000000,9900.0,", "20000000,0.0,", "210000000,-9000.0,"};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // use in aggregation operation
+
+    // avg
+    sql =
+        "select avg(case when s1=0 then 99 when s1>22 then 999 else 9999 end) as `result` from root.sg.d1";
+    expectedHeader = new String[] {"result"};
+    retArray = new String[] {"5274.0,"};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // max_value
+    sql =
+        "select max_value(case when s1=0 then 99 when s1>22 then 999 else 9999 end) as `result` from root.sg.d1";
+    expectedHeader = new String[] {"result"};
+    retArray = new String[] {"9999.0,"};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // avg × max_value
+    resultSetEqualTest(
+        "select avg(case when s1=0 then 99 when s1>22 then 999 else 9999 end) * max_value(case when s1=0 then 99 when s1>22 then 999 else 9999 end) from root.sg.d1",
+        new String[] {
+          "avg(CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 > 22 THEN 999 ELSE 9999 END) * max_value(CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 > 22 THEN 999 ELSE 9999 END)"
+        },
+        new String[] {"5.2734726E7,"});
+
+    // UDF is not allowed
+
+    sql = "select change_points(case when s1=0 then 99 end) from root.sg.d1";
+    String msg = "301: CASE expression cannot be used with non-mappable UDF";
+    assertTestFail(sql, msg);
+
+    sql = "select change_points(s1) + case when s1=0 then 99 end from root.sg.d1";
+    msg = "301: CASE expression cannot be used with non-mappable UDF";
+    assertTestFail(sql, msg);
+  }
+
+  @Test
+  public void testKind2UsedInOtherOperation() {
+    String sql;
+    String[] expectedHeader;
+    String[] retArray;
+
+    // use in scalar operation
+
+    // multiply
+    sql = "select 2 * case s1 when 0 then 99 when 22.0 then 999 else 9999 end from root.sg.d1";
+    expectedHeader =
+        new String[] {
+          "Time",
+          "2 * CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 = 22.0 THEN 999 ELSE 9999 END",
+        };
+    retArray =
+        new String[] {"0,198.0,", "1000000,19998.0,", "20000000,1998.0,", "210000000,19998.0,"};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // add
+    sql =
+        "select "
+            + "case s1 when 0 then 99 when 22.0 then 999 else 9999 end "
+            + "+"
+            + "case s3 when 11 then 99 else 9999 end "
+            + "from root.sg.d1, root.sg.d2";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 = 22.0 THEN 999 ELSE 9999 END + CASE WHEN root.sg.d2.s3 = 11 THEN 99 ELSE 9999 END"
+        };
+    retArray =
+        new String[] {
+          "0,10098.0,", "1000000,10098.0,", "20000000,10998.0,", "210000000,19998.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // function
+    sql =
+        "select diff(case s1 when 0 then 99 when 22 then 999 else 9999 end) as `result` from root.sg.d1";
+    expectedHeader = new String[] {TIMESTAMP_STR, "result"};
+    retArray =
+        new String[] {
+          "0,null,", "1000000,9900.0,", "20000000,-9000.0,", "210000000,9000.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // use in aggregation operation
+
+    // avg
+    sql =
+        "select avg(case s1 when 0 then 99 when 22 then 999 else 9999 end) as `result` from root.sg.d1";
+    expectedHeader = new String[] {"result"};
+    retArray = new String[] {"5274.0,"};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // max_value
+    sql =
+        "select max_value(case s1 when 0 then 99 when 22 then 999 else 9999 end) as `result` from root.sg.d1";
+    expectedHeader = new String[] {"result"};
+    retArray = new String[] {"9999.0,"};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // avg × max_value
+    resultSetEqualTest(
+        "select avg(case s1 when 0 then 99 when 22 then 999 else 9999 end) * max_value(case s1 when 0 then 99 when 22 then 999 else 9999 end) from root.sg.d1",
+        new String[] {
+          "avg(CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 = 22 THEN 999 ELSE 9999 END) * max_value(CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 = 22 THEN 999 ELSE 9999 END)"
+        },
+        new String[] {"5.2734726E7,"});
+
+    // UDF is not allowed
+
+    sql = "select change_points(case s1 when 0 then 99 end) from root.sg.d1";
+    String msg = "301: CASE expression cannot be used with non-mappable UDF";
+    assertTestFail(sql, msg);
+
+    sql = "select change_points(s1) + case when s1=0 then 99 end from root.sg.d1";
+    msg = "301: CASE expression cannot be used with non-mappable UDF";
+    assertTestFail(sql, msg);
+  }
+
+  @Test
+  public void testKind1UseOtherOperation() {
+    // WHEN-clause use scalar function
+    String sql = "select case when sin(s1)>=0 then \">0\" else \"<0\" end from root.sg.d1";
+    String[] expectHeader =
+        new String[] {
+          TIMESTAMP_STR, "CASE WHEN sin(root.sg.d1.s1) >= 0 THEN \">0\" ELSE \"<0\" END",
+        };
+    String[] retArray =
+        new String[] {
+          "0,>0,", "1000000,<0,", "20000000,<0,", "210000000,>0,",
+        };
+    resultSetEqualTest(sql, expectHeader, retArray);
+
+    // THEN-clause and ELSE-clause use scalar function
+    sql =
+        "select case when s1<=11 then CAST(diff(s1) as TEXT) else CAST(s1-1 as TEXT) end from root.sg.d1 align by device";
+    expectHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          DEVICE,
+          "CASE WHEN s1 <= 11 THEN CAST(diff(s1) AS TEXT) ELSE CAST(s1 - 1 AS TEXT) END",
+        };
+    retArray =
+        new String[] {
+          "0,root.sg.d1,null,",
+          "1000000,root.sg.d1,11.0,",
+          "20000000,root.sg.d1,21.0,",
+          "210000000,root.sg.d1,32.0,",
+        };
+    resultSetEqualTest(sql, expectHeader, retArray);
+
+    // UDF is not allowed
+    sql = "select case when s1=0 then change_points(s1) end from root.sg.d1";
+    String msg = "301: CASE expression cannot be used with non-mappable UDF";
+    assertTestFail(sql, msg);
+  }
+
+  @Test
+  public void testKind2UseOtherOperation() {
+    // CASE-clause use scalar function
+    String sql =
+        "select case round(sin(s1)) when 0 then \"=0\" when -1 then \"<0\" else \">0\" end from root.sg.d1";
+    String[] expectHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN ROUND(sin(root.sg.d1.s1)) = 0 THEN \"=0\" WHEN ROUND(sin(root.sg.d1.s1)) = -1 THEN \"<0\" ELSE \">0\" END",
+        };
+    String[] retArray =
+        new String[] {
+          "0,=0,", "1000000,<0,", "20000000,>0,", "210000000,>0,",
+        };
+    resultSetEqualTest(sql, expectHeader, retArray);
+
+    // WHEN-clause use scalar function
+    sql = "select case 0 when sin(s1) then \"=0\" else \"!=0\" end from root.sg.d1";
+    expectHeader =
+        new String[] {
+          TIMESTAMP_STR, "CASE WHEN 0 = sin(root.sg.d1.s1) THEN \"=0\" ELSE \"!=0\" END",
+        };
+    retArray =
+        new String[] {
+          "0,=0,", "1000000,!=0,", "20000000,!=0,", "210000000,!=0,",
+        };
+    resultSetEqualTest(sql, expectHeader, retArray);
+
+    // THEN-clause and ELSE-clause use scalar function
+    sql =
+        "select case s1 when 11 then CAST(diff(s1) as TEXT) else CAST(s1-1 as TEXT) end from root.sg.d1 align by device";
+    expectHeader =
+        new String[] {
+          "Time",
+          "Device",
+          "CASE WHEN s1 = 11 THEN CAST(diff(s1) AS TEXT) ELSE CAST(s1 - 1 AS TEXT) END",
+        };
+    retArray =
+        new String[] {
+          "0,root.sg.d1,-1.0,",
+          "1000000,root.sg.d1,11.0,",
+          "20000000,root.sg.d1,21.0,",
+          "210000000,root.sg.d1,32.0,",
+        };
+    resultSetEqualTest(sql, expectHeader, retArray);
+
+    // UDF is not allowed
+    sql = "select case s1 when 0 then change_points(s1) end from root.sg.d1";
+    String msg = "301: CASE expression cannot be used with non-mappable UDF";
+    assertTestFail(sql, msg);
+  }
+
+  @Test
+  public void testKind1Wildcard() {
+    String sql = "select case when *=* then * else * end from root.sg.d2";
+    String[] expectedHeaders =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s3 THEN root.sg.d2.s3 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s3 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s3 THEN root.sg.d2.s4 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s3 THEN root.sg.d2.s4 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s4 THEN root.sg.d2.s3 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s4 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s4 THEN root.sg.d2.s4 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s4 THEN root.sg.d2.s4 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s3 THEN root.sg.d2.s3 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s3 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s3 THEN root.sg.d2.s4 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s3 THEN root.sg.d2.s4 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s4 THEN root.sg.d2.s3 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s4 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s4 THEN root.sg.d2.s4 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s4 THEN root.sg.d2.s4 ELSE root.sg.d2.s4 END",
+        };
+    String[] retArray = {
+      "0,0.0,0.0,44.0,44.0,0.0,44.0,0.0,44.0,0.0,44.0,0.0,44.0,0.0,0.0,44.0,44.0,",
+      "1000000,11.0,11.0,55.0,55.0,11.0,55.0,11.0,55.0,11.0,55.0,11.0,55.0,11.0,11.0,55.0,55.0,",
+      "20000000,22.0,22.0,66.0,66.0,22.0,66.0,22.0,66.0,22.0,66.0,22.0,66.0,22.0,22.0,66.0,66.0,",
+      "210000000,33.0,33.0,77.0,77.0,33.0,77.0,33.0,77.0,33.0,77.0,33.0,77.0,33.0,33.0,77.0,77.0,",
+    };
+    resultSetEqualTest(sql, expectedHeaders, retArray);
+  }
+
+  @Test
+  public void testKind2Wildcard() {
+    String sql = "select case * when * then * else * end from root.sg.d2";
+    String[] expectedHeaders =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s3 THEN root.sg.d2.s3 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s3 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s3 THEN root.sg.d2.s4 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s3 THEN root.sg.d2.s4 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s4 THEN root.sg.d2.s3 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s4 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s4 THEN root.sg.d2.s4 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s3 = root.sg.d2.s4 THEN root.sg.d2.s4 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s3 THEN root.sg.d2.s3 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s3 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s3 THEN root.sg.d2.s4 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s3 THEN root.sg.d2.s4 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s4 THEN root.sg.d2.s3 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s4 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s4 THEN root.sg.d2.s4 ELSE root.sg.d2.s3 END",
+          "CASE WHEN root.sg.d2.s4 = root.sg.d2.s4 THEN root.sg.d2.s4 ELSE root.sg.d2.s4 END",
+        };
+    String[] retArray = {
+      "0,0.0,0.0,44.0,44.0,0.0,44.0,0.0,44.0,0.0,44.0,0.0,44.0,0.0,0.0,44.0,44.0,",
+      "1000000,11.0,11.0,55.0,55.0,11.0,55.0,11.0,55.0,11.0,55.0,11.0,55.0,11.0,11.0,55.0,55.0,",
+      "20000000,22.0,22.0,66.0,66.0,22.0,66.0,22.0,66.0,22.0,66.0,22.0,66.0,22.0,22.0,66.0,66.0,",
+      "210000000,33.0,33.0,77.0,77.0,33.0,77.0,33.0,77.0,33.0,77.0,33.0,77.0,33.0,33.0,77.0,77.0,",
+    };
+    resultSetEqualTest(sql, expectedHeaders, retArray);
+  }
+
+  @Test
+  public void testKind1AlignedByDevice() {
+    // from different devices, result should be empty
+    String sql =
+        "select case when s1<=11 then s3 else s4 end from root.sg.d1, root.sg.d2 align by device";
+    String[] expectedHeader = new String[] {TIMESTAMP_STR};
+    String[] retArray = {};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // from same device
+    sql = "select case when s3<=11 then s3 else s4 end from root.sg.d1, root.sg.d2 align by device";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR, DEVICE, "CASE WHEN s3 <= 11 THEN s3 ELSE s4 END",
+        };
+    retArray =
+        new String[] {
+          "0,root.sg.d2,0.0,",
+          "1000000,root.sg.d2,11.0,",
+          "20000000,root.sg.d2,66.0,",
+          "210000000,root.sg.d2,77.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // from same device, two result column
+    sql =
+        "select "
+            + "case when s1<=11 then s1 else s1*2 end, "
+            + "case when s3<=11 then s3 else s4 end "
+            + "from root.sg.d1, root.sg.d2 align by device";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          DEVICE,
+          "CASE WHEN s1 <= 11 THEN s1 ELSE s1 * 2 END",
+          "CASE WHEN s3 <= 11 THEN s3 ELSE s4 END",
+        };
+    retArray =
+        new String[] {
+          "0,root.sg.d1,0.0,null,",
+          "1000000,root.sg.d1,11.0,null,",
+          "20000000,root.sg.d1,44.0,null,",
+          "210000000,root.sg.d1,66.0,null,",
+          "0,root.sg.d2,null,0.0,",
+          "1000000,root.sg.d2,null,11.0,",
+          "20000000,root.sg.d2,null,66.0,",
+          "210000000,root.sg.d2,null,77.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+  }
+
+  @Test
+  public void testKind2AlignedByDevice() {
+    // from different devices, result should be empty
+    String sql =
+        "select case s1 when 11 then s3 else s4 end from root.sg.d1, root.sg.d2 align by device";
+    String[] expectedHeader = new String[] {TIMESTAMP_STR};
+    String[] retArray = {};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // from same device
+    sql = "select case s3 when 11 then s3 else s4 end from root.sg.d1, root.sg.d2 align by device";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR, DEVICE, "CASE WHEN s3 = 11 THEN s3 ELSE s4 END",
+        };
+    retArray =
+        new String[] {
+          "0,root.sg.d2,44.0,",
+          "1000000,root.sg.d2,11.0,",
+          "20000000,root.sg.d2,66.0,",
+          "210000000,root.sg.d2,77.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // from same device, two result column
+    sql =
+        "select "
+            + "case s1 when 11 then s1 else s1*2 end, "
+            + "case s3 when 11 then s3 else s4 end "
+            + "from root.sg.d1, root.sg.d2 align by device";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          DEVICE,
+          "CASE WHEN s1 = 11 THEN s1 ELSE s1 * 2 END",
+          "CASE WHEN s3 = 11 THEN s3 ELSE s4 END",
+        };
+    retArray =
+        new String[] {
+          "0,root.sg.d1,0.0,null,",
+          "1000000,root.sg.d1,11.0,null,",
+          "20000000,root.sg.d1,44.0,null,",
+          "210000000,root.sg.d1,66.0,null,",
+          "0,root.sg.d2,null,44.0,",
+          "1000000,root.sg.d2,null,11.0,",
+          "20000000,root.sg.d2,null,66.0,",
+          "210000000,root.sg.d2,null,77.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+  }
+
+  @Test
+  public void testKind1MultipleTimeseries() {
+    // time stamp is aligned
+    String sql = "select s1*s1, case when s1<=11 then s3 else s4 end from root.sg.d1, root.sg.d2";
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "root.sg.d1.s1 * root.sg.d1.s1",
+          "CASE WHEN root.sg.d1.s1 <= 11 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+        };
+    String[] retArray =
+        new String[] {
+          "0,0.0,0.0,", "1000000,121.0,11.0,", "20000000,484.0,66.0,", "210000000,1089.0,77.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // time stamp is not aligned
+    sql =
+        "select "
+            + "case when s2%2==1 then s2 else s2/2 end, "
+            + "case when s1<=11 then s3 else s4 end "
+            + "from root.sg.d1, root.sg.d2 limit 5 offset 98";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN root.sg.d1.s2 % 2 = 1 THEN root.sg.d1.s2 ELSE root.sg.d1.s2 / 2 END",
+          "CASE WHEN root.sg.d1.s1 <= 11 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+        };
+    retArray =
+        new String[] {
+          "98,49.0,null,",
+          "99,99.0,null,",
+          "1000000,null,11.0,",
+          "20000000,null,66.0,",
+          "210000000,null,77.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+  }
+
+  @Test
+  public void testKind2MultipleTimeseries() {
+    // time stamp is aligned
+    String sql = "select s1*s1, case s1 when 11 then s3 else s4 end from root.sg.d1, root.sg.d2";
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "root.sg.d1.s1 * root.sg.d1.s1",
+          "CASE WHEN root.sg.d1.s1 = 11 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+        };
+    String[] retArray =
+        new String[] {
+          "0,0.0,44.0,", "1000000,121.0,11.0,", "20000000,484.0,66.0,", "210000000,1089.0,77.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // time stamp is not aligned
+    sql =
+        "select "
+            + "case s2%2 when 1 then s2 else s2/2 end, "
+            + "case s3 when 11 then s3 else s4 end "
+            + "from root.sg.d1, root.sg.d2 limit 5 offset 98";
+    expectedHeader =
+        new String[] {
+          "Time",
+          "CASE WHEN root.sg.d1.s2 % 2 = 1 THEN root.sg.d1.s2 ELSE root.sg.d1.s2 / 2 END",
+          "CASE WHEN root.sg.d2.s3 = 11 THEN root.sg.d2.s3 ELSE root.sg.d2.s4 END",
+        };
+    retArray =
+        new String[] {
+          "98,49.0,null,",
+          "99,99.0,null,",
+          "1000000,null,11.0,",
+          "20000000,null,66.0,",
+          "210000000,null,77.0,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+  }
+
+  @Test
+  public void testKind1UseInWhereClause() {
+    String sql =
+        "select s4 from root.sg.d2 where case when s3=0 then s4>44 when s3=22 then s4>0 when time>200000000 then true end";
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR, "root.sg.d2.s4",
+        };
+    String[] retArray = new String[] {"20000000,66.0,", "210000000,77.0,"};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    sql =
+        "select case when s3=0 then s4>44 when s3=22 then s4>0 when time>200000000 then true end as result from root.sg.d2";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR, "result",
+        };
+    retArray =
+        new String[] {
+          "0,false,", "1000000,null,", "20000000,true,", "210000000,true,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+  }
+
+  @Test
+  public void testKind2UseInWhereClause() {
+    String sql = "select s4 from root.sg.d2 where case s3 when 0 then s4>44 when 22 then s4>0 end";
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR, "root.sg.d2.s4",
+        };
+    String[] retArray = new String[] {"20000000,66.0,"};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    sql = "select case s3 when 0 then s4>44 when 22 then s4>0 end as result from root.sg.d2";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR, "result",
+        };
+    retArray =
+        new String[] {
+          "0,false,", "1000000,null,", "20000000,true,", "210000000,null,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+
+    // CASE time
+    sql =
+        "select s4 from root.sg.d2 where case time when 0 then false when 20000000 then true when 1000000 then true end";
+    expectedHeader =
+        new String[] {
+          TIMESTAMP_STR, "root.sg.d2.s4",
+        };
+    retArray = new String[] {"1000000,55.0,", "20000000,66.0,"};
+    resultSetEqualTest(sql, expectedHeader, retArray);
+  }
+
+  @Test
+  public void testKind1CaseInCase() {
+    String sql =
+        "select case when s1=0 || s1=22 then cast(case when s1=0 then 99 when s1>22 then 999 end as TEXT) else \"xxx\" end from root.sg.d1";
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN root.sg.d1.s1 = 0 | root.sg.d1.s1 = 22 THEN CAST(CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 > 22 THEN 999 END AS TEXT) ELSE \"xxx\" END"
+        };
+    String[] retArray =
+        new String[] {
+          "0,99.0,", "1000000,xxx,", "20000000,null,", "210000000,xxx,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+  }
+
+  @Test
+  public void testKind2CaseInCase() {
+    String sql =
+        "select case s1 when 0 then cast(case when s1=0 then 99 when s1>22 then 999 end as TEXT) when 22 then cast(case when s1=0 then 99 when s1>22 then 999 end as TEXT) else \"xxx\" end from root.sg.d1";
+    String[] expectedHeader =
+        new String[] {
+          TIMESTAMP_STR,
+          "CASE WHEN root.sg.d1.s1 = 0 THEN CAST(CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 > 22 THEN 999 END AS TEXT) WHEN root.sg.d1.s1 = 22 THEN CAST(CASE WHEN root.sg.d1.s1 = 0 THEN 99 WHEN root.sg.d1.s1 > 22 THEN 999 END AS TEXT) ELSE \"xxx\" END"
+        };
+    String[] retArray =
+        new String[] {
+          "0,99.0,", "1000000,xxx,", "20000000,null,", "210000000,xxx,",
+        };
+    resultSetEqualTest(sql, expectedHeader, retArray);
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/FilterAndProjectOperator.java b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/FilterAndProjectOperator.java
index 0016c7255c..232b6c08cc 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/FilterAndProjectOperator.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/execution/operator/process/FilterAndProjectOperator.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.mpp.execution.operator.process;
 
 import org.apache.iotdb.db.mpp.execution.operator.Operator;
 import org.apache.iotdb.db.mpp.execution.operator.OperatorContext;
+import org.apache.iotdb.db.mpp.transformation.dag.column.CaseWhenThenColumnTransformer;
 import org.apache.iotdb.db.mpp.transformation.dag.column.ColumnTransformer;
 import org.apache.iotdb.db.mpp.transformation.dag.column.binary.BinaryColumnTransformer;
 import org.apache.iotdb.db.mpp.transformation.dag.column.leaf.IdentityColumnTransformer;
@@ -36,6 +37,7 @@ import org.apache.iotdb.tsfile.read.common.block.column.Column;
 import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder;
 import org.apache.iotdb.tsfile.read.common.block.column.TimeColumn;
 import org.apache.iotdb.tsfile.read.common.block.column.TimeColumnBuilder;
+import org.apache.iotdb.tsfile.utils.Pair;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -312,6 +314,26 @@ public class FilterAndProjectOperator implements ProcessOperator {
                   .getInputColumnTransformers()
                   .length,
           childMaxLevel);
+    } else if (columnTransformer instanceof CaseWhenThenColumnTransformer) {
+      int childMaxLevel = 0;
+      int childCount = 0;
+      for (Pair<ColumnTransformer, ColumnTransformer> whenThenColumnTransformer :
+          ((CaseWhenThenColumnTransformer) columnTransformer).getWhenThenColumnTransformers()) {
+        childMaxLevel =
+            Math.max(
+                childMaxLevel, getMaxLevelOfColumnTransformerTree(whenThenColumnTransformer.left));
+        childMaxLevel =
+            Math.max(
+                childMaxLevel, getMaxLevelOfColumnTransformerTree(whenThenColumnTransformer.right));
+        childCount++;
+      }
+      childMaxLevel =
+          Math.max(
+              childMaxLevel,
+              getMaxLevelOfColumnTransformerTree(
+                  ((CaseWhenThenColumnTransformer) columnTransformer).getElseTransformer()));
+      childMaxLevel = Math.max(childMaxLevel, childCount + 2);
+      return childMaxLevel;
     } else {
       throw new UnsupportedOperationException("Unsupported ColumnTransformer");
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java
index 72eaf98227..260c97dab4 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionAnalyzer.java
@@ -37,6 +37,7 @@ import org.apache.iotdb.db.mpp.plan.expression.leaf.NullOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.TernaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
@@ -61,6 +62,7 @@ import org.apache.iotdb.tsfile.read.filter.factory.FilterFactory;
 import org.apache.iotdb.tsfile.utils.Pair;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -93,6 +95,10 @@ public class ExpressionAnalyzer {
       for (Expression childExpression : expression.getExpressions()) {
         checkIsAllMeasurement(childExpression);
       }
+    } else if (expression instanceof CaseWhenThenExpression) {
+      for (Expression childExpression : expression.getExpressions()) {
+        checkIsAllMeasurement(childExpression);
+      }
     } else if (expression instanceof TimeSeriesOperand) {
       PartialPath path = ((TimeSeriesOperand) expression).getPath();
       if (path.getNodes().length > 1
@@ -100,7 +106,9 @@ public class ExpressionAnalyzer {
         throw new SemanticException(
             "the suffix paths can only be measurement or one-level wildcard");
       }
-    } else if (expression instanceof TimestampOperand || expression instanceof ConstantOperand) {
+    } else if (expression instanceof TimestampOperand
+        || expression instanceof ConstantOperand
+        || expression instanceof NullOperand) {
       // do nothing
     } else {
       throw new IllegalArgumentException(
@@ -108,12 +116,6 @@ public class ExpressionAnalyzer {
     }
   }
 
-  /**
-   * Identify the expression is a valid built-in aggregation function.
-   *
-   * @param expression expression to be checked
-   * @return true if this expression is valid
-   */
   public static ResultColumn.ColumnType identifyOutputColumnType(
       Expression expression, boolean isRoot) {
     if (expression instanceof TernaryExpression) {
@@ -214,9 +216,40 @@ public class ExpressionAnalyzer {
         }
         return checkedType;
       }
+    } else if (expression instanceof CaseWhenThenExpression) {
+      // first, get all subexpression's type
+      CaseWhenThenExpression caseExpression = (CaseWhenThenExpression) expression;
+      List<ResultColumn.ColumnType> typeList =
+          caseExpression.getExpressions().stream()
+              .map(e -> identifyOutputColumnType(e, false))
+              .collect(Collectors.toList());
+      // if at least one subexpression is RAW, I'm RAW too
+      boolean rawFlag =
+          typeList.stream().anyMatch(columnType -> columnType == ResultColumn.ColumnType.RAW);
+      // if at least one subexpression is AGGREGATION, I'm AGGREGATION too
+      boolean aggregationFlag =
+          typeList.stream()
+              .anyMatch(columnType -> columnType == ResultColumn.ColumnType.AGGREGATION);
+      // not allow RAW && AGGREGATION
+      if (rawFlag && aggregationFlag) {
+        throw new SemanticException(
+            "Raw data and aggregation result hybrid calculation is not supported.");
+      }
+      // not allow all const
+      boolean allConst =
+          typeList.stream().allMatch(columnType -> columnType == ResultColumn.ColumnType.CONSTANT);
+      if (allConst) {
+        throw new SemanticException("Constant column is not supported.");
+      }
+      for (ResultColumn.ColumnType type : typeList) {
+        if (type != ResultColumn.ColumnType.CONSTANT) {
+          return type;
+        }
+      }
+      throw new IllegalArgumentException("shouldn't attach here");
     } else if (expression instanceof TimeSeriesOperand || expression instanceof TimestampOperand) {
       return ResultColumn.ColumnType.RAW;
-    } else if (expression instanceof ConstantOperand) {
+    } else if (expression instanceof ConstantOperand || expression instanceof NullOperand) {
       return ResultColumn.ColumnType.CONSTANT;
     } else {
       throw new IllegalArgumentException(
@@ -298,7 +331,14 @@ public class ExpressionAnalyzer {
         }
       }
       return actualPaths;
-    } else if (expression instanceof TimestampOperand || expression instanceof ConstantOperand) {
+    } else if (expression instanceof CaseWhenThenExpression) {
+      return expression.getExpressions().stream()
+          .map(expression1 -> concatExpressionWithSuffixPaths(expression1, prefixPaths))
+          .flatMap(Collection::stream)
+          .collect(Collectors.toList());
+    } else if (expression instanceof TimestampOperand
+        || expression instanceof ConstantOperand
+        || expression instanceof NullOperand) {
       return new ArrayList<>();
     } else {
       throw new IllegalArgumentException(
@@ -345,7 +385,15 @@ public class ExpressionAnalyzer {
         PartialPath concatPath = prefixPath.concatPath(rawPath);
         patternTree.appendPathPattern(concatPath);
       }
-    } else if (predicate instanceof TimestampOperand || predicate instanceof ConstantOperand) {
+    } else if (predicate instanceof CaseWhenThenExpression) {
+      predicate
+          .getExpressions()
+          .forEach(
+              expression ->
+                  constructPatternTreeFromExpression(expression, prefixPaths, patternTree));
+    } else if (predicate instanceof TimestampOperand
+        || predicate instanceof ConstantOperand
+        || predicate instanceof NullOperand) {
       // do nothing
     } else {
       throw new IllegalArgumentException(
@@ -544,6 +592,8 @@ public class ExpressionAnalyzer {
         || predicate.getExpressionType().equals(ExpressionType.CONSTANT)
         || predicate.getExpressionType().equals(ExpressionType.NULL)) {
       return new Pair<>(null, true);
+    } else if (predicate.getExpressionType().equals(ExpressionType.CASE_WHEN_THEN)) {
+      return new Pair<>(null, true);
     } else {
       throw new IllegalArgumentException(
           "unsupported expression type: " + predicate.getExpressionType());
@@ -566,7 +616,16 @@ public class ExpressionAnalyzer {
         timeFilterExist = timeFilterExist || checkIfTimeFilterExist(childExpression);
       }
       return timeFilterExist;
-    } else if (predicate instanceof TimeSeriesOperand || predicate instanceof ConstantOperand) {
+    } else if (predicate instanceof CaseWhenThenExpression) {
+      for (Expression childExpression : predicate.getExpressions()) {
+        if (checkIfTimeFilterExist(childExpression)) {
+          return true;
+        }
+      }
+      return false;
+    } else if (predicate instanceof TimeSeriesOperand
+        || predicate instanceof ConstantOperand
+        || predicate instanceof NullOperand) {
       return false;
     } else if (predicate instanceof TimestampOperand) {
       return true;
@@ -622,6 +681,8 @@ public class ExpressionAnalyzer {
       return false;
     } else if (expression instanceof NullOperand) {
       return true;
+    } else if (expression instanceof CaseWhenThenExpression) {
+      return true;
     } else {
       throw new IllegalArgumentException(
           "unsupported expression type: " + expression.getExpressionType());
@@ -712,6 +773,13 @@ public class ExpressionAnalyzer {
         }
       }
       return false;
+    } else if (expression instanceof CaseWhenThenExpression) {
+      for (Expression subexpression : expression.getExpressions()) {
+        if (isDeviceViewNeedSpecialProcess(subexpression)) {
+          return true;
+        }
+      }
+      return false;
     } else if (expression instanceof LeafOperand) {
       return false;
     } else {
@@ -746,6 +814,13 @@ public class ExpressionAnalyzer {
         }
       }
       return true;
+    } else if (expression instanceof CaseWhenThenExpression) {
+      for (Expression subexpression : expression.getExpressions()) {
+        if (!checkIsScalarExpression(subexpression, analysis)) {
+          return false;
+        }
+      }
+      return true;
     } else if (expression instanceof LeafOperand) {
       return true;
     } else {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionTypeAnalyzer.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionTypeAnalyzer.java
index 28cf2d4b30..ee2f2e1b73 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionTypeAnalyzer.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionTypeAnalyzer.java
@@ -25,11 +25,13 @@ import org.apache.iotdb.db.mpp.plan.expression.Expression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.ArithmeticBinaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.CompareBinaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.LogicBinaryExpression;
+import org.apache.iotdb.db.mpp.plan.expression.binary.WhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.NullOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.IsNullExpression;
@@ -43,9 +45,11 @@ import org.apache.iotdb.db.utils.TypeInferenceUtils;
 import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 public class ExpressionTypeAnalyzer {
@@ -296,6 +300,51 @@ public class ExpressionTypeAnalyzer {
       return null;
     }
 
+    @Override
+    public TSDataType visitCaseWhenThenExpression(
+        CaseWhenThenExpression caseWhenThenExpression, Void context) {
+      Set<TSDataType> typeSet = new HashSet<>();
+      for (WhenThenExpression whenThenExpression :
+          caseWhenThenExpression.getWhenThenExpressions()) {
+        typeSet.add(process(whenThenExpression, context));
+      }
+      if (!(caseWhenThenExpression.getElseExpression() instanceof NullOperand)) {
+        typeSet.add(process(caseWhenThenExpression.getElseExpression(), context));
+      }
+      // if TEXT exists, every branch need to be TEXT
+      if (typeSet.contains(TSDataType.TEXT)) {
+        if (typeSet.stream().anyMatch(tsDataType -> tsDataType != TSDataType.TEXT)) {
+          throw new SemanticException(
+              "CASE expression: TEXT and other types cannot exist at the same time");
+        }
+        return setExpressionType(caseWhenThenExpression, TSDataType.TEXT);
+      }
+      // if BOOLEAN exists, every branch need to be BOOLEAN
+      if (typeSet.contains(TSDataType.BOOLEAN)) {
+        if (typeSet.stream().anyMatch(tsDataType -> tsDataType != TSDataType.BOOLEAN)) {
+          throw new SemanticException(
+              "CASE expression: BOOLEAN and other types cannot exist at the same time");
+        }
+        return setExpressionType(caseWhenThenExpression, TSDataType.BOOLEAN);
+      }
+      // other 4 TSDataType can exist at the same time
+      // because they can be transformed by Type, finally treated as DOUBLE
+      return setExpressionType(caseWhenThenExpression, TSDataType.DOUBLE);
+    }
+
+    @Override
+    public TSDataType visitWhenThenExpression(WhenThenExpression whenThenExpression, Void context) {
+      TSDataType whenType = process(whenThenExpression.getWhen(), context);
+      if (!whenType.equals(TSDataType.BOOLEAN)) {
+        throw new SemanticException(
+            String.format(
+                "The expression in the WHEN clause must return BOOLEAN. expression: %s, actual data type: %s.",
+                whenThenExpression.getWhen().getExpressionString(), whenType.name()));
+      }
+      TSDataType thenType = process(whenThenExpression.getThen(), context);
+      return setExpressionType(whenThenExpression, thenType);
+    }
+
     private TSDataType setExpressionType(Expression expression, TSDataType type) {
       expressionTypes.put(NodeRef.of(expression), type);
       return type;
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java
index eadbcc1aac..7615f9b311 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/analyze/ExpressionUtils.java
@@ -35,10 +35,12 @@ import org.apache.iotdb.db.mpp.plan.expression.binary.ModuloExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.MultiplicationExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.NonEqualExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.SubtractionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.binary.WhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.IsNullExpression;
@@ -54,6 +56,7 @@ import org.apache.iotdb.tsfile.utils.Pair;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class ExpressionUtils {
   public static List<Expression> reconstructTimeSeriesOperands(
@@ -97,6 +100,15 @@ public class ExpressionUtils {
     return resultExpressions;
   }
 
+  public static Expression reconstructCaseWHenThenExpression(List<Expression> childExpressions) {
+    return new CaseWhenThenExpression(
+        childExpressions // transform to List<WhenThenExpression>
+            .subList(0, childExpressions.size() - 1).stream()
+            .map(expression -> (WhenThenExpression) expression)
+            .collect(Collectors.toList()),
+        childExpressions.get(childExpressions.size() - 1));
+  }
+
   public static Expression reconstructUnaryExpression(
       UnaryExpression expression, Expression childExpression) {
     switch (expression.getExpressionType()) {
@@ -181,6 +193,9 @@ public class ExpressionUtils {
       case LOGIC_OR:
         return new LogicOrExpression(leftExpression, rightExpression);
 
+      case WHEN_THEN:
+        return new WhenThenExpression(leftExpression, rightExpression);
+
       default:
         throw new IllegalArgumentException("unsupported expression type: " + expressionType);
     }
@@ -219,6 +234,17 @@ public class ExpressionUtils {
     }
   }
 
+  /**
+   * Make cartesian product. Attention, in this implementation, the way to handle the empty set is
+   * to ignore it instead of making the result an empty set.
+   *
+   * @param dimensionValue source data
+   * @param resultList final results
+   * @param layer the depth of recursive, dimensionValue[layer] will be processed this time, should
+   *     always be 0 while call from outside
+   * @param currentList intermediate result, should always be empty while call from outside
+   * @param <T> any type
+   */
   public static <T> void cartesianProduct(
       List<List<T>> dimensionValue, List<List<T>> resultList, int layer, List<T> currentList) {
     if (layer < dimensionValue.size() - 1) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/Expression.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/Expression.java
index 4bdb16881d..ea2605288d 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/Expression.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/Expression.java
@@ -33,11 +33,13 @@ import org.apache.iotdb.db.mpp.plan.expression.binary.ModuloExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.MultiplicationExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.NonEqualExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.SubtractionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.binary.WhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.NullOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.IsNullExpression;
@@ -303,6 +305,14 @@ public abstract class Expression extends StatementNode {
         expression = new NullOperand();
         break;
 
+      case 21:
+        expression = new CaseWhenThenExpression(byteBuffer);
+        break;
+
+      case 22:
+        expression = new WhenThenExpression(byteBuffer);
+        break;
+
       default:
         throw new IllegalArgumentException("Invalid expression type: " + type);
     }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionFactory.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionFactory.java
index 7ac2d0e6bc..dfd2f05444 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionFactory.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionFactory.java
@@ -23,9 +23,12 @@ import org.apache.iotdb.common.rpc.thrift.TAggregationType;
 import org.apache.iotdb.commons.exception.IllegalPathException;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.db.mpp.plan.expression.binary.AdditionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.binary.EqualToExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.GreaterThanExpression;
+import org.apache.iotdb.db.mpp.plan.expression.binary.LessThanExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.LogicAndExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.LogicOrExpression;
+import org.apache.iotdb.db.mpp.plan.expression.binary.WhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
@@ -104,4 +107,16 @@ public class ExpressionFactory {
   public static GreaterThanExpression gt(Expression leftExpression, Expression rightExpression) {
     return new GreaterThanExpression(leftExpression, rightExpression);
   }
+
+  public static LessThanExpression lt(Expression leftExpression, Expression rightExpression) {
+    return new LessThanExpression(leftExpression, rightExpression);
+  }
+
+  public static EqualToExpression eq(Expression leftExpression, Expression rightExpression) {
+    return new EqualToExpression(leftExpression, rightExpression);
+  }
+
+  public static WhenThenExpression whenThen(Expression whenExpression, Expression thenExpression) {
+    return new WhenThenExpression(whenExpression, thenExpression);
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionType.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionType.java
index 5bc43e40a0..c49a674fff 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionType.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/ExpressionType.java
@@ -56,6 +56,10 @@ public enum ExpressionType {
   LOGIC_OR((short) 19, (short) 200),
 
   NULL((short) 20, (short) 1400),
+
+  CASE_WHEN_THEN((short) 21, (short) 1000), // priority not sure
+
+  WHEN_THEN((short) 22, (short) 1000), // priority not sure
   ;
 
   private final short expressionType;
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/binary/BinaryExpression.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/binary/BinaryExpression.java
index f456499b8e..33a35e6b18 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/binary/BinaryExpression.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/binary/BinaryExpression.java
@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 package org.apache.iotdb.db.mpp.plan.expression.binary;
 
 import org.apache.iotdb.db.mpp.common.NodeRef;
@@ -115,7 +114,7 @@ public abstract class BinaryExpression extends Expression {
   }
 
   @Override
-  public final String getExpressionStringInternal() {
+  public String getExpressionStringInternal() {
     StringBuilder builder = new StringBuilder();
     if (leftExpression.getExpressionType().getPriority() < this.getExpressionType().getPriority()) {
       builder.append("(").append(leftExpression.getExpressionString()).append(")");
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/binary/WhenThenExpression.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/binary/WhenThenExpression.java
new file mode 100644
index 0000000000..53b08473d5
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/binary/WhenThenExpression.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.mpp.plan.expression.binary;
+
+import org.apache.iotdb.db.mpp.plan.expression.Expression;
+import org.apache.iotdb.db.mpp.plan.expression.ExpressionType;
+import org.apache.iotdb.db.mpp.plan.expression.visitor.ExpressionVisitor;
+
+import java.nio.ByteBuffer;
+
+public class WhenThenExpression extends BinaryExpression {
+
+  public WhenThenExpression(Expression leftExpression, Expression rightExpression) {
+    super(leftExpression, rightExpression);
+  }
+
+  public WhenThenExpression(ByteBuffer byteBuffer) {
+    super(byteBuffer);
+  }
+
+  public void setWhen(Expression expression) {
+    leftExpression = expression;
+  }
+
+  public void setThen(Expression expression) {
+    rightExpression = expression;
+  }
+
+  public Expression getWhen() {
+    return leftExpression;
+  }
+
+  public Expression getThen() {
+    return rightExpression;
+  }
+
+  @Override
+  public ExpressionType getExpressionType() {
+    return ExpressionType.WHEN_THEN;
+  }
+
+  @Override
+  protected String operator() {
+    return "When Then";
+  }
+
+  @Override
+  public String getExpressionStringInternal() {
+    return "WHEN " + this.getWhen().toString() + " THEN " + this.getThen().toString();
+  }
+
+  @Override
+  public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+    return visitor.visitWhenThenExpression(this, context);
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/other/CaseWhenThenExpression.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/other/CaseWhenThenExpression.java
new file mode 100644
index 0000000000..fa122ef87f
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/other/CaseWhenThenExpression.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.mpp.plan.expression.other;
+
+import org.apache.iotdb.db.mpp.common.NodeRef;
+import org.apache.iotdb.db.mpp.plan.expression.Expression;
+import org.apache.iotdb.db.mpp.plan.expression.ExpressionType;
+import org.apache.iotdb.db.mpp.plan.expression.binary.WhenThenExpression;
+import org.apache.iotdb.db.mpp.plan.expression.leaf.NullOperand;
+import org.apache.iotdb.db.mpp.plan.expression.visitor.ExpressionVisitor;
+import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.InputLocation;
+import org.apache.iotdb.db.mpp.transformation.dag.memory.LayerMemoryAssigner;
+import org.apache.iotdb.db.mpp.transformation.dag.udf.UDTFExecutor;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
+
+import org.apache.commons.lang3.Validate;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class CaseWhenThenExpression extends Expression {
+  protected List<WhenThenExpression> whenThenExpressions = new ArrayList<>();
+  protected Expression elseExpression;
+
+  public CaseWhenThenExpression(
+      List<WhenThenExpression> whenThenExpressions, Expression elseExpression) {
+    this.whenThenExpressions = whenThenExpressions;
+    this.elseExpression = elseExpression;
+    if (this.elseExpression == null) {
+      this.elseExpression = new NullOperand();
+    }
+  }
+
+  public CaseWhenThenExpression(ByteBuffer byteBuffer) {
+    int len = ReadWriteIOUtils.readInt(byteBuffer);
+    Validate.isTrue(
+        len > 0, "the length of CaseWhenThenExpression's whenThenList must greater than 0");
+    for (int i = 0; i < len; i++) {
+      Expression expression = Expression.deserialize(byteBuffer);
+      this.whenThenExpressions.add((WhenThenExpression) expression);
+    }
+    this.elseExpression = Expression.deserialize(byteBuffer);
+  }
+
+  public void setElseExpression(Expression expression) {
+    this.elseExpression = expression;
+  }
+
+  public List<WhenThenExpression> getWhenThenExpressions() {
+    return whenThenExpressions;
+  }
+
+  public Expression getElseExpression() {
+    return elseExpression;
+  }
+
+  @Override
+  public ExpressionType getExpressionType() {
+    return ExpressionType.CASE_WHEN_THEN;
+  }
+
+  @Override
+  public boolean isMappable(Map<NodeRef<Expression>, TSDataType> expressionTypes) {
+    for (Expression expression : this.getExpressions()) {
+      if (!expression.isMappable(expressionTypes)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  protected boolean isConstantOperandInternal() {
+    for (Expression expression : this.getExpressions()) {
+      if (!expression.isConstantOperand()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public void constructUdfExecutors(
+      Map<String, UDTFExecutor> expressionName2Executor, ZoneId zoneId) {
+    for (Expression expression : this.getExpressions()) {
+      expression.constructUdfExecutors(expressionName2Executor, zoneId);
+    }
+  }
+
+  @Override
+  public void bindInputLayerColumnIndexWithExpression(
+      Map<String, List<InputLocation>> inputLocations) {
+    this.getExpressions()
+        .forEach(expression -> expression.bindInputLayerColumnIndexWithExpression(inputLocations));
+    final String digest = toString();
+
+    if (inputLocations.containsKey(digest)) {
+      inputColumnIndex = inputLocations.get(digest).get(0).getValueColumnIndex();
+    }
+  }
+
+  @Override
+  public void updateStatisticsForMemoryAssigner(LayerMemoryAssigner memoryAssigner) {
+    this.getExpressions()
+        .forEach(expression -> expression.updateStatisticsForMemoryAssigner(memoryAssigner));
+    memoryAssigner.increaseExpressionReference(this);
+  }
+
+  @Override
+  protected String getExpressionStringInternal() {
+    StringBuilder builder = new StringBuilder();
+    builder.append("CASE ");
+    for (Expression expression : this.whenThenExpressions) {
+      builder.append(expression.toString()).append(" ");
+    }
+    if (!(this.elseExpression instanceof NullOperand)) {
+      builder.append("ELSE ").append(this.elseExpression.toString()).append(" ");
+    }
+    builder.append("END");
+    return builder.toString();
+  }
+
+  @Override
+  protected void serialize(ByteBuffer byteBuffer) {
+    int len = this.whenThenExpressions.size();
+    ReadWriteIOUtils.write(len, byteBuffer);
+    getExpressions().forEach(child -> Expression.serialize(child, byteBuffer));
+  }
+
+  @Override
+  protected void serialize(DataOutputStream stream) throws IOException {
+    ReadWriteIOUtils.write(this.whenThenExpressions.size(), stream);
+    for (Expression expression : this.getExpressions()) {
+      Expression.serialize(expression, stream);
+    }
+  }
+
+  @Override
+  public List<Expression> getExpressions() {
+    List<Expression> result = new ArrayList<>(whenThenExpressions);
+    result.add(elseExpression);
+    return result;
+  }
+
+  @Override
+  public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
+    return visitor.visitCaseWhenThenExpression(this, context);
+  }
+}
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/CartesianProductVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/CartesianProductVisitor.java
index 7f3c919c56..7d824b0e25 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/CartesianProductVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/CartesianProductVisitor.java
@@ -21,12 +21,18 @@ package org.apache.iotdb.db.mpp.plan.expression.visitor;
 
 import org.apache.iotdb.db.mpp.plan.expression.Expression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.BinaryExpression;
+import org.apache.iotdb.db.mpp.plan.expression.leaf.NullOperand;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.TernaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.UnaryExpression;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.cartesianProduct;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructBinaryExpressions;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructCaseWHenThenExpression;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTernaryExpressions;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructUnaryExpressions;
 
@@ -54,4 +60,25 @@ public abstract class CartesianProductVisitor<C>
     List<List<Expression>> childResultsList = getResultsFromChild(unaryExpression, context);
     return reconstructUnaryExpressions(unaryExpression, childResultsList.get(0));
   }
+
+  @Override
+  public List<Expression> visitCaseWhenThenExpression(
+      CaseWhenThenExpression caseWhenThenExpression, C context) {
+    List<List<Expression>> childResultsList = getResultsFromChild(caseWhenThenExpression, context);
+    List<List<Expression>> cartesianResults = new ArrayList<>();
+    boolean hasEmptyList = childResultsList.stream().anyMatch(List::isEmpty);
+    if (!hasEmptyList) {
+      cartesianProduct(childResultsList, cartesianResults, 0, new ArrayList<>());
+    }
+    List<Expression> result = new ArrayList<>();
+    for (List<Expression> cartesianResult : cartesianResults) {
+      result.add(reconstructCaseWHenThenExpression(cartesianResult));
+    }
+    return result;
+  }
+
+  @Override
+  public List<Expression> visitNullOperand(NullOperand nullOperand, C context) {
+    return Collections.singletonList(nullOperand);
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/CollectVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/CollectVisitor.java
index aea7d1efa5..920d59f9c8 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/CollectVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/CollectVisitor.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.mpp.plan.expression.visitor;
 
 import org.apache.iotdb.db.mpp.plan.expression.Expression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.BinaryExpression;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.TernaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.UnaryExpression;
 
@@ -49,6 +50,12 @@ public abstract class CollectVisitor extends ExpressionAnalyzeVisitor<List<Expre
     return mergeList(getResultsFromChild(binaryExpression, null));
   }
 
+  @Override
+  public List<Expression> visitCaseWhenThenExpression(
+      CaseWhenThenExpression caseWhenThenExpression, Void context) {
+    return mergeList(getResultsFromChild(caseWhenThenExpression, null));
+  }
+
   @Override
   public List<Expression> visitUnaryExpression(UnaryExpression unaryExpression, Void context) {
     return mergeList(getResultsFromChild(unaryExpression, null));
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 9352e2d358..ec2f30b2d7 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
@@ -22,12 +22,14 @@ package org.apache.iotdb.db.mpp.plan.expression.visitor;
 import org.apache.iotdb.db.mpp.common.NodeRef;
 import org.apache.iotdb.db.mpp.plan.expression.Expression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.BinaryExpression;
+import org.apache.iotdb.db.mpp.plan.expression.binary.WhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.NullOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
 import org.apache.iotdb.db.mpp.plan.expression.multi.builtin.BuiltInScalarFunctionHelperFactory;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.TernaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
@@ -36,6 +38,7 @@ import org.apache.iotdb.db.mpp.plan.expression.unary.LikeExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.RegularExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.UnaryExpression;
 import org.apache.iotdb.db.mpp.plan.planner.plan.parameter.InputLocation;
+import org.apache.iotdb.db.mpp.transformation.dag.column.CaseWhenThenColumnTransformer;
 import org.apache.iotdb.db.mpp.transformation.dag.column.ColumnTransformer;
 import org.apache.iotdb.db.mpp.transformation.dag.column.binary.ArithmeticAdditionColumnTransformer;
 import org.apache.iotdb.db.mpp.transformation.dag.column.binary.ArithmeticDivisionColumnTransformer;
@@ -69,6 +72,7 @@ import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
 import org.apache.iotdb.tsfile.read.common.type.Type;
 import org.apache.iotdb.tsfile.read.common.type.TypeFactory;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -337,6 +341,46 @@ public class ColumnTransformerVisitor
     return res;
   }
 
+  @Override
+  public ColumnTransformer visitCaseWhenThenExpression(
+      CaseWhenThenExpression caseWhenThenExpression, ColumnTransformerVisitorContext context) {
+    if (!context.cache.containsKey(caseWhenThenExpression)) {
+      if (context.hasSeen.containsKey(caseWhenThenExpression)) {
+        IdentityColumnTransformer identity =
+            new IdentityColumnTransformer(
+                TypeFactory.getType(context.getType(caseWhenThenExpression)),
+                context.originSize + context.commonTransformerList.size());
+        ColumnTransformer columnTransformer = context.hasSeen.get(caseWhenThenExpression);
+        columnTransformer.addReferenceCount();
+        context.commonTransformerList.add(columnTransformer);
+        context.leafList.add(identity);
+        context.inputDataTypes.add(context.getType(caseWhenThenExpression));
+        context.cache.put(caseWhenThenExpression, identity);
+      } else {
+        List<ColumnTransformer> whenList = new ArrayList<>();
+        List<ColumnTransformer> thenList = new ArrayList<>();
+        for (WhenThenExpression whenThenExpression :
+            caseWhenThenExpression.getWhenThenExpressions()) {
+          whenList.add(this.process(whenThenExpression.getWhen(), context));
+          thenList.add(this.process(whenThenExpression.getThen(), context));
+        }
+        ColumnTransformer elseColumnTransformer =
+            this.process(caseWhenThenExpression.getElseExpression(), context);
+        context.cache.put(
+            caseWhenThenExpression,
+            new CaseWhenThenColumnTransformer(
+                TypeFactory.getType(context.getType(caseWhenThenExpression)),
+                whenList,
+                thenList,
+                elseColumnTransformer));
+      }
+    }
+
+    ColumnTransformer res = context.cache.get(caseWhenThenExpression);
+    res.addReferenceCount();
+    return res;
+  }
+
   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/expression/visitor/ExpressionAnalyzeVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ExpressionAnalyzeVisitor.java
index 2f727f1c0c..fdb5f34df3 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ExpressionAnalyzeVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ExpressionAnalyzeVisitor.java
@@ -33,7 +33,7 @@ public abstract class ExpressionAnalyzeVisitor<R, C> extends ExpressionVisitor<R
 
   List<R> getResultsFromChild(Expression expression, C context) {
     return expression.getExpressions().stream()
-        .map(child -> process(child, context))
+        .map(child -> this.process(child, context))
         .collect(Collectors.toList());
   }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ExpressionVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ExpressionVisitor.java
index d44306fc67..cbfed82bb8 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ExpressionVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ExpressionVisitor.java
@@ -24,12 +24,14 @@ import org.apache.iotdb.db.mpp.plan.expression.binary.ArithmeticBinaryExpression
 import org.apache.iotdb.db.mpp.plan.expression.binary.BinaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.CompareBinaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.LogicBinaryExpression;
+import org.apache.iotdb.db.mpp.plan.expression.binary.WhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.LeafOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.NullOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.TernaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
@@ -132,4 +134,12 @@ public abstract class ExpressionVisitor<R, C> {
   public R visitNullOperand(NullOperand nullOperand, C context) {
     return visitLeafOperand(nullOperand, context);
   }
+
+  public R visitCaseWhenThenExpression(CaseWhenThenExpression caseWhenThenExpression, C context) {
+    return visitExpression(caseWhenThenExpression, context);
+  }
+
+  public R visitWhenThenExpression(WhenThenExpression whenThenExpression, C context) {
+    return visitBinaryExpression(whenThenExpression, context);
+  }
 }
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/IntermediateLayerVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/IntermediateLayerVisitor.java
index 9f481e4a1d..3b402e3439 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/IntermediateLayerVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/IntermediateLayerVisitor.java
@@ -28,6 +28,7 @@ import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
 import org.apache.iotdb.db.mpp.plan.expression.multi.builtin.BuiltInScalarFunctionHelperFactory;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.TernaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
@@ -291,6 +292,12 @@ public class IntermediateLayerVisitor
     return context.expressionIntermediateLayerMap.get(constantOperand);
   }
 
+  @Override
+  public IntermediateLayer visitCaseWhenThenExpression(
+      CaseWhenThenExpression caseWhenThenExpression, IntermediateLayerVisitorContext context) {
+    throw new UnsupportedOperationException("CASE expression cannot be used with non-mappable UDF");
+  }
+
   private Transformer getConcreteUnaryTransformer(
       Expression expression, LayerPointReader pointReader) {
     switch (expression.getExpressionType()) {
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ReconstructVisitor.java b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ReconstructVisitor.java
index 19b122d097..83f2ad01d8 100644
--- a/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ReconstructVisitor.java
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/plan/expression/visitor/ReconstructVisitor.java
@@ -22,12 +22,14 @@ package org.apache.iotdb.db.mpp.plan.expression.visitor;
 import org.apache.iotdb.db.mpp.plan.expression.Expression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.BinaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.LeafOperand;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.TernaryExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.UnaryExpression;
 
 import java.util.List;
 
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructBinaryExpression;
+import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructCaseWHenThenExpression;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructTernaryExpression;
 import static org.apache.iotdb.db.mpp.plan.analyze.ExpressionUtils.reconstructUnaryExpression;
 
@@ -56,6 +58,13 @@ public abstract class ReconstructVisitor<C> extends ExpressionAnalyzeVisitor<Exp
     return reconstructUnaryExpression(unaryExpression, childResults.get(0));
   }
 
+  @Override
+  public Expression visitCaseWhenThenExpression(
+      CaseWhenThenExpression caseWhenThenExpression, C context) {
+    List<Expression> childResults = getResultsFromChild(caseWhenThenExpression, context);
+    return reconstructCaseWHenThenExpression(childResults);
+  }
+
   @Override
   public Expression visitLeafOperand(LeafOperand leafOperand, C context) {
     return leafOperand;
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 85eb0db088..a08d9930e6 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
@@ -52,11 +52,14 @@ import org.apache.iotdb.db.mpp.plan.expression.binary.ModuloExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.MultiplicationExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.NonEqualExpression;
 import org.apache.iotdb.db.mpp.plan.expression.binary.SubtractionExpression;
+import org.apache.iotdb.db.mpp.plan.expression.binary.WhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.ConstantOperand;
+import org.apache.iotdb.db.mpp.plan.expression.leaf.NullOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimeSeriesOperand;
 import org.apache.iotdb.db.mpp.plan.expression.leaf.TimestampOperand;
 import org.apache.iotdb.db.mpp.plan.expression.multi.FunctionExpression;
 import org.apache.iotdb.db.mpp.plan.expression.multi.builtin.BuiltInScalarFunctionHelperFactory;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.ternary.BetweenExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.InExpression;
 import org.apache.iotdb.db.mpp.plan.expression.unary.IsNullExpression;
@@ -2396,6 +2399,10 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
       return parseConstantOperand(context.constant(0));
     }
 
+    if (context.caseWhenThenExpression() != null) {
+      return parseCaseWhenThenExpression(context.caseWhenThenExpression(), canUseFullPath);
+    }
+
     throw new UnsupportedOperationException();
   }
 
@@ -2463,6 +2470,42 @@ public class ASTVisitor extends IoTDBSqlParserBaseVisitor<Statement> {
     return functionExpression;
   }
 
+  private CaseWhenThenExpression parseCaseWhenThenExpression(
+      IoTDBSqlParser.CaseWhenThenExpressionContext context, boolean canUseFullPath) {
+    // handle CASE
+    Expression caseExpression = null;
+    boolean simpleCase = false;
+    if (context.caseExpression != null) {
+      caseExpression = parseExpression(context.caseExpression, canUseFullPath);
+      simpleCase = true;
+    }
+    // handle WHEN-THEN
+    List<WhenThenExpression> whenThenList = new ArrayList<>();
+    if (simpleCase) {
+      for (IoTDBSqlParser.WhenThenExpressionContext whenThenExpressionContext :
+          context.whenThenExpression()) {
+        Expression when = parseExpression(whenThenExpressionContext.whenExpression, canUseFullPath);
+        Expression then = parseExpression(whenThenExpressionContext.thenExpression, canUseFullPath);
+        Expression comparison = new EqualToExpression(caseExpression, when);
+        whenThenList.add(new WhenThenExpression(comparison, then));
+      }
+    } else {
+      for (IoTDBSqlParser.WhenThenExpressionContext whenThenExpressionContext :
+          context.whenThenExpression()) {
+        whenThenList.add(
+            new WhenThenExpression(
+                parseExpression(whenThenExpressionContext.whenExpression, canUseFullPath),
+                parseExpression(whenThenExpressionContext.thenExpression, canUseFullPath)));
+      }
+    }
+    // handle ELSE
+    Expression elseExpression = new NullOperand();
+    if (context.elseExpression != null) {
+      elseExpression = parseExpression(context.elseExpression, canUseFullPath);
+    }
+    return new CaseWhenThenExpression(whenThenList, elseExpression);
+  }
+
   private Expression parseFunctionExpression(
       IoTDBSqlParser.ExpressionContext functionClause, boolean canUseFullPath) {
     FunctionExpression functionExpression =
diff --git a/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/column/CaseWhenThenColumnTransformer.java b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/column/CaseWhenThenColumnTransformer.java
new file mode 100644
index 0000000000..6be728206d
--- /dev/null
+++ b/server/src/main/java/org/apache/iotdb/db/mpp/transformation/dag/column/CaseWhenThenColumnTransformer.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.iotdb.db.mpp.transformation.dag.column;
+
+import org.apache.iotdb.tsfile.read.common.block.column.Column;
+import org.apache.iotdb.tsfile.read.common.block.column.ColumnBuilder;
+import org.apache.iotdb.tsfile.read.common.type.BinaryType;
+import org.apache.iotdb.tsfile.read.common.type.BooleanType;
+import org.apache.iotdb.tsfile.read.common.type.DoubleType;
+import org.apache.iotdb.tsfile.read.common.type.FloatType;
+import org.apache.iotdb.tsfile.read.common.type.IntType;
+import org.apache.iotdb.tsfile.read.common.type.LongType;
+import org.apache.iotdb.tsfile.read.common.type.Type;
+import org.apache.iotdb.tsfile.utils.Pair;
+
+import org.apache.commons.lang3.Validate;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CaseWhenThenColumnTransformer extends ColumnTransformer {
+
+  //  List<WhenThenColumnTransformer> whenThenTransformers;
+  List<Pair<ColumnTransformer, ColumnTransformer>> whenThenTransformers;
+  ColumnTransformer elseTransformer;
+
+  public CaseWhenThenColumnTransformer(
+      Type returnType,
+      List<ColumnTransformer> whenTransformers,
+      List<ColumnTransformer> thenTransformers,
+      ColumnTransformer elseTransformer) {
+    super(returnType);
+    Validate.isTrue(
+        whenTransformers.size() == thenTransformers.size(),
+        "the size between whenTransformers and thenTransformers needs to be same");
+    this.whenThenTransformers = new ArrayList<>();
+    for (int i = 0; i < whenTransformers.size(); i++) {
+      this.whenThenTransformers.add(new Pair<>(whenTransformers.get(i), thenTransformers.get(i)));
+    }
+    this.elseTransformer = elseTransformer;
+  }
+
+  public List<Pair<ColumnTransformer, ColumnTransformer>> getWhenThenColumnTransformers() {
+    return whenThenTransformers;
+  }
+
+  public ColumnTransformer getElseTransformer() {
+    return elseTransformer;
+  }
+
+  private void writeToColumnBuilder(
+      ColumnTransformer childTransformer, Column column, int index, ColumnBuilder builder) {
+    if (returnType instanceof BooleanType) {
+      builder.writeBoolean(childTransformer.getType().getBoolean(column, index));
+    } else if (returnType instanceof IntType) {
+      builder.writeInt(childTransformer.getType().getInt(column, index));
+    } else if (returnType instanceof LongType) {
+      builder.writeLong(childTransformer.getType().getLong(column, index));
+    } else if (returnType instanceof FloatType) {
+      builder.writeFloat(childTransformer.getType().getFloat(column, index));
+    } else if (returnType instanceof DoubleType) {
+      builder.writeDouble(childTransformer.getType().getDouble(column, index));
+    } else if (returnType instanceof BinaryType) {
+      builder.writeBinary(childTransformer.getType().getBinary(column, index));
+    } else {
+      throw new UnsupportedOperationException("Unsupported Type");
+    }
+  }
+
+  @Override
+  protected void evaluate() {
+    List<Column> whenColumnList = new ArrayList<>();
+    List<Column> thenColumnList = new ArrayList<>();
+    for (Pair<ColumnTransformer, ColumnTransformer> whenThenTransformer : whenThenTransformers) {
+      whenThenTransformer.left.tryEvaluate();
+      whenThenTransformer.right.tryEvaluate();
+    }
+    elseTransformer.tryEvaluate();
+    int positionCount = whenThenTransformers.get(0).left.getColumnCachePositionCount();
+    for (Pair<ColumnTransformer, ColumnTransformer> whenThenTransformer : whenThenTransformers) {
+      whenColumnList.add(whenThenTransformer.left.getColumn());
+      thenColumnList.add(whenThenTransformer.right.getColumn());
+    }
+    ColumnBuilder builder = returnType.createColumnBuilder(positionCount);
+    Column elseColumn = elseTransformer.getColumn();
+    for (int i = 0; i < positionCount; i++) {
+      boolean hasValue = false;
+      for (int j = 0; j < whenThenTransformers.size(); j++) {
+        Column whenColumn = whenColumnList.get(j);
+        Column thenColumn = thenColumnList.get(j);
+        if (!whenColumn.isNull(i) && whenColumn.getBoolean(i)) {
+          if (thenColumn.isNull(i)) {
+            builder.appendNull();
+          } else {
+            writeToColumnBuilder(whenThenTransformers.get(j).right, thenColumn, i, builder);
+          }
+          hasValue = true;
+          break;
+        }
+      }
+      if (!hasValue) {
+        if (!elseColumn.isNull(i)) {
+          writeToColumnBuilder(elseTransformer, elseColumn, i, builder);
+        } else {
+          builder.appendNull();
+        }
+      }
+    }
+
+    initializeColumnCache(builder.build());
+  }
+
+  @Override
+  protected void checkType() {}
+}
diff --git a/server/src/test/java/org/apache/iotdb/db/mpp/expression/other/CaseWhenThenExpressionTest.java b/server/src/test/java/org/apache/iotdb/db/mpp/expression/other/CaseWhenThenExpressionTest.java
new file mode 100644
index 0000000000..5d7f30916d
--- /dev/null
+++ b/server/src/test/java/org/apache/iotdb/db/mpp/expression/other/CaseWhenThenExpressionTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.expression.other;
+
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.db.mpp.plan.expression.Expression;
+import org.apache.iotdb.db.mpp.plan.expression.binary.WhenThenExpression;
+import org.apache.iotdb.db.mpp.plan.expression.other.CaseWhenThenExpression;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.iotdb.db.mpp.plan.expression.ExpressionFactory.eq;
+import static org.apache.iotdb.db.mpp.plan.expression.ExpressionFactory.gt;
+import static org.apache.iotdb.db.mpp.plan.expression.ExpressionFactory.intValue;
+import static org.apache.iotdb.db.mpp.plan.expression.ExpressionFactory.lt;
+import static org.apache.iotdb.db.mpp.plan.expression.ExpressionFactory.timeSeries;
+import static org.apache.iotdb.db.mpp.plan.expression.ExpressionFactory.whenThen;
+import static org.junit.Assert.assertEquals;
+
+public class CaseWhenThenExpressionTest {
+
+  private void assertSerializeDeserializeEqual(CaseWhenThenExpression caseWhenThenExpression) {
+    ByteBuffer byteBuffer = ByteBuffer.allocate(1000);
+    byteBuffer.mark();
+    Expression.serialize(caseWhenThenExpression, byteBuffer);
+    byteBuffer.reset();
+    assertEquals(caseWhenThenExpression, Expression.deserialize(byteBuffer));
+  }
+
+  @Test
+  public void serializeDeserializeTest() throws IllegalPathException {
+    // normal: case when x=1 then 10 else 20 end
+    List<WhenThenExpression> whenThenExpressionList = new ArrayList<>();
+    whenThenExpressionList.add(whenThen(eq(timeSeries("root.f.x"), intValue("1")), intValue("10")));
+    CaseWhenThenExpression case1 =
+        new CaseWhenThenExpression(whenThenExpressionList, intValue("20"));
+    assertSerializeDeserializeEqual(case1);
+
+    // 4 WHEN-THENs
+    whenThenExpressionList.add(whenThen(gt(timeSeries("root.f.x"), intValue("1")), intValue("10")));
+    whenThenExpressionList.add(whenThen(lt(timeSeries("root.f.y"), intValue("2")), intValue("20")));
+    whenThenExpressionList.add(
+        whenThen(eq(timeSeries("root.f.g"), intValue("333")), intValue("20")));
+    CaseWhenThenExpression case2 =
+        new CaseWhenThenExpression(whenThenExpressionList, intValue("99999"));
+    assertSerializeDeserializeEqual(case2);
+
+    // without ELSE:
+    CaseWhenThenExpression case3 = new CaseWhenThenExpression(whenThenExpressionList, null);
+    assertSerializeDeserializeEqual(case3);
+  }
+}