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 85b8f168d7c implement the except (distinct | all ) for table model 
#16760
85b8f168d7c is described below

commit 85b8f168d7c18e71eed86f907b9015456e7feb7b
Author: alpass163 <[email protected]>
AuthorDate: Fri Nov 28 16:43:56 2025 +0800

    implement the except (distinct | all ) for table model #16760
---
 .../it/query/recent/IoTDBExceptTableIT.java        | 159 +++++++++++++++++++++
 .../plan/planner/plan/node/PlanGraphPrinter.java   |  10 ++
 .../plan/planner/plan/node/PlanNodeType.java       |   4 +
 .../plan/planner/plan/node/PlanVisitor.java        |   5 +
 .../plan/relational/planner/RelationPlanner.java   |  24 +++-
 ...istinctAsUnion.java => ImplementExceptAll.java} |  78 ++++++----
 ...on.java => ImplementExceptDistinctAsUnion.java} |  52 ++++---
 .../rule/ImplementIntersectDistinctAsUnion.java    |   3 +-
 .../iterative/rule/PruneDistinctAggregation.java   |  27 +---
 .../iterative/rule/SetOperationNodeTranslator.java |   4 +-
 .../plan/relational/planner/node/ExceptNode.java   |  99 +++++++++++++
 .../plan/relational/planner/node/Patterns.java     |  20 +--
 .../optimizations/LogicalOptimizeFactory.java      |   9 +-
 .../optimizations/UnaliasSymbolReferences.java     |  31 ++++
 .../plan/relational/analyzer/ExceptTest.java       | 132 +++++++++++++++++
 .../plan/relational/analyzer/TestMetadata.java     |  11 +-
 16 files changed, 574 insertions(+), 94 deletions(-)

diff --git 
a/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBExceptTableIT.java
 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBExceptTableIT.java
new file mode 100644
index 00000000000..6c9c70d40e3
--- /dev/null
+++ 
b/integration-test/src/test/java/org/apache/iotdb/relational/it/query/recent/IoTDBExceptTableIT.java
@@ -0,0 +1,159 @@
+/*
+ * 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.relational.it.query.recent;
+
+import org.apache.iotdb.it.env.EnvFactory;
+import org.apache.iotdb.it.framework.IoTDBTestRunner;
+import org.apache.iotdb.itbase.category.TableClusterIT;
+import org.apache.iotdb.itbase.category.TableLocalStandaloneIT;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+
+import static org.apache.iotdb.db.it.utils.TestUtils.prepareTableData;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableAssertTestFail;
+import static org.apache.iotdb.db.it.utils.TestUtils.tableResultSetEqualTest;
+
+@RunWith(IoTDBTestRunner.class)
+@Category({TableLocalStandaloneIT.class, TableClusterIT.class})
+public class IoTDBExceptTableIT {
+  protected static final String DATABASE_NAME = "test";
+  protected static final String[] createSqls =
+      new String[] {
+        "CREATE DATABASE " + DATABASE_NAME,
+        "USE " + DATABASE_NAME,
+        // table1:  ('d1', 1, 1) * 3, ('d1', 2, 2) * 1
+        "create table table1(device STRING TAG, s1 INT32 FIELD, s2 INT32 
FIELD)",
+        "insert into table1 values (1, 'd1', 1, 1)",
+        "insert into table1 values (2, 'd1', 1, 1)",
+        "insert into table1 values (3, 'd1', 1, 1)",
+        "insert into table1 values (4, 'd1', 2, 2)",
+        // table2: ('d1', 1, 1.0) * 1, ('d1', 3, 3.0) * 1
+        "create table table2(device STRING TAG, s1 INT64 FIELD, s2 DOUBLE 
FIELD)",
+        "insert into table2 values (1, 'd1', 1, 1.0)",
+        "insert into table2 values (2, 'd1', 3, 3.0)",
+        // table3: use for testing alias, mirrors table2
+        "create table table3(device STRING TAG, s1_testName INT64 FIELD, 
s2_testName DOUBLE FIELD)",
+        "insert into table3 values (1, 'd1', 1, 1.0)",
+        "insert into table3 values (2, 'd1', 3, 3.0)",
+        // table4: test type compatible
+        "create table table4(device STRING TAG, s1 TEXT FIELD, s2 DOUBLE 
FIELD)"
+      };
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    EnvFactory.getEnv().initClusterEnvironment();
+    prepareTableData(createSqls);
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    EnvFactory.getEnv().cleanClusterEnvironment();
+  }
+
+  @Test
+  public void normalTest() {
+    String[] expectedHeader = new String[] {"device", "s1", "s2"};
+
+    // --- EXCEPT (DISTINCT) ---
+    // table1 (distinct): {('d1', 1, 1.0), ('d1', 2, 2.0)}
+    // table2 (distinct): {('d1', 1, 1.0), ('d1', 3, 3.0)}
+    // expected one tuple : ('d1', 2, 2.0)
+    String[] retArray =
+        new String[] {
+          "d1,2,2.0,",
+        };
+    tableResultSetEqualTest(
+        "select device, s1, s2 from table1 except select device, s1, s2 from 
table2",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    tableResultSetEqualTest(
+        "select device, s1, s2 from table1 except distinct select device, s1, 
s2 from table2",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    // --- EXCEPT ALL ---
+    // Row ('d1', 1, 1.0): table1 has 3, table2 has 1. max(0, 3 - 1) = 2 
tuples.
+    // Row ('d1', 2, 2.0): table1 has 1, table2 has 0. max(0, 1 - 0) = 1 tuple.
+    // Row ('d1', 3, 3.0): table1 has 0, table2 has 1. max(0, 0 - 1) = 0 
tuples.
+    // expected: 2 * ('d1', 1, 1.0) and 1 * ('d1', 2, 2.0)
+    retArray = new String[] {"d1,1,1.0,", "d1,1,1.0,", "d1,2,2.0,"};
+    tableResultSetEqualTest(
+        "select device, s1, s2 from table1 except all select device, s1, s2 
from table2",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+    // test table3, the column name is different
+    tableResultSetEqualTest(
+        "select device, s1, s2 from table1 except all select device, 
s1_testName, s2_testName from table3",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+
+  @Test
+  public void mappingTest() {
+    // table1 (aliased): (s1 as col_a) -> (1), (1), (1), (2) -> { (1.0) * 3, 
(2.0) * 1 }
+    // common value: (1.0)
+
+    String[] expectedHeader = new String[] {"col_a"};
+
+    // --- EXCEPT (DISTINCT) with alias ---
+    // t1_distinct = {1.0, 2.0}
+    // t2_distinct = {1.0, 3.0}
+    // Result: {2.0}
+    String[] retArray = new String[] {"2.0,"};
+    tableResultSetEqualTest(
+        "select col_a from ((select s1 as col_a, device as col_b from table1) 
except (select s2, device from table2)) order by col_a",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+
+    // --- EXCEPT ALL with alias ---
+    // Row (1.0): t1 has 3, t2 has 1. max(0, 3 - 1) = 2.
+    // Row (2.0): t1 has 1, t2 has 0. max(0, 1 - 0) = 1.
+    // Result: {1.0, 1.0, 2.0} (query has order by)
+    retArray = new String[] {"1.0,", "1.0,", "2.0,"};
+    tableResultSetEqualTest(
+        "select col_a from ((select s1 as col_a, device as col_b from table1) 
except all (select s2, device from table2)) order by col_a",
+        expectedHeader,
+        retArray,
+        DATABASE_NAME);
+  }
+
+  @Test
+  public void exceptionTest() {
+    // type is incompatible (INT32 vs TEXT)
+    tableAssertTestFail(
+        "(select * from table1) except all (select * from table4)",
+        "has incompatible types: INT32, TEXT",
+        DATABASE_NAME);
+
+    tableAssertTestFail(
+        "(select * from table1) except all (select time from table4)",
+        "EXCEPT query has different number of fields: 4, 1",
+        DATABASE_NAME);
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanGraphPrinter.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanGraphPrinter.java
index 82e03d8789b..bd4dd912e4c 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanGraphPrinter.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanGraphPrinter.java
@@ -71,6 +71,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.AggregationT
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.AssignUniqueId;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.DeviceTableScanNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.EnforceSingleRowNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExchangeNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExplainAnalyzeNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.GapFillNode;
@@ -1116,6 +1117,15 @@ public class PlanGraphPrinter extends 
PlanVisitor<List<String>, PlanGraphPrinter
     return render(node, boxValue, context);
   }
 
+  @Override
+  public List<String> visitExcept(ExceptNode node, GraphContext context) {
+    List<String> boxValue = new ArrayList<>();
+    boxValue.add(String.format("Except-%s", node.getPlanNodeId().getId()));
+    boxValue.add(String.format("OutputSymbols: %s", node.getOutputSymbols()));
+    boxValue.add(String.format("isDistinct: %s", node.isDistinct()));
+    return render(node, boxValue, context);
+  }
+
   private List<String> render(PlanNode node, List<String> nodeBoxString, 
GraphContext context) {
     Box box = new Box(nodeBoxString);
     List<List<String>> children = new ArrayList<>();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java
index 1100793ca88..56ad6d59ba0 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java
@@ -119,6 +119,7 @@ import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalIn
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.AggregationTreeDeviceViewScanNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.AssignUniqueId;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.EnforceSingleRowNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.GapFillNode;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.GroupNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.InformationSchemaTableScanNode;
@@ -314,6 +315,7 @@ public enum PlanNodeType {
   TABLE_INTO_NODE((short) 1033),
   TABLE_UNION_NODE((short) 1034),
   TABLE_INTERSECT_NODE((short) 1035),
+  TABLE_EXCEPT_NODE((short) 1036),
 
   RELATIONAL_INSERT_TABLET((short) 2000),
   RELATIONAL_INSERT_ROW((short) 2001),
@@ -705,6 +707,8 @@ public enum PlanNodeType {
         return UnionNode.deserialize(buffer);
       case 1035:
         return IntersectNode.deserialize(buffer);
+      case 1036:
+        return ExceptNode.deserialize(buffer);
       case 2000:
         return RelationalInsertTabletNode.deserialize(buffer);
       case 2001:
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java
index 9e8e1834247..ea669491a5f 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java
@@ -123,6 +123,7 @@ import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalIn
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.GroupReference;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.AggregationTreeDeviceViewScanNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.DeviceTableScanNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.GapFillNode;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.GroupNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.InformationSchemaTableScanNode;
@@ -845,4 +846,8 @@ public abstract class PlanVisitor<R, C> {
   public R visitIntersect(IntersectNode node, C context) {
     return visitPlan(node, context);
   }
+
+  public R visitExcept(ExceptNode node, C context) {
+    return visitPlan(node, context);
+  }
 }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java
index ce573ce1e2d..9732f8221d6 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/RelationPlanner.java
@@ -55,6 +55,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.TableMetadataImp
 import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.TreeDeviceViewSchema;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.DeviceTableScanNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.InformationSchemaTableScanNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.IntersectNode;
@@ -1164,6 +1165,24 @@ public class RelationPlanner extends 
AstVisitor<RelationPlan, Void> {
         intersectNode, analysis.getScope(node), 
intersectNode.getOutputSymbols(), outerContext);
   }
 
+  @Override
+  protected RelationPlan visitExcept(Except node, Void context) {
+    Preconditions.checkArgument(
+        !node.getRelations().isEmpty(), "No relations specified for except");
+    SetOperationPlan setOperationPlan = process(node);
+
+    PlanNode exceptNode =
+        new ExceptNode(
+            idAllocator.genPlanNodeId(),
+            setOperationPlan.getChildren(),
+            setOperationPlan.getSymbolMapping(),
+            ImmutableList.copyOf(setOperationPlan.getSymbolMapping().keySet()),
+            node.isDistinct());
+
+    return new RelationPlan(
+        exceptNode, analysis.getScope(node), exceptNode.getOutputSymbols(), 
outerContext);
+  }
+
   private SetOperationPlan process(SetOperation node) {
     RelationType outputFields = analysis.getOutputDescriptor(node);
     List<Symbol> outputs =
@@ -1210,11 +1229,6 @@ public class RelationPlanner extends 
AstVisitor<RelationPlan, Void> {
     throw new IllegalStateException("Values is not supported in current 
version.");
   }
 
-  @Override
-  protected RelationPlan visitExcept(Except node, Void context) {
-    throw new IllegalStateException("Except is not supported in current 
version.");
-  }
-
   @Override
   protected RelationPlan visitInsertTablet(InsertTablet node, Void context) {
     final InsertTabletStatement insertTabletStatement = 
node.getInnerTreeStatement();
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptAll.java
similarity index 51%
copy from 
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java
copy to 
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptAll.java
index 0a8f8c498ae..9cb2de73511 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptAll.java
@@ -19,66 +19,90 @@
 
 package org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule;
 
+import 
org.apache.iotdb.commons.udf.builtin.relational.TableBuiltinScalarFunction;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.Assignments;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Rule;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode;
-import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.IntersectNode;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.Patterns;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.ProjectNode;
+import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ArithmeticBinaryExpression;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Expression;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.FunctionCall;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.GenericLiteral;
+import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.QualifiedName;
 import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Captures;
 import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Pattern;
 
 import com.google.common.collect.ImmutableList;
+import org.apache.tsfile.read.common.type.LongType;
 
+import static com.google.common.base.Preconditions.checkState;
 import static java.util.Objects.requireNonNull;
-import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils.and;
-import static 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.Patterns.Except.distinct;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ArithmeticBinaryExpression.Operator.SUBTRACT;
 
-public class ImplementIntersectDistinctAsUnion implements Rule<IntersectNode> {
+public class ImplementExceptAll implements Rule<ExceptNode> {
 
-  private static final Pattern<IntersectNode> PATTERN =
-      Patterns.intersect().with(Patterns.Intersect.distinct().equalTo(true));
+  private static final Pattern<ExceptNode> PATTERN =
+      Patterns.except().with(distinct().equalTo(false));
 
   private final Metadata metadata;
 
-  @Override
-  public Pattern<IntersectNode> getPattern() {
-    return PATTERN;
+  public ImplementExceptAll(Metadata metadata) {
+    this.metadata = requireNonNull(metadata, "metadata is null");
   }
 
-  public ImplementIntersectDistinctAsUnion(Metadata metadata) {
-    this.metadata = requireNonNull(metadata, "metadata is null");
+  @Override
+  public Pattern<ExceptNode> getPattern() {
+    return PATTERN;
   }
 
   @Override
-  public Result apply(IntersectNode node, Captures captures, Context context) {
+  public Result apply(ExceptNode node, Captures captures, Context context) {
 
     SetOperationNodeTranslator translator =
         new SetOperationNodeTranslator(
             metadata, context.getSymbolAllocator(), context.getIdAllocator());
 
-    SetOperationNodeTranslator.TranslationResult result =
-        translator.makeSetContainmentPlanForDistinct(node);
-
-    // add the filterNode above the aggregation node
-    Expression predicate =
-        and(
-            result.getCountSymbols().stream()
-                .map(
-                    symbol ->
-                        new ComparisonExpression(
-                            GREATER_THAN_OR_EQUAL,
-                            symbol.toSymbolReference(),
-                            new GenericLiteral("INT64", "1")))
-                .collect(ImmutableList.toImmutableList()));
+    // 1. translate the except(all) node to other planNodes
+    SetOperationNodeTranslator.TranslationResult translationResult =
+        translator.makeSetContainmentPlanForAll(node);
+
+    checkState(
+        !translationResult.getCountSymbols().isEmpty(),
+        "ExceptNode translation result has no count symbols");
+
+    // 2. add the filter node above the result node from translation process
+    // filter condition : row_number <= greatest(...greatest((greatest(count1 
- count2, 0) - count3,
+    // 0))....)
+    Expression minusCount = 
translationResult.getCountSymbols().get(0).toSymbolReference();
+    QualifiedName greatest =
+        
QualifiedName.of(TableBuiltinScalarFunction.GREATEST.getFunctionName());
+    for (int i = 1; i < translationResult.getCountSymbols().size(); i++) {
+      minusCount =
+          new FunctionCall(
+              greatest,
+              ImmutableList.of(
+                  new ArithmeticBinaryExpression(
+                      SUBTRACT,
+                      minusCount,
+                      
translationResult.getCountSymbols().get(i).toSymbolReference()),
+                  new GenericLiteral(LongType.INT64.getDisplayName(), "0")));
+    }
 
     FilterNode filterNode =
-        new FilterNode(context.getIdAllocator().genPlanNodeId(), 
result.getPlanNode(), predicate);
+        new FilterNode(
+            context.getIdAllocator().genPlanNodeId(),
+            translationResult.getPlanNode(),
+            new ComparisonExpression(
+                ComparisonExpression.Operator.LESS_THAN_OR_EQUAL,
+                translationResult.getRowNumberSymbol().toSymbolReference(),
+                minusCount));
 
+    // 3. add the project node to remove the redundant columns
     return Result.ofPlanNode(
         new ProjectNode(
             context.getIdAllocator().genPlanNodeId(),
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptDistinctAsUnion.java
similarity index 69%
copy from 
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java
copy to 
iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptDistinctAsUnion.java
index 0a8f8c498ae..7b3d1c00156 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementExceptDistinctAsUnion.java
@@ -22,8 +22,8 @@ package 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.Assignments;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Rule;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode;
-import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.IntersectNode;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.Patterns;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.ProjectNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression;
@@ -33,29 +33,29 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Captures;
 import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Pattern;
 
 import com.google.common.collect.ImmutableList;
+import org.apache.tsfile.read.common.type.LongType;
 
 import static java.util.Objects.requireNonNull;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils.and;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL;
 
-public class ImplementIntersectDistinctAsUnion implements Rule<IntersectNode> {
-
-  private static final Pattern<IntersectNode> PATTERN =
-      Patterns.intersect().with(Patterns.Intersect.distinct().equalTo(true));
+public class ImplementExceptDistinctAsUnion implements Rule<ExceptNode> {
 
   private final Metadata metadata;
+  private static final Pattern<ExceptNode> PATTERN =
+      Patterns.except().with(Patterns.Except.distinct().equalTo(true));
 
-  @Override
-  public Pattern<IntersectNode> getPattern() {
-    return PATTERN;
+  public ImplementExceptDistinctAsUnion(Metadata metadata) {
+    this.metadata = requireNonNull(metadata, "metadata is null");
   }
 
-  public ImplementIntersectDistinctAsUnion(Metadata metadata) {
-    this.metadata = requireNonNull(metadata, "metadata is null");
+  @Override
+  public Pattern<ExceptNode> getPattern() {
+    return PATTERN;
   }
 
   @Override
-  public Result apply(IntersectNode node, Captures captures, Context context) {
+  public Result apply(ExceptNode node, Captures captures, Context context) {
 
     SetOperationNodeTranslator translator =
         new SetOperationNodeTranslator(
@@ -64,20 +64,26 @@ public class ImplementIntersectDistinctAsUnion implements 
Rule<IntersectNode> {
     SetOperationNodeTranslator.TranslationResult result =
         translator.makeSetContainmentPlanForDistinct(node);
 
-    // add the filterNode above the aggregation node
-    Expression predicate =
-        and(
-            result.getCountSymbols().stream()
-                .map(
-                    symbol ->
-                        new ComparisonExpression(
-                            GREATER_THAN_OR_EQUAL,
-                            symbol.toSymbolReference(),
-                            new GenericLiteral("INT64", "1")))
-                .collect(ImmutableList.toImmutableList()));
+    ImmutableList.Builder<Expression> predicatesBuilder = 
ImmutableList.builder();
+    predicatesBuilder.add(
+        new ComparisonExpression(
+            GREATER_THAN_OR_EQUAL,
+            result.getCountSymbols().get(0).toSymbolReference(),
+            new GenericLiteral(LongType.INT64.getDisplayName(), "1")));
+
+    for (int i = 1; i < node.getChildren().size(); i++) {
+      predicatesBuilder.add(
+          new ComparisonExpression(
+              ComparisonExpression.Operator.EQUAL,
+              result.getCountSymbols().get(i).toSymbolReference(),
+              new GenericLiteral(LongType.INT64.getDisplayName(), "0")));
+    }
 
     FilterNode filterNode =
-        new FilterNode(context.getIdAllocator().genPlanNodeId(), 
result.getPlanNode(), predicate);
+        new FilterNode(
+            context.getIdAllocator().genPlanNodeId(),
+            result.getPlanNode(),
+            and(predicatesBuilder.build()));
 
     return Result.ofPlanNode(
         new ProjectNode(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java
index 0a8f8c498ae..388631c795b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/ImplementIntersectDistinctAsUnion.java
@@ -33,6 +33,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Captures;
 import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Pattern;
 
 import com.google.common.collect.ImmutableList;
+import org.apache.tsfile.read.common.type.LongType;
 
 import static java.util.Objects.requireNonNull;
 import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.ir.IrUtils.and;
@@ -73,7 +74,7 @@ public class ImplementIntersectDistinctAsUnion implements 
Rule<IntersectNode> {
                         new ComparisonExpression(
                             GREATER_THAN_OR_EQUAL,
                             symbol.toSymbolReference(),
-                            new GenericLiteral("INT64", "1")))
+                            new 
GenericLiteral(LongType.INT64.getDisplayName(), "1")))
                 .collect(ImmutableList.toImmutableList()));
 
     FilterNode filterNode =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/PruneDistinctAggregation.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/PruneDistinctAggregation.java
index 26fd0278016..37d604f3dd4 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/PruneDistinctAggregation.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/PruneDistinctAggregation.java
@@ -24,6 +24,7 @@ import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Lookup;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Rule;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.AggregationNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.IntersectNode;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.UnionNode;
 import org.apache.iotdb.db.queryengine.plan.relational.utils.matching.Captures;
@@ -109,30 +110,14 @@ public class PruneDistinctAggregation implements 
Rule<AggregationNode> {
       return visitPlan(node, context);
     }
 
-    /*@Override
-    public PlanNode visitUnion(UnionNode node, Boolean context)
-    {
-        return rewriteChildren(node, context);
-    }
-
     @Override
-    public PlanNode visitIntersect(IntersectNode node, Boolean context)
-    {
-        if (node.isDistinct()) {
-            return rewriteChildren(node, context);
-        }
-        return visitPlan(node, context);
+    public PlanNode visitExcept(ExceptNode node, Boolean context) {
+      if (node.isDistinct()) {
+        return rewriteChildren(node, context);
+      }
+      return visitPlan(node, context);
     }
 
-    @Override
-    public PlanNode visitExcept(ExceptNode node, Boolean context)
-    {
-        if (node.isDistinct()) {
-            return rewriteChildren(node, context);
-        }
-        return visitPlan(node, context);
-    }*/
-
     @Override
     public PlanNode visitAggregation(AggregationNode node, Boolean context) {
       boolean distinct = isDistinctOperator(node);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/SetOperationNodeTranslator.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/SetOperationNodeTranslator.java
index 3088860564e..509af165b8b 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/SetOperationNodeTranslator.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/iterative/rule/SetOperationNodeTranslator.java
@@ -145,8 +145,8 @@ public class SetOperationNodeTranslator {
   }
 
   /**
-   * only for transforming the intersection (all) node, add the window node 
and group node above the
-   * union node
+   * for transforming the intersectNode (all) and exceptNode(all), add the 
window node and group
+   * node above the union node
    */
   private WindowNode appendCounts(
       UnionNode union,
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ExceptNode.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ExceptNode.java
new file mode 100644
index 00000000000..9eb1df47696
--- /dev/null
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/ExceptNode.java
@@ -0,0 +1,99 @@
+/*
+ * 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.queryengine.plan.relational.planner.node;
+
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.Symbol;
+
+import com.google.common.collect.ListMultimap;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+public class ExceptNode extends SetOperationNode {
+
+  private final boolean distinct;
+
+  public ExceptNode(
+      PlanNodeId id,
+      List<PlanNode> children,
+      ListMultimap<Symbol, Symbol> outputToInputs,
+      List<Symbol> outputs,
+      boolean distinct) {
+
+    super(id, children, outputToInputs, outputs);
+    this.distinct = distinct;
+  }
+
+  private ExceptNode(
+      PlanNodeId id,
+      ListMultimap<Symbol, Symbol> outputToInputs,
+      List<Symbol> outputs,
+      boolean distinct) {
+    super(id, outputToInputs, outputs);
+    this.distinct = distinct;
+  }
+
+  @Override
+  public <R, C> R accept(PlanVisitor<R, C> visitor, C context) {
+    return visitor.visitExcept(this, context);
+  }
+
+  public boolean isDistinct() {
+    return distinct;
+  }
+
+  @Override
+  public PlanNode clone() {
+    return new ExceptNode(getPlanNodeId(), getSymbolMapping(), 
getOutputSymbols(), distinct);
+  }
+
+  @Override
+  public List<String> getOutputColumnNames() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  protected void serializeAttributes(ByteBuffer byteBuffer) {
+    throw new UnsupportedOperationException(
+        "ExceptNode should never be serialized in current version");
+  }
+
+  @Override
+  protected void serializeAttributes(DataOutputStream stream) throws 
IOException {
+    throw new UnsupportedOperationException(
+        "ExceptNode should never be serialized in current version");
+  }
+
+  public static ExceptNode deserialize(ByteBuffer byteBuffer) {
+    throw new UnsupportedOperationException(
+        "ExceptNode should never be deserialized in current version");
+  }
+
+  @Override
+  public PlanNode replaceChildren(List<PlanNode> newChildren) {
+    return new ExceptNode(
+        getPlanNodeId(), newChildren, getSymbolMapping(), getOutputSymbols(), 
isDistinct());
+  }
+}
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/Patterns.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/Patterns.java
index d08ac9ecf72..0297add91f4 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/Patterns.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/node/Patterns.java
@@ -193,6 +193,10 @@ public final class Patterns {
     return typeOf(IntersectNode.class);
   }
 
+  public static Pattern<ExceptNode> except() {
+    return typeOf(ExceptNode.class);
+  }
+
   /*public static Pattern<TableWriterNode> tableWriterNode()
   {
       return typeOf(TableWriterNode.class);
@@ -365,6 +369,12 @@ public final class Patterns {
     }
   }
 
+  public static final class Except {
+    public static Property<ExceptNode, Lookup, Boolean> distinct() {
+      return property("distinct", ExceptNode::isDistinct);
+    }
+  }
+
   /*public static final class Sample
   {
       public static Property<SampleNode, Lookup, Double> sampleRatio()
@@ -421,16 +431,6 @@ public final class Patterns {
       }
   }*/
 
-  /*
-
-  public static final class Except
-  {
-      public static Property<ExceptNode, Lookup, Boolean> distinct()
-      {
-          return property("distinct", ExceptNode::isDistinct);
-      }
-  }
-  */
   public static final class PatternRecognition {
     public static Property<PatternRecognitionNode, Lookup, RowsPerMatch> 
rowsPerMatch() {
       return property("rowsPerMatch", PatternRecognitionNode::getRowsPerMatch);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/LogicalOptimizeFactory.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/LogicalOptimizeFactory.java
index 65015715945..d7084c3d24e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/LogicalOptimizeFactory.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/LogicalOptimizeFactory.java
@@ -26,6 +26,8 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Iterati
 import org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.Rule;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.RuleStatsRecorder;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.CanonicalizeExpressions;
+import 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.ImplementExceptAll;
+import 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.ImplementExceptDistinctAsUnion;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.ImplementIntersectAll;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.ImplementIntersectDistinctAsUnion;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.iterative.rule.ImplementPatternRecognition;
@@ -283,10 +285,9 @@ public class LogicalOptimizeFactory {
             ImmutableSet.<Rule<?>>builder()
                 .add(
                     new ImplementIntersectDistinctAsUnion(metadata),
-                    // new ImplementExceptDistinctAsUnion(metadata)
-                    new ImplementIntersectAll(metadata)
-                    // new ImplementExceptAll(metadata))),
-                    )
+                    new ImplementExceptDistinctAsUnion(metadata),
+                    new ImplementIntersectAll(metadata),
+                    new ImplementExceptAll(metadata))
                 .build()),
         columnPruningOptimizer,
         inlineProjectionLimitFiltersOptimizer,
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java
index ff3a01be849..3fdbec6b0cc 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/UnaliasSymbolReferences.java
@@ -36,6 +36,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.AssignUnique
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.CorrelatedJoinNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.DeviceTableScanNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.EnforceSingleRowNode;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExceptNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.ExplainAnalyzeNode;
 import org.apache.iotdb.db.queryengine.plan.relational.planner.node.FilterNode;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.node.GapFillNode;
@@ -1046,6 +1047,36 @@ public class UnaliasSymbolReferences implements 
PlanOptimizer {
           mapping);
     }
 
+    @Override
+    public PlanAndMappings visitExcept(ExceptNode node, UnaliasContext 
context) {
+
+      List<PlanAndMappings> rewrittenSources =
+          node.getChildren().stream()
+              .map(source -> source.accept(this, context))
+              .collect(toImmutableList());
+
+      List<SymbolMapper> inputMappers =
+          rewrittenSources.stream()
+              .map(source -> symbolMapper(new HashMap<>(source.getMappings())))
+              .collect(toImmutableList());
+
+      Map<Symbol, Symbol> mapping = new 
HashMap<>(context.getCorrelationMapping());
+      SymbolMapper outputMapper = symbolMapper(mapping);
+
+      ListMultimap<Symbol, Symbol> newOutputToInputs =
+          rewriteOutputToInputsMap(node.getSymbolMapping(), outputMapper, 
inputMappers);
+      List<Symbol> newOutputs = 
outputMapper.mapAndDistinct(node.getOutputSymbols());
+
+      return new PlanAndMappings(
+          new ExceptNode(
+              node.getPlanNodeId(),
+              
rewrittenSources.stream().map(PlanAndMappings::getRoot).collect(toImmutableList()),
+              newOutputToInputs,
+              newOutputs,
+              node.isDistinct()),
+          mapping);
+    }
+
     private ListMultimap<Symbol, Symbol> rewriteOutputToInputsMap(
         ListMultimap<Symbol, Symbol> oldMapping,
         SymbolMapper outputMapper,
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExceptTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExceptTest.java
new file mode 100644
index 00000000000..35d35693fce
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/ExceptTest.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.queryengine.plan.relational.analyzer;
+
+import org.apache.iotdb.db.queryengine.plan.planner.plan.LogicalQueryPlan;
+import org.apache.iotdb.db.queryengine.plan.relational.planner.PlanTester;
+
+import org.junit.Test;
+
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanAssert.assertPlan;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.aggregation;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.filter;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.output;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.project;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.sort;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.tableScan;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.union;
+import static 
org.apache.iotdb.db.queryengine.plan.relational.planner.assertions.PlanMatchPattern.window;
+
+/** tests for except (distinct) and except all */
+public class ExceptTest {
+
+  @Test
+  public void exceptTest() {
+
+    PlanTester planTester = new PlanTester();
+    LogicalQueryPlan actualLogicalQueryPlan =
+        planTester.createPlan("select tag1 from t1 except select tag1 from 
t2");
+    // Verify Logical plan:  `Output - project - filter - aggregation - union 
- 2*(project -
+    // tableScan)`
+    // This matches the ImplementExceptDistinctAsUnion logic
+    assertPlan(
+        actualLogicalQueryPlan,
+        output(
+            project(
+                filter(
+                    aggregation(
+                        union(
+                            project(tableScan("testdb.t1")), 
project(tableScan("testdb.t2"))))))));
+  }
+
+  @Test
+  public void exceptAllTest() {
+
+    PlanTester planTester = new PlanTester();
+    LogicalQueryPlan actualLogicalQueryPlan =
+        planTester.createPlan("select tag1 from t1 except all select tag1 from 
t2");
+    // Verify Logical plan: `Output - project - filter - project - window - 
sort - union -
+    // 2*(project - tableScan)`
+    // This matches the ImplementExceptAll logic
+    assertPlan(
+        actualLogicalQueryPlan,
+        output(
+            project(
+                filter(
+                    project(
+                        window(
+                            sort(
+                                union(
+                                    project(tableScan("testdb.t1")),
+                                    project(tableScan("testdb.t2"))))))))));
+  }
+
+  @Test
+  public void typeCompatibleTest() {
+    // use CAST if types of according columns is not compatible
+    // s1 is INT64, s3 is DOUBLE
+
+    PlanTester planTester = new PlanTester();
+    LogicalQueryPlan actualLogicalQueryPlan =
+        planTester.createPlan("select s1, s3 from table2 except all select s1, 
s1 from table3 ");
+
+    // The plan structure should be the same as exceptAllTest
+    assertPlan(
+        actualLogicalQueryPlan,
+        output(
+            project(
+                filter(
+                    project(
+                        window(
+                            sort(
+                                union(
+                                    project(tableScan("testdb.table2")),
+                                    
project(tableScan("testdb.table3"))))))))));
+  }
+
+  /**
+   * The priority of INTERSECT is higher than that of EXCEPT. This query is 
parsed as: t1 EXCEPT (t2
+   * INTERSECT t3)
+   */
+  @Test
+  public void setOperationPriority() {
+
+    PlanTester planTester = new PlanTester();
+    LogicalQueryPlan actualLogicalQueryPlan =
+        planTester.createPlan(
+            "select tag1 from t1 except select tag1 from t2 intersect select 
tag1 from t3");
+
+    assertPlan(
+        actualLogicalQueryPlan,
+        output(
+            project(
+                filter(
+                    aggregation(
+                        union(
+                            project(tableScan("testdb.t1")),
+                            project(
+                                project(
+                                    filter(
+                                        aggregation(
+                                            union(
+                                                
project(tableScan("testdb.t2")),
+                                                
project(tableScan("testdb.t3")))))))))))));
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMetadata.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMetadata.java
index 6182defaaec..aa9fcdfd1b5 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMetadata.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/TestMetadata.java
@@ -40,6 +40,7 @@ import org.apache.iotdb.db.queryengine.plan.function.Split;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.model.ModelInferenceDescriptor;
 import org.apache.iotdb.db.queryengine.plan.relational.function.OperatorType;
 import 
org.apache.iotdb.db.queryengine.plan.relational.function.TableBuiltinTableFunction;
+import 
org.apache.iotdb.db.queryengine.plan.relational.function.arithmetic.SubtractionResolver;
 import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.AlignedDeviceEntry;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnMetadata;
 import org.apache.iotdb.db.queryengine.plan.relational.metadata.ColumnSchema;
@@ -244,8 +245,16 @@ public class TestMetadata implements Metadata {
       throws OperatorNotFoundException {
 
     switch (operatorType) {
-      case ADD:
       case SUBTRACT:
+        Optional<Type> resolvedType = 
SubtractionResolver.checkConditions(argumentTypes);
+        return resolvedType.orElseThrow(
+            () ->
+                new OperatorNotFoundException(
+                    operatorType,
+                    argumentTypes,
+                    new IllegalArgumentException(
+                        "The combination of argument types is not supported 
for this operator.")));
+      case ADD:
       case MULTIPLY:
       case DIVIDE:
       case MODULUS:

Reply via email to