diff --git a/src/parser/TraverseSentences.h b/src/parser/TraverseSentences.h
index 0c717e1d11a968f65673764c47e5b5598e1f31a6..3b85b1fbdf9a1c765a8f0817b363af935f4bb683 100644
--- a/src/parser/TraverseSentences.h
+++ b/src/parser/TraverseSentences.h
@@ -158,7 +158,7 @@ public:
         return right_.get();
     }
 
-    auto op() {
+    Operator op() const {
         return op_;
     }
 
@@ -166,7 +166,7 @@ public:
         distinct_ = true;
     }
 
-    auto distinct() {
+    bool distinct() const {
         return distinct_;
     }
 
diff --git a/src/parser/parser.yy b/src/parser/parser.yy
index d17d67073c01e5bd52b55ddb6183655cb4d7576e..e8b422e23bf0e35e817954770af1bac8d57618cf 100644
--- a/src/parser/parser.yy
+++ b/src/parser/parser.yy
@@ -794,8 +794,8 @@ group_clause
 yield_sentence
     : KW_YIELD yield_columns where_clause {
         auto *s = new YieldSentence($2);
-		s->setWhereClause($3);
-		$$ = s;
+        s->setWhereClause($3);
+        $$ = s;
     }
     | KW_YIELD KW_DISTINCT yield_columns where_clause {
         auto *s = new YieldSentence($3, true);
diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h
index 459db15a6ec0cb2d82514d9f0a3ffdb5d6eda9ae..2c5c1f94a0742bd1762aa12fa120cdb422e9dda0 100644
--- a/src/planner/PlanNode.h
+++ b/src/planner/PlanNode.h
@@ -97,8 +97,8 @@ public:
         plan_ = plan;
     }
 
-    void setColNames(std::vector<std::string>&& cols) {
-        colNames_ = std::move(cols);
+    void setColNames(const std::vector<std::string>& cols) {
+        colNames_ = cols;
     }
 
     static const char* toString(Kind kind);
diff --git a/src/validator/SequentialValidator.cpp b/src/validator/SequentialValidator.cpp
index 263518f766f39319b079295790a9cb0fb4d87976..23df2d34c1c15782dc9bb06d7776f7332976591e 100644
--- a/src/validator/SequentialValidator.cpp
+++ b/src/validator/SequentialValidator.cpp
@@ -46,10 +46,7 @@ Status SequentialValidator::validateImpl() {
             }
         }
         auto validator = makeValidator(sentence, qctx_);
-        status = validator->validate();
-        if (!status.ok()) {
-            return status;
-        }
+        NG_RETURN_IF_ERROR(validator->validate());
         validators_.emplace_back(std::move(validator));
     }
 
@@ -61,13 +58,10 @@ Status SequentialValidator::toPlan() {
     root_ = validators_.back()->root();
     ifBuildDataCollectForRoot(root_);
     for (auto iter = validators_.begin(); iter < validators_.end() - 1; ++iter) {
-        auto status = Validator::appendPlan((iter + 1)->get()->tail(), iter->get()->root());
-        if (!status.ok()) {
-            return status;
-        }
+        NG_RETURN_IF_ERROR((iter + 1)->get()->appendPlan(iter->get()->root()));
     }
     tail_ = StartNode::make(plan);
-    Validator::appendPlan(validators_.front()->tail(), tail_);
+    NG_RETURN_IF_ERROR(validators_.front()->appendPlan(tail_));
     return Status::OK();
 }
 
diff --git a/src/validator/SetValidator.cpp b/src/validator/SetValidator.cpp
index 846f73a8cbef2a68be2aad930a9576cceaa167b0..59690ae62c3e30e7dbc2c8ecbb7a47aad6dfff3e 100644
--- a/src/validator/SetValidator.cpp
+++ b/src/validator/SetValidator.cpp
@@ -5,27 +5,24 @@
  */
 
 #include "validator/SetValidator.h"
+
 #include "planner/Query.h"
 
 namespace nebula {
 namespace graph {
+
 Status SetValidator::validateImpl() {
-    auto setSentence = static_cast<SetSentence*>(sentence_);
+    auto setSentence = static_cast<SetSentence *>(sentence_);
     lValidator_ = makeValidator(setSentence->left(), qctx_);
-    auto status = lValidator_->validate();
-    if (!status.ok()) {
-        return status;
-    }
+    NG_RETURN_IF_ERROR(lValidator_->validate());
     rValidator_ = makeValidator(setSentence->right(), qctx_);
-    status = rValidator_->validate();
-    if (!status.ok()) {
-        return status;
-    }
+    NG_RETURN_IF_ERROR(rValidator_->validate());
 
     auto lCols = lValidator_->outputs();
     auto rCols = rValidator_->outputs();
-    if (lCols.size() != rCols.size()) {
-        return Status::Error("The used statements have a diffrent number of columns.");
+
+    if (UNLIKELY(lCols != rCols)) {
+        return Status::Error("Different columns to UNION/INTERSECT/MINUS");
     }
 
     outputs_ = std::move(lCols);
@@ -33,43 +30,48 @@ Status SetValidator::validateImpl() {
 }
 
 Status SetValidator::toPlan() {
-    auto* plan = qctx_->plan();
-    switch (op_) {
+    auto plan = qctx_->plan();
+    auto setSentence = static_cast<const SetSentence *>(sentence_);
+    auto lRoot = DCHECK_NOTNULL(lValidator_->root());
+    auto rRoot = DCHECK_NOTNULL(rValidator_->root());
+    auto colNames = lRoot->colNames();
+    BiInputNode *bNode = nullptr;
+    switch (setSentence->op()) {
         case SetSentence::Operator::UNION: {
-            auto unionOp = Union::make(
-                    plan, lValidator_->root(), rValidator_->root());
-            if (distinct_) {
-                auto dedup = Dedup::make(
-                        plan,
-                        lValidator_->root());
+            bNode = Union::make(plan, lRoot, rRoot);
+            if (setSentence->distinct()) {
+                auto dedup = Dedup::make(plan, bNode);
+                dedup->setInputVar(bNode->varName());
+                dedup->setColNames(colNames);
                 root_ = dedup;
             } else {
-                root_ = unionOp;
+                root_ = bNode;
             }
             break;
         }
         case SetSentence::Operator::INTERSECT: {
-            root_ = Intersect::make(
-                    plan,
-                    lValidator_->root(),
-                    rValidator_->root());
+            bNode = Intersect::make(plan, lRoot, rRoot);
+            root_ = bNode;
             break;
         }
         case SetSentence::Operator::MINUS: {
-            root_ = Minus::make(
-                    plan,
-                    lValidator_->root(),
-                    rValidator_->root());
+            bNode = Minus::make(plan, lRoot, rRoot);
+            root_ = bNode;
             break;
         }
         default:
-            return Status::Error("Unkown operator: %ld", static_cast<int64_t>(op_));
+            return Status::Error("Unknown operator: %ld", static_cast<int64_t>(setSentence->op()));
     }
 
+    bNode->setColNames(colNames);
+    bNode->setLeftVar(lRoot->varName());
+    bNode->setRightVar(rRoot->varName());
+
     tail_ = MultiOutputsNode::make(plan, nullptr);
-    Validator::appendPlan(lValidator_->tail(), tail_);
-    Validator::appendPlan(rValidator_->tail(), tail_);
+    NG_RETURN_IF_ERROR(lValidator_->appendPlan(tail_));
+    NG_RETURN_IF_ERROR(rValidator_->appendPlan(tail_));
     return Status::OK();
 }
-}  // namespace graph
-}  // namespace nebula
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/validator/SetValidator.h b/src/validator/SetValidator.h
index 19cc8c690a09f3d3af27f740a3e34bd6c62ecd3a..3a475a6971e9409a38213ff75524ebe991205b31 100644
--- a/src/validator/SetValidator.h
+++ b/src/validator/SetValidator.h
@@ -12,6 +12,7 @@
 
 namespace nebula {
 namespace graph {
+
 class SetValidator final : public Validator {
 public:
     SetValidator(Sentence* sentence, QueryContext* context)
@@ -37,9 +38,8 @@ private:
 private:
     std::unique_ptr<Validator>  lValidator_;
     std::unique_ptr<Validator>  rValidator_;
-    SetSentence::Operator       op_;
-    bool                        distinct_;
 };
+
 }  // namespace graph
 }  // namespace nebula
 #endif  // VALIDATOR_SETVALIDATOR_H_
diff --git a/src/validator/Validator.cpp b/src/validator/Validator.cpp
index b72bc1121a8dbf5caad1880e5c83c02f272a1d00..25b2ec7f9104c3aed704e19baa0dcb674cce2572 100644
--- a/src/validator/Validator.cpp
+++ b/src/validator/Validator.cpp
@@ -74,7 +74,7 @@ std::unique_ptr<Validator> Validator::makeValidator(Sentence* sentence, QueryCon
 }
 
 Status Validator::appendPlan(PlanNode* node, PlanNode* appended) {
-    switch (node->kind()) {
+    switch (DCHECK_NOTNULL(node)->kind()) {
         case PlanNode::Kind::kFilter:
         case PlanNode::Kind::kProject:
         case PlanNode::Kind::kSort:
@@ -82,6 +82,7 @@ Status Validator::appendPlan(PlanNode* node, PlanNode* appended) {
         case PlanNode::Kind::kAggregate:
         case PlanNode::Kind::kSelect:
         case PlanNode::Kind::kLoop:
+        case PlanNode::Kind::kMultiOutputs:
         case PlanNode::Kind::kSwitchSpace:
         case PlanNode::Kind::kCreateSpace:
         case PlanNode::Kind::kCreateTag:
@@ -105,9 +106,11 @@ Status Validator::appendPlan(PlanNode* node, PlanNode* appended) {
     return Status::OK();
 }
 
-Status Validator::validate() {
-    Status status;
+Status Validator::appendPlan(PlanNode* tail) {
+    return appendPlan(tail_, DCHECK_NOTNULL(tail));
+}
 
+Status Validator::validate() {
     if (!vctx_) {
         VLOG(1) << "Validate context was not given.";
         return Status::Error("Validate context was not given.");
@@ -120,23 +123,15 @@ Status Validator::validate() {
 
     if (!noSpaceRequired_ && !spaceChosen()) {
         VLOG(1) << "Space was not chosen.";
-        status = Status::Error("Space was not chosen.");
-        return status;
+        return Status::Error("Space was not chosen.");
     }
 
     if (!noSpaceRequired_) {
         space_ = vctx_->whichSpace();
     }
 
-    status = validateImpl();
-    if (!status.ok()) {
-        return status;
-    }
-
-    status = toPlan();
-    if (!status.ok()) {
-        return status;
-    }
+    NG_RETURN_IF_ERROR(validateImpl());
+    NG_RETURN_IF_ERROR(toPlan());
 
     return Status::OK();
 }
@@ -511,4 +506,3 @@ bool Validator::evaluableExpr(const Expression* expr) const {
 
 }  // namespace graph
 }  // namespace nebula
-
diff --git a/src/validator/Validator.h b/src/validator/Validator.h
index e588785eb87a9eb520cebb9a1a23731fb3f3d6c0..c207693ee1ad318f61627b63f95938b3f6401e3d 100644
--- a/src/validator/Validator.h
+++ b/src/validator/Validator.h
@@ -28,7 +28,7 @@ public:
     static std::unique_ptr<Validator> makeValidator(Sentence* sentence,
                                                     QueryContext* context);
 
-    static Status appendPlan(PlanNode* plan, PlanNode* appended);
+    MUST_USE_RESULT Status appendPlan(PlanNode* tail);
 
     Status validate();
 
@@ -36,19 +36,19 @@ public:
         inputs_ = std::move(inputs);
     }
 
-    auto root() const {
+    PlanNode* root() const {
         return root_;
     }
 
-    auto tail() const {
+    PlanNode* tail() const {
         return tail_;
     }
 
-    auto outputs() const {
+    ColsDef outputs() const {
         return outputs_;
     }
 
-    auto inputs() const {
+    ColsDef inputs() const {
         return inputs_;
     }
 
@@ -80,6 +80,8 @@ protected:
 
     bool evaluableExpr(const Expression* expr) const;
 
+    static Status appendPlan(PlanNode* plan, PlanNode* appended);
+
 protected:
     SpaceDescription                space_;
     Sentence*                       sentence_{nullptr};
diff --git a/src/validator/test/QueryValidatorTest.cpp b/src/validator/test/QueryValidatorTest.cpp
index 1a16ab44277a1a24db23de1c80cf58595e657f6d..c56ef8519f656d8a2177cc27d0f83a21e4b15816 100644
--- a/src/validator/test/QueryValidatorTest.cpp
+++ b/src/validator/test/QueryValidatorTest.cpp
@@ -288,6 +288,124 @@ TEST_F(QueryValidatorTest, OrderByAndLimt) {
     }
 }
 
+TEST_F(QueryValidatorTest, TestSetValidator) {
+  // UNION ALL
+  {
+      std::string query =
+          "GO FROM \"1\" OVER like YIELD like.start AS start UNION ALL GO FROM \"2\" "
+          "OVER like YIELD like.start AS start";
+      auto status = validate(query);
+      ASSERT_TRUE(status.ok()) << status.status();
+      auto plan = std::move(status).value();
+      ASSERT_NE(plan, nullptr);
+      std::vector<PlanNode::Kind> expected = {
+          PK::kDataCollect,
+          PK::kUnion,
+          PK::kProject,
+          PK::kProject,
+          PK::kGetNeighbors,
+          PK::kGetNeighbors,
+          PK::kMultiOutputs,
+          PK::kStart,
+      };
+      ASSERT_TRUE(verifyPlan(plan->root(), expected));
+  }
+  // UNION DISTINCT twice
+  {
+      std::string query = "GO FROM \"1\" OVER like YIELD like.start AS start UNION GO FROM \"2\" "
+                          "OVER like YIELD like.start AS start UNION GO FROM \"3\" OVER like YIELD "
+                          "like.start AS start";
+      auto status = validate(query);
+      ASSERT_TRUE(status.ok()) << status.status();
+      auto plan = std::move(status).value();
+      ASSERT_NE(plan, nullptr);
+      std::vector<PlanNode::Kind> expected = {
+          PK::kDataCollect,
+          PK::kDedup,
+          PK::kUnion,
+          PK::kDedup,
+          PK::kProject,
+          PK::kUnion,
+          PK::kGetNeighbors,
+          PK::kProject,
+          PK::kProject,
+          PK::kMultiOutputs,
+          PK::kGetNeighbors,
+          PK::kGetNeighbors,
+          PK::kStart,
+          PK::kMultiOutputs,
+      };
+      ASSERT_TRUE(verifyPlan(plan->root(), expected));
+  }
+  // UNION DISTINCT
+  {
+      std::string query =
+          "GO FROM \"1\" OVER like YIELD like.start AS start UNION DISTINCT GO FROM \"2\" "
+          "OVER like YIELD like.start AS start";
+      auto status = validate(query);
+      EXPECT_TRUE(status.ok()) << status.status();
+      auto plan = std::move(status).value();
+      ASSERT_NE(plan, nullptr);
+      std::vector<PlanNode::Kind> expected = {
+          PK::kDataCollect,
+          PK::kDedup,
+          PK::kUnion,
+          PK::kProject,
+          PK::kProject,
+          PK::kGetNeighbors,
+          PK::kGetNeighbors,
+          PK::kMultiOutputs,
+          PK::kStart,
+      };
+      ASSERT_TRUE(verifyPlan(plan->root(), expected));
+  }
+  // INVALID UNION ALL
+  {
+      std::string query = "GO FROM \"1\" OVER like YIELD like.start AS start, $^.person.name AS "
+                          "name UNION GO FROM \"2\" OVER like YIELD like.start AS start";
+      auto status = validate(query);
+      ASSERT_FALSE(status.ok()) << status.status();
+  }
+  // INTERSECT
+  {
+      std::string query = "GO FROM \"1\" OVER like YIELD like.start AS start INTERSECT GO FROM "
+                          "\"2\" OVER like YIELD like.start AS start";
+      auto status = validate(query);
+      EXPECT_TRUE(status.ok()) << status.status();
+      auto plan = std::move(status).value();
+      ASSERT_NE(plan, nullptr);
+      std::vector<PlanNode::Kind> expected = {
+          PK::kDataCollect,
+          PK::kIntersect,
+          PK::kProject,
+          PK::kProject,
+          PK::kGetNeighbors,
+          PK::kGetNeighbors,
+          PK::kMultiOutputs,
+          PK::kStart,
+      };
+      ASSERT_TRUE(verifyPlan(plan->root(), expected));
+  }
+  // MINUS
+  {
+      std::string query = "GO FROM \"1\" OVER like YIELD like.start AS start MINUS GO FROM "
+                          "\"2\" OVER like YIELD like.start AS start";
+      auto status = validate(query);
+      EXPECT_TRUE(status.ok()) << status.status();
+      auto plan = std::move(status).value();
+      ASSERT_NE(plan, nullptr);
+      std::vector<PlanNode::Kind> expected = {
+          PK::kDataCollect,
+          PK::kMinus,
+          PK::kProject,
+          PK::kProject,
+          PK::kGetNeighbors,
+          PK::kGetNeighbors,
+          PK::kMultiOutputs,
+          PK::kStart,
+      };
+      ASSERT_TRUE(verifyPlan(plan->root(), expected));
+  }
+}
 }  // namespace graph
 }  // namespace nebula
-